From 8804f6b03f51b47bc11bafdcbcff5209203283b8 Mon Sep 17 00:00:00 2001 From: sudacode Date: Mon, 9 Feb 2026 23:40:44 -0800 Subject: [PATCH] refactor: extract overlay broadcast runtime helpers --- package.json | 2 +- .../overlay-broadcast-runtime-service.test.ts | 58 +++++++++++++++++++ .../overlay-broadcast-runtime-service.ts | 45 ++++++++++++++ src/main.ts | 37 +++++++----- 4 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/core/services/overlay-broadcast-runtime-service.test.ts create mode 100644 src/core/services/overlay-broadcast-runtime-service.ts diff --git a/package.json b/package.json index 8b7d951..0ca135f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort", "test:config": "pnpm run build && node --test dist/config/config.test.js", - "test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-service.test.js dist/core/services/numeric-shortcut-session-service.test.js dist/core/services/secondary-subtitle-service.test.js dist/core/services/mpv-render-metrics-service.test.js dist/core/services/mpv-runtime-service.test.js dist/core/services/runtime-options-runtime-service.test.js dist/core/services/overlay-modal-restore-service.test.js dist/core/services/runtime-config-service.test.js dist/core/services/overlay-bridge-runtime-service.test.js dist/core/services/overlay-visibility-facade-service.test.js", + "test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-service.test.js dist/core/services/numeric-shortcut-session-service.test.js dist/core/services/secondary-subtitle-service.test.js dist/core/services/mpv-render-metrics-service.test.js dist/core/services/mpv-runtime-service.test.js dist/core/services/runtime-options-runtime-service.test.js dist/core/services/overlay-modal-restore-service.test.js dist/core/services/runtime-config-service.test.js dist/core/services/overlay-bridge-runtime-service.test.js dist/core/services/overlay-visibility-facade-service.test.js dist/core/services/overlay-broadcast-runtime-service.test.js", "test:subtitle": "pnpm run build && node --test dist/subtitle/stages.test.js dist/subtitle/pipeline.test.js", "generate:config-example": "pnpm run build && node dist/generate-config-example.js", "start": "pnpm run build && electron . --start", diff --git a/src/core/services/overlay-broadcast-runtime-service.test.ts b/src/core/services/overlay-broadcast-runtime-service.test.ts new file mode 100644 index 0000000..08995d0 --- /dev/null +++ b/src/core/services/overlay-broadcast-runtime-service.test.ts @@ -0,0 +1,58 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + broadcastRuntimeOptionsChangedRuntimeService, + broadcastToOverlayWindowsRuntimeService, + getOverlayWindowsRuntimeService, + setOverlayDebugVisualizationEnabledRuntimeService, +} from "./overlay-broadcast-runtime-service"; + +test("getOverlayWindowsRuntimeService returns non-destroyed windows only", () => { + const alive = { isDestroyed: () => false } as unknown as Electron.BrowserWindow; + const dead = { isDestroyed: () => true } as unknown as Electron.BrowserWindow; + const windows = getOverlayWindowsRuntimeService({ + mainWindow: alive, + invisibleWindow: dead, + }); + assert.deepEqual(windows, [alive]); +}); + +test("broadcastToOverlayWindowsRuntimeService sends channel to each window", () => { + const calls: unknown[][] = []; + const window = { + webContents: { + send: (...args: unknown[]) => { + calls.push(args); + }, + }, + } as unknown as Electron.BrowserWindow; + broadcastToOverlayWindowsRuntimeService([window], "x", 1, "a"); + assert.deepEqual(calls, [["x", 1, "a"]]); +}); + +test("runtime-option and debug broadcasts use expected channels", () => { + const broadcasts: unknown[][] = []; + broadcastRuntimeOptionsChangedRuntimeService( + () => [], + (channel, ...args) => { + broadcasts.push([channel, ...args]); + }, + ); + let state = false; + const changed = setOverlayDebugVisualizationEnabledRuntimeService( + state, + true, + (enabled) => { + state = enabled; + }, + (channel, ...args) => { + broadcasts.push([channel, ...args]); + }, + ); + assert.equal(changed, true); + assert.equal(state, true); + assert.deepEqual(broadcasts, [ + ["runtime-options:changed", []], + ["overlay-debug-visualization:set", true], + ]); +}); diff --git a/src/core/services/overlay-broadcast-runtime-service.ts b/src/core/services/overlay-broadcast-runtime-service.ts new file mode 100644 index 0000000..6c44506 --- /dev/null +++ b/src/core/services/overlay-broadcast-runtime-service.ts @@ -0,0 +1,45 @@ +import { BrowserWindow } from "electron"; +import { RuntimeOptionState } from "../../types"; + +export function getOverlayWindowsRuntimeService(options: { + mainWindow: BrowserWindow | null; + invisibleWindow: BrowserWindow | null; +}): BrowserWindow[] { + const windows: BrowserWindow[] = []; + if (options.mainWindow && !options.mainWindow.isDestroyed()) { + windows.push(options.mainWindow); + } + if (options.invisibleWindow && !options.invisibleWindow.isDestroyed()) { + windows.push(options.invisibleWindow); + } + return windows; +} + +export function broadcastToOverlayWindowsRuntimeService( + windows: BrowserWindow[], + channel: string, + ...args: unknown[] +): void { + for (const window of windows) { + window.webContents.send(channel, ...args); + } +} + +export function broadcastRuntimeOptionsChangedRuntimeService( + getRuntimeOptionsState: () => RuntimeOptionState[], + broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void, +): void { + broadcastToOverlayWindows("runtime-options:changed", getRuntimeOptionsState()); +} + +export function setOverlayDebugVisualizationEnabledRuntimeService( + currentEnabled: boolean, + nextEnabled: boolean, + setState: (enabled: boolean) => void, + broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void, +): boolean { + if (currentEnabled === nextEnabled) return false; + setState(nextEnabled); + broadcastToOverlayWindows("overlay-debug-visualization:set", nextEnabled); + return true; +} diff --git a/src/main.ts b/src/main.ts index 371d7dd..bc5ecef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -195,6 +195,12 @@ import { createFieldGroupingCallbackRuntimeService, sendToVisibleOverlayRuntimeService, } from "./core/services/overlay-bridge-runtime-service"; +import { + broadcastRuntimeOptionsChangedRuntimeService, + broadcastToOverlayWindowsRuntimeService, + getOverlayWindowsRuntimeService, + setOverlayDebugVisualizationEnabledRuntimeService, +} from "./core/services/overlay-broadcast-runtime-service"; import { runSubsyncManualFromIpcRuntimeService, triggerSubsyncFromConfigRuntimeService, @@ -305,25 +311,30 @@ const SUBTITLE_POSITIONS_DIR = path.join(CONFIG_DIR, "subtitle-positions"); function getRuntimeOptionsState(): RuntimeOptionState[] { if (!runtimeOptionsManager) return []; return runtimeOptionsManager.listOptions(); } function getOverlayWindows(): BrowserWindow[] { - const windows: BrowserWindow[] = []; - if (mainWindow && !mainWindow.isDestroyed()) { - windows.push(mainWindow); - } - if (invisibleWindow && !invisibleWindow.isDestroyed()) { - windows.push(invisibleWindow); - } - return windows; + return getOverlayWindowsRuntimeService({ mainWindow, invisibleWindow }); } function broadcastToOverlayWindows(channel: string, ...args: unknown[]): void { - for (const window of getOverlayWindows()) { - window.webContents.send(channel, ...args); - } + broadcastToOverlayWindowsRuntimeService(getOverlayWindows(), channel, ...args); } -function broadcastRuntimeOptionsChanged(): void { broadcastToOverlayWindows("runtime-options:changed", getRuntimeOptionsState()); } +function broadcastRuntimeOptionsChanged(): void { + broadcastRuntimeOptionsChangedRuntimeService( + () => getRuntimeOptionsState(), + (channel, ...args) => broadcastToOverlayWindows(channel, ...args), + ); +} -function setOverlayDebugVisualizationEnabled(enabled: boolean): void { if (overlayDebugVisualizationEnabled === enabled) return; overlayDebugVisualizationEnabled = enabled; broadcastToOverlayWindows("overlay-debug-visualization:set", overlayDebugVisualizationEnabled); } +function setOverlayDebugVisualizationEnabled(enabled: boolean): void { + setOverlayDebugVisualizationEnabledRuntimeService( + overlayDebugVisualizationEnabled, + enabled, + (next) => { + overlayDebugVisualizationEnabled = next; + }, + (channel, ...args) => broadcastToOverlayWindows(channel, ...args), + ); +} function openRuntimeOptionsPalette(): void { sendToVisibleOverlay("runtime-options:open", undefined, { restoreOnModalClose: "runtime-options" }); }