From 3dfba92e046ae76e58d75d9928f6e1eb6f8c622c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 03:40:36 +0000 Subject: [PATCH] refactor(ai): extract VideoModalController from js/app.js (agent) Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/app.js | 165 +++------------------ js/ui/videoModalController.js | 202 ++++++++++++++++++++++++++ tests/video-modal-controller.test.mjs | 113 ++++++++++++++ 3 files changed, 332 insertions(+), 148 deletions(-) create mode 100644 js/ui/videoModalController.js create mode 100644 tests/video-modal-controller.test.mjs diff --git a/js/app.js b/js/app.js index 50123bd6..8658f073 100644 --- a/js/app.js +++ b/js/app.js @@ -173,6 +173,7 @@ import EngagementController from "./ui/engagementController.js"; import SimilarContentController from "./ui/similarContentController.js"; import UrlHealthController from "./ui/urlHealthController.js"; import VideoModalCommentController from "./ui/videoModalCommentController.js"; +import VideoModalController from "./ui/videoModalController.js"; import TorrentStatusController from "./ui/torrentStatusController.js"; import ShareNostrController from "./ui/shareNostrController.js"; import ModerationActionController from "./services/moderationActionController.js"; @@ -251,6 +252,16 @@ class Application { const { modalManager } = this.bootstrapper.initialize(); this.modalManager = modalManager; + this.videoModalController = new VideoModalController({ + getVideoModal: () => this.videoModal, + callbacks: { + showError: (msg) => this.showError(msg), + getLastModalTrigger: () => this.lastModalTrigger, + setLastModalTrigger: (val) => this.setLastModalTrigger(val), + getCurrentVideo: () => this.currentVideo, + }, + }); + this.modalCreatorProfileRequestToken = null; this.dmRecipientPubkey = null; this.dmRelayHints = new Map(); @@ -1176,162 +1187,21 @@ class Application { * Show the modal and set the "Please stand by" poster on the video. */ async showModalWithPoster(video = this.currentVideo, options = {}) { - if (!this.videoModal) { - return null; - } - - if (Object.prototype.hasOwnProperty.call(options || {}, "trigger")) { - this.setLastModalTrigger(options.trigger); - } - - const targetVideo = video || this.currentVideo; - if (!targetVideo) { - this.log( - "[Application] Skipping video modal open; no target video is available.", - ); - return null; - } - - try { - const { root } = await this.ensureVideoModalReady({ - ensureVideoElement: true, - }); - - if (!this.videoModal) { - return root || null; - } - - this.videoModal.open(targetVideo, { - triggerElement: this.lastModalTrigger, - }); - this.applyModalLoadingPoster(); - - return ( - root || - (typeof this.videoModal.getRoot === "function" - ? this.videoModal.getRoot() - : null) - ); - } catch (error) { - devLogger.error( - "[Application] Failed to open the video modal before playback:", - error - ); - this.showError("Could not open the video player. Please try again."); - return null; - } + return this.videoModalController.showModalWithPoster(video, options); } applyModalLoadingPoster() { - if (!this.videoModal) { - return; - } - this.videoModal.applyLoadingPoster(); + this.videoModalController.applyModalLoadingPoster(); } forceRemoveModalPoster(reason = "manual-clear") { - if (!this.videoModal) { - return false; - } - return this.videoModal.forceRemovePoster(reason); + return this.videoModalController.forceRemoveModalPoster(reason); } async ensureVideoModalReady({ ensureVideoElement = false } = {}) { - if (!this.videoModal) { - throw new Error("Video modal instance is not available."); - } - - const getRoot = () => - typeof this.videoModal.getRoot === "function" - ? this.videoModal.getRoot() - : null; - const getVideoElement = () => - typeof this.videoModal.getVideoElement === "function" - ? this.videoModal.getVideoElement() - : null; - - const existingRoot = getRoot(); - const existingVideoElement = getVideoElement(); - const rootConnected = Boolean(existingRoot && existingRoot.isConnected); - const hasVideoElement = Boolean(existingVideoElement); - const videoConnected = Boolean( - existingVideoElement && existingVideoElement.isConnected, - ); - - const needsRehydrate = - !rootConnected || !hasVideoElement || !videoConnected; - - if (!needsRehydrate) { - this.modalVideo = existingVideoElement; - - if ( - existingVideoElement && - this.videoModal && - typeof this.videoModal.setVideoElement === "function" - ) { - const modalVideo = - typeof this.videoModal.getVideoElement === "function" - ? this.videoModal.getVideoElement() - : null; - if (modalVideo !== existingVideoElement) { - this.videoModal.setVideoElement(existingVideoElement); - } - } - - return { - root: existingRoot, - videoElement: existingVideoElement, - }; - } - - if (!videoConnected) { - this.modalVideo = null; - } - - if (!this.videoModalReadyPromise) { - if (typeof this.videoModal.load !== "function") { - throw new Error("Video modal does not expose a load() method."); - } - this.videoModalReadyPromise = Promise.resolve(this.videoModal.load()); - } - - try { - await this.videoModalReadyPromise; - } catch (error) { - this.videoModalReadyPromise = null; - throw error; - } - - this.videoModalReadyPromise = null; - - const readyRoot = getRoot(); - const readyVideoElement = getVideoElement(); - const readyVideoConnected = Boolean( - readyVideoElement && readyVideoElement.isConnected, - ); - - if (readyVideoConnected) { - this.modalVideo = readyVideoElement; - if ( - this.videoModal && - typeof this.videoModal.setVideoElement === "function" - ) { - this.videoModal.setVideoElement(readyVideoElement); - } - } else { - this.modalVideo = null; - } - - if (ensureVideoElement && !readyVideoConnected) { - throw new Error( - "Video modal video element is missing after load().", - ); - } - - return { - root: readyRoot, - videoElement: readyVideoConnected ? readyVideoElement : null, - }; + const result = await this.videoModalController.ensureVideoModalReady({ ensureVideoElement }); + this.modalVideo = result.videoElement; + return result; } initializeSimilarContentController() { @@ -10024,7 +9894,6 @@ class Application { destroy() { this.clearActiveIntervals(); this.teardownModalViewCountSubscription(); - this.videoModalReadyPromise = null; if (this.moderationActionController) { try { diff --git a/js/ui/videoModalController.js b/js/ui/videoModalController.js new file mode 100644 index 00000000..7f32497a --- /dev/null +++ b/js/ui/videoModalController.js @@ -0,0 +1,202 @@ +import { devLogger } from "../utils/logger.js"; + +export default class VideoModalController { + constructor({ + getVideoModal, + callbacks: { + showError, + getLastModalTrigger, + setLastModalTrigger, + getCurrentVideo, + } = {}, + }) { + this.getVideoModal = getVideoModal; + this.showError = showError; + this.getLastModalTrigger = getLastModalTrigger; + this.setLastModalTrigger = setLastModalTrigger; + this.getCurrentVideo = getCurrentVideo; + + this.videoModalReadyPromise = null; + } + + get videoModal() { + return this.getVideoModal ? this.getVideoModal() : null; + } + + log(message, ...args) { + devLogger.log("[VideoModalController]", message, ...args); + } + + /** + * Show the modal and set the "Please stand by" poster on the video. + */ + async showModalWithPoster(video = null, options = {}) { + if (!this.videoModal) { + return null; + } + + if (Object.prototype.hasOwnProperty.call(options || {}, "trigger")) { + if (this.setLastModalTrigger) { + this.setLastModalTrigger(options.trigger); + } + } + + const targetVideo = + video || (this.getCurrentVideo ? this.getCurrentVideo() : null); + if (!targetVideo) { + this.log("Skipping video modal open; no target video is available."); + return null; + } + + try { + const { root } = await this.ensureVideoModalReady({ + ensureVideoElement: true, + }); + + if (!this.videoModal) { + return root || null; + } + + const trigger = this.getLastModalTrigger + ? this.getLastModalTrigger() + : null; + this.videoModal.open(targetVideo, { + triggerElement: trigger, + }); + this.applyModalLoadingPoster(); + + return ( + root || + (typeof this.videoModal.getRoot === "function" + ? this.videoModal.getRoot() + : null) + ); + } catch (error) { + devLogger.error( + "[VideoModalController] Failed to open the video modal before playback:", + error, + ); + if (this.showError) { + this.showError("Could not open the video player. Please try again."); + } + return null; + } + } + + applyModalLoadingPoster() { + if (!this.videoModal) { + return; + } + this.videoModal.applyLoadingPoster(); + } + + forceRemoveModalPoster(reason = "manual-clear") { + if (!this.videoModal) { + return false; + } + return this.videoModal.forceRemovePoster(reason); + } + + async ensureVideoModalReady({ ensureVideoElement = false } = {}) { + if (!this.videoModal) { + throw new Error("Video modal instance is not available."); + } + + const getRoot = () => + typeof this.videoModal.getRoot === "function" + ? this.videoModal.getRoot() + : null; + const getVideoElement = () => + typeof this.videoModal.getVideoElement === "function" + ? this.videoModal.getVideoElement() + : null; + + const existingRoot = getRoot(); + const existingVideoElement = getVideoElement(); + const rootConnected = Boolean(existingRoot && existingRoot.isConnected); + const hasVideoElement = Boolean(existingVideoElement); + const videoConnected = Boolean( + existingVideoElement && existingVideoElement.isConnected, + ); + + const needsRehydrate = + !rootConnected || !hasVideoElement || !videoConnected; + + if (!needsRehydrate) { + if ( + existingVideoElement && + this.videoModal && + typeof this.videoModal.setVideoElement === "function" + ) { + const currentModalVideo = + typeof this.videoModal.getVideoElement === "function" + ? this.videoModal.getVideoElement() + : null; + if (currentModalVideo !== existingVideoElement) { + this.videoModal.setVideoElement(existingVideoElement); + } + } + + return { + root: existingRoot, + videoElement: existingVideoElement, + }; + } + + if (!videoConnected) { + if ( + this.videoModal && + typeof this.videoModal.setVideoElement === "function" + ) { + this.videoModal.setVideoElement(null); + } + } + + if (!this.videoModalReadyPromise) { + if (typeof this.videoModal.load !== "function") { + throw new Error("Video modal does not expose a load() method."); + } + this.videoModalReadyPromise = Promise.resolve(this.videoModal.load()); + } + + try { + await this.videoModalReadyPromise; + } catch (error) { + this.videoModalReadyPromise = null; + throw error; + } + + this.videoModalReadyPromise = null; + + const readyRoot = getRoot(); + const readyVideoElement = getVideoElement(); + const readyVideoConnected = Boolean( + readyVideoElement && readyVideoElement.isConnected, + ); + + if (readyVideoConnected) { + if ( + this.videoModal && + typeof this.videoModal.setVideoElement === "function" + ) { + this.videoModal.setVideoElement(readyVideoElement); + } + } else { + if ( + this.videoModal && + typeof this.videoModal.setVideoElement === "function" + ) { + this.videoModal.setVideoElement(null); + } + } + + if (ensureVideoElement && !readyVideoConnected) { + throw new Error("Video modal video element is missing after load()."); + } + + return { + root: readyRoot, + videoElement: readyVideoConnected ? readyVideoElement : null, + }; + } +} diff --git a/tests/video-modal-controller.test.mjs b/tests/video-modal-controller.test.mjs new file mode 100644 index 00000000..311d2c54 --- /dev/null +++ b/tests/video-modal-controller.test.mjs @@ -0,0 +1,113 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import VideoModalController from "../js/ui/videoModalController.js"; + +test("VideoModalController", async (t) => { + await t.test("ensureVideoModalReady throws if modal is missing", async () => { + const controller = new VideoModalController({ + getVideoModal: () => null, + }); + await assert.rejects( + controller.ensureVideoModalReady(), + /Video modal instance is not available/ + ); + }); + + await t.test("ensureVideoModalReady loads modal if needs rehydrate", async () => { + let loadCalled = false; + let setVideoElementCalled = false; + const mockRoot = { isConnected: true }; + const mockVideoElement = { isConnected: true }; + + const mockModal = { + // First call returns null (simulate missing root), subsequent calls return valid + getRoot: (() => { + let calls = 0; + return () => { + calls++; + return calls === 1 ? null : mockRoot; + }; + })(), + getVideoElement: () => mockVideoElement, + load: async () => { + loadCalled = true; + }, + setVideoElement: (el) => { + setVideoElementCalled = true; + assert.equal(el, mockVideoElement); + }, + }; + + const controller = new VideoModalController({ + getVideoModal: () => mockModal, + }); + + const result = await controller.ensureVideoModalReady(); + + assert.equal(loadCalled, true, "Should call load()"); + assert.equal(setVideoElementCalled, true, "Should set video element"); + assert.equal(result.root, mockRoot); + assert.equal(result.videoElement, mockVideoElement); + }); + + await t.test("showModalWithPoster uses provided video", async () => { + const mockVideo = { id: "v1" }; + let openedVideo = null; + let appliedPoster = false; + + const mockModal = { + getRoot: () => ({ isConnected: true }), + getVideoElement: () => ({ isConnected: true }), + load: async () => {}, + open: (video) => { openedVideo = video; }, + applyLoadingPoster: () => { appliedPoster = true; }, + setVideoElement: () => {}, + }; + + const controller = new VideoModalController({ + getVideoModal: () => mockModal, + }); + + await controller.showModalWithPoster(mockVideo); + assert.deepEqual(openedVideo, mockVideo); + assert.equal(appliedPoster, true); + }); + + await t.test("showModalWithPoster falls back to current video", async () => { + const currentVideo = { id: "current" }; + let openedVideo = null; + + const mockModal = { + getRoot: () => ({ isConnected: true }), + getVideoElement: () => ({ isConnected: true }), + load: async () => {}, + open: (video) => { openedVideo = video; }, + applyLoadingPoster: () => {}, + setVideoElement: () => {}, + }; + + const controller = new VideoModalController({ + getVideoModal: () => mockModal, + callbacks: { + getCurrentVideo: () => currentVideo, + } + }); + + await controller.showModalWithPoster(); // No video passed + assert.deepEqual(openedVideo, currentVideo); + }); + + await t.test("forceRemoveModalPoster calls modal method", async () => { + let removeReason = null; + const mockModal = { + forceRemovePoster: (reason) => { removeReason = reason; return true; } + }; + + const controller = new VideoModalController({ + getVideoModal: () => mockModal, + }); + + controller.forceRemoveModalPoster("test-reason"); + assert.equal(removeReason, "test-reason"); + }); +});