diff --git a/js/exploreView.js b/js/exploreView.js new file mode 100644 index 00000000..82316796 --- /dev/null +++ b/js/exploreView.js @@ -0,0 +1,120 @@ +import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js"; + +const VIDEO_CARD_SELECTOR = "[data-component=\"video-card\"]"; +const EMPTY_STATE_SELECTOR = "[data-explore-empty-state]"; +const LOADING_SELECTOR = ".sidebar-loading-wrapper"; + +class ExploreView { + constructor({ document: doc = document } = {}) { + this.document = doc; + this.container = null; + this.observer = null; + this.handleMutation = this.handleMutation.bind(this); + } + + init() { + this.container = this.document?.getElementById("videoList") || null; + if (!this.container) { + return; + } + + this.observeContainer(); + this.updateEmptyState(); + + const infoTrigger = this.document.getElementById("exploreInfoTrigger"); + if (infoTrigger) { + attachFeedInfoPopover( + infoTrigger, + "Explore scores videos using freshness, engagement, and diversity signals to surface a balanced mix." + ); + } + } + + observeContainer() { + if (!this.container || typeof MutationObserver === "undefined") { + return; + } + + if (this.observer) { + this.observer.disconnect(); + } + + this.observer = new MutationObserver(this.handleMutation); + this.observer.observe(this.container, { + childList: true, + subtree: true, + }); + } + + handleMutation() { + this.updateEmptyState(); + } + + updateEmptyState() { + if (!this.container) { + return; + } + + const hasVideos = Boolean(this.container.querySelector(VIDEO_CARD_SELECTOR)); + const isLoading = Boolean(this.container.querySelector(LOADING_SELECTOR)); + const existingEmptyState = this.container.querySelector(EMPTY_STATE_SELECTOR); + + if (isLoading) { + if (existingEmptyState) { + existingEmptyState.remove(); + } + return; + } + + if (hasVideos) { + if (existingEmptyState) { + existingEmptyState.remove(); + } + return; + } + + if (existingEmptyState) { + return; + } + + const emptyState = this.buildEmptyState(); + if (emptyState) { + this.container.innerHTML = ""; + this.container.appendChild(emptyState); + } + } + + buildEmptyState() { + if (!this.document) { + return null; + } + + const wrapper = this.document.createElement("div"); + wrapper.className = "col-span-full"; + wrapper.dataset.exploreEmptyState = "true"; + + const card = this.document.createElement("div"); + card.className = "card p-lg text-center"; + + const title = this.document.createElement("h3"); + title.className = "text-lg font-semibold text-text"; + title.textContent = "Explore is empty right now"; + + const copy = this.document.createElement("p"); + copy.className = "mt-2 text-sm text-muted"; + copy.textContent = + "Check back soon for a fresh mix of videos across the network."; + + card.appendChild(title); + card.appendChild(copy); + wrapper.appendChild(card); + + return wrapper; + } +} + +export function initExploreView() { + const view = new ExploreView(); + view.init(); + return view; +} diff --git a/js/viewManager.js b/js/viewManager.js index 71744c6d..2522fcaa 100644 --- a/js/viewManager.js +++ b/js/viewManager.js @@ -1,6 +1,7 @@ // js/viewManager.js import { initChannelProfileView } from "./channelProfile.js"; import { initForYouView } from "./forYouView.js"; +import { initExploreView } from "./exploreView.js"; import { subscriptions } from "./subscriptions.js"; import { getApplication } from "./applicationContext.js"; import { ASSET_VERSION } from "../config/asset-version.js"; @@ -118,7 +119,7 @@ export const viewInitRegistry = { } }, explore: () => { - devLogger.log("Explore view loaded."); + initExploreView(); const app = getApplication(); if (app && typeof app.loadExploreVideos === "function") { if (typeof app.mountVideoListView === "function") { @@ -130,13 +131,6 @@ export const viewInitRegistry = { if (refreshApp && typeof refreshApp.forceRefreshAllProfiles === "function") { refreshApp.forceRefreshAllProfiles(); } - const infoTrigger = document.getElementById("exploreInfoTrigger"); - if (infoTrigger) { - attachFeedInfoPopover( - infoTrigger, - "Discovery feed blended for freshness, novelty, and diversity." - ); - } }, history: async () => { try {