refactor: extract overlay broadcast runtime helpers

This commit is contained in:
2026-02-09 23:40:44 -08:00
parent bf579225f6
commit 8804f6b03f
4 changed files with 128 additions and 14 deletions

View File

@@ -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",

View File

@@ -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],
]);
});

View File

@@ -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;
}

View File

@@ -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" }); }