From 512afb07e754672a0b35182f020ac2903d7902b8 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 18 Jan 2026 14:20:43 -0500 Subject: [PATCH] Add for-you empty state controller --- js/forYouView.js | 143 ++++++++++++++++++++++++++++++++++++++++++++++ js/viewManager.js | 2 + 2 files changed, 145 insertions(+) create mode 100644 js/forYouView.js diff --git a/js/forYouView.js b/js/forYouView.js new file mode 100644 index 00000000..8fceac2d --- /dev/null +++ b/js/forYouView.js @@ -0,0 +1,143 @@ +import { getApplication } from "./applicationContext.js"; +import { devLogger } from "./utils/logger.js"; + +const VIDEO_CARD_SELECTOR = "[data-component=\"video-card\"]"; +const EMPTY_STATE_SELECTOR = "[data-for-you-empty-state]"; +const LOADING_SELECTOR = ".sidebar-loading-wrapper"; + +class ForYouView { + constructor({ document: doc = document } = {}) { + this.document = doc; + this.container = null; + this.observer = null; + this.handleContainerClick = this.handleContainerClick.bind(this); + this.handleMutation = this.handleMutation.bind(this); + } + + init() { + this.container = this.document?.getElementById("videoList") || null; + if (!this.container) { + return; + } + + this.container.addEventListener("click", this.handleContainerClick); + this.observeContainer(); + this.updateEmptyState(); + } + + 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(); + } + + handleContainerClick(event) { + const target = event?.target instanceof HTMLElement ? event.target : null; + const actionButton = target?.closest?.("[data-action=\"open-hashtag-preferences\"]"); + if (!(actionButton instanceof HTMLElement)) { + return; + } + + event.preventDefault(); + this.openHashtagPreferences(); + } + + openHashtagPreferences() { + const app = getApplication(); + if (app?.profileController && typeof app.profileController.show === "function") { + app.profileController.show("hashtags"); + return; + } + + devLogger.warn("[ForYouView] Profile modal controller unavailable."); + } + + 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.forYouEmptyState = "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 = "Your For You feed is empty"; + + const copy = this.document.createElement("p"); + copy.className = "mt-2 text-sm text-muted"; + copy.textContent = + "Add some interests to personalize your recommendations and start discovering videos."; + + const button = this.document.createElement("button"); + button.type = "button"; + button.className = "btn focus-ring mt-4"; + button.dataset.action = "open-hashtag-preferences"; + button.textContent = "Add interests"; + + card.appendChild(title); + card.appendChild(copy); + card.appendChild(button); + wrapper.appendChild(card); + + return wrapper; + } +} + +export function initForYouView() { + const view = new ForYouView(); + view.init(); + return view; +} diff --git a/js/viewManager.js b/js/viewManager.js index 4a783274..c40443a2 100644 --- a/js/viewManager.js +++ b/js/viewManager.js @@ -1,5 +1,6 @@ // js/viewManager.js import { initChannelProfileView } from "./channelProfile.js"; +import { initForYouView } from "./forYouView.js"; import { subscriptions } from "./subscriptions.js"; import { getApplication } from "./applicationContext.js"; import { ASSET_VERSION } from "../config/asset-version.js"; @@ -93,6 +94,7 @@ export const viewInitRegistry = { } }, "for-you": () => { + initForYouView(); const app = getApplication(); if (app && typeof app.loadForYouVideos === "function") { if (typeof app.mountVideoListView === "function") {