mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
feat: bind overlay state to secondary subtitle mpv visibility
This commit is contained in:
@@ -12,6 +12,7 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
|
||||
destroyTray: () => calls.push('destroy-tray'),
|
||||
stopConfigHotReload: () => calls.push('stop-config'),
|
||||
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
||||
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
||||
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||
@@ -33,7 +34,7 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
|
||||
});
|
||||
|
||||
cleanup();
|
||||
assert.equal(calls.length, 21);
|
||||
assert.equal(calls.length, 22);
|
||||
assert.equal(calls[0], 'destroy-tray');
|
||||
assert.equal(calls[calls.length - 1], 'stop-discord-presence');
|
||||
assert.ok(calls.indexOf('flush-mpv-log') < calls.indexOf('destroy-socket'));
|
||||
@@ -58,11 +59,10 @@ test('restore windows on activate recreates windows then syncs visibility', () =
|
||||
const calls: string[] = [];
|
||||
const restore = createRestoreWindowsOnActivateHandler({
|
||||
createMainWindow: () => calls.push('main'),
|
||||
createInvisibleWindow: () => calls.push('invisible'),
|
||||
updateVisibleOverlayVisibility: () => calls.push('visible-sync'),
|
||||
updateInvisibleOverlayVisibility: () => calls.push('invisible-sync'),
|
||||
syncOverlayMpvSubtitleSuppression: () => calls.push('mpv-sync'),
|
||||
});
|
||||
|
||||
restore();
|
||||
assert.deepEqual(calls, ['main', 'invisible', 'visible-sync', 'invisible-sync']);
|
||||
assert.deepEqual(calls, ['main', 'visible-sync', 'mpv-sync']);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
||||
destroyTray: () => void;
|
||||
stopConfigHotReload: () => void;
|
||||
restorePreviousSecondarySubVisibility: () => void;
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
||||
unregisterAllGlobalShortcuts: () => void;
|
||||
stopSubtitleWebsocket: () => void;
|
||||
stopTexthookerService: () => void;
|
||||
@@ -25,6 +26,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
||||
deps.destroyTray();
|
||||
deps.stopConfigHotReload();
|
||||
deps.restorePreviousSecondarySubVisibility();
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
||||
deps.unregisterAllGlobalShortcuts();
|
||||
deps.stopSubtitleWebsocket();
|
||||
deps.stopTexthookerService();
|
||||
@@ -55,14 +57,12 @@ export function createShouldRestoreWindowsOnActivateHandler(deps: {
|
||||
|
||||
export function createRestoreWindowsOnActivateHandler(deps: {
|
||||
createMainWindow: () => void;
|
||||
createInvisibleWindow: () => void;
|
||||
updateVisibleOverlayVisibility: () => void;
|
||||
updateInvisibleOverlayVisibility: () => void;
|
||||
syncOverlayMpvSubtitleSuppression: () => void;
|
||||
}) {
|
||||
return (): void => {
|
||||
deps.createMainWindow();
|
||||
deps.createInvisibleWindow();
|
||||
deps.updateVisibleOverlayVisibility();
|
||||
deps.updateInvisibleOverlayVisibility();
|
||||
deps.syncOverlayMpvSubtitleSuppression();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ test('cleanup deps builder returns handlers that guard optional runtime objects'
|
||||
destroyTray: () => calls.push('destroy-tray'),
|
||||
stopConfigHotReload: () => calls.push('stop-config'),
|
||||
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
||||
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
||||
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||
@@ -72,6 +73,7 @@ test('cleanup deps builder skips destroyed yomitan window', () => {
|
||||
destroyTray: () => {},
|
||||
stopConfigHotReload: () => {},
|
||||
restorePreviousSecondarySubVisibility: () => {},
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
||||
unregisterAllGlobalShortcuts: () => {},
|
||||
stopSubtitleWebsocket: () => {},
|
||||
stopTexthookerService: () => {},
|
||||
|
||||
@@ -21,6 +21,7 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
||||
destroyTray: () => void;
|
||||
stopConfigHotReload: () => void;
|
||||
restorePreviousSecondarySubVisibility: () => void;
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
||||
unregisterAllGlobalShortcuts: () => void;
|
||||
stopSubtitleWebsocket: () => void;
|
||||
stopTexthookerService: () => void;
|
||||
@@ -51,6 +52,8 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
||||
destroyTray: () => deps.destroyTray(),
|
||||
stopConfigHotReload: () => deps.stopConfigHotReload(),
|
||||
restorePreviousSecondarySubVisibility: () => deps.restorePreviousSecondarySubVisibility(),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
||||
unregisterAllGlobalShortcuts: () => deps.unregisterAllGlobalShortcuts(),
|
||||
stopSubtitleWebsocket: () => deps.stopSubtitleWebsocket(),
|
||||
stopTexthookerService: () => deps.stopTexthookerService(),
|
||||
|
||||
@@ -16,6 +16,7 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler
|
||||
destroyTray: () => {},
|
||||
stopConfigHotReload: () => {},
|
||||
restorePreviousSecondarySubVisibility: () => {},
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
||||
unregisterAllGlobalShortcuts: () => {},
|
||||
stopSubtitleWebsocket: () => {},
|
||||
stopTexthookerService: () => {},
|
||||
@@ -43,9 +44,8 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler
|
||||
},
|
||||
restoreWindowsOnActivateMainDeps: {
|
||||
createMainWindow: () => {},
|
||||
createInvisibleWindow: () => {},
|
||||
updateVisibleOverlayVisibility: () => {},
|
||||
updateInvisibleOverlayVisibility: () => {},
|
||||
syncOverlayMpvSubtitleSuppression: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export function createHandleMpvSecondarySubtitleChangeHandler(deps: {
|
||||
export function createHandleMpvMediaPathChangeHandler(deps: {
|
||||
updateCurrentMediaPath: (path: string) => void;
|
||||
reportJellyfinRemoteStopped: () => void;
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
||||
getCurrentAnilistMediaKey: () => string | null;
|
||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||
@@ -44,6 +45,7 @@ export function createHandleMpvMediaPathChangeHandler(deps: {
|
||||
deps.updateCurrentMediaPath(path);
|
||||
if (!path) {
|
||||
deps.reportJellyfinRemoteStopped();
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
||||
}
|
||||
const mediaKey = deps.getCurrentAnilistMediaKey();
|
||||
deps.resetAnilistMediaTracking(mediaKey);
|
||||
|
||||
@@ -8,6 +8,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
|
||||
const bind = createBindMpvMainEventHandlersHandler({
|
||||
reportJellyfinRemoteStopped: () => calls.push('remote-stopped'),
|
||||
syncOverlayMpvSubtitleSuppression: () => calls.push('sync-overlay-mpv-sub'),
|
||||
hasInitialJellyfinPlayArg: () => false,
|
||||
isOverlayRuntimeInitialized: () => false,
|
||||
isQuitOnDisconnectArmed: () => false,
|
||||
@@ -35,6 +36,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
broadcastSecondarySubtitle: (text) => calls.push(`broadcast-secondary:${text}`),
|
||||
|
||||
updateCurrentMediaPath: (path) => calls.push(`media-path:${path}`),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
||||
getCurrentAnilistMediaKey: () => 'media-key',
|
||||
resetAnilistMediaTracking: (key) => calls.push(`reset-media:${String(key)}`),
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
@@ -62,6 +64,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
});
|
||||
|
||||
handlers.get('subtitle-change')?.({ text: 'line' });
|
||||
handlers.get('media-path-change')?.({ path: '' });
|
||||
handlers.get('media-title-change')?.({ title: 'Episode 1' });
|
||||
handlers.get('time-pos-change')?.({ time: 2.5 });
|
||||
handlers.get('pause-change')?.({ paused: true });
|
||||
@@ -70,6 +73,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
assert.ok(calls.includes('broadcast-sub:line'));
|
||||
assert.ok(calls.includes('subtitle-change:line'));
|
||||
assert.ok(calls.includes('media-title:Episode 1'));
|
||||
assert.ok(calls.includes('restore-mpv-sub'));
|
||||
assert.ok(calls.includes('reset-guess-state'));
|
||||
assert.ok(calls.includes('notify-title:Episode 1'));
|
||||
assert.ok(calls.includes('progress:normal'));
|
||||
|
||||
@@ -19,6 +19,7 @@ type MpvEventClient = Parameters<ReturnType<typeof createBindMpvClientEventHandl
|
||||
|
||||
export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
reportJellyfinRemoteStopped: () => void;
|
||||
syncOverlayMpvSubtitleSuppression: () => void;
|
||||
hasInitialJellyfinPlayArg: () => boolean;
|
||||
isOverlayRuntimeInitialized: () => boolean;
|
||||
isQuitOnDisconnectArmed: () => boolean;
|
||||
@@ -42,6 +43,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
broadcastSecondarySubtitle: (text: string) => void;
|
||||
|
||||
updateCurrentMediaPath: (path: string) => void;
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
||||
getCurrentAnilistMediaKey: () => string | null;
|
||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||
@@ -63,6 +65,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
const handleMpvConnectionChange = createHandleMpvConnectionChangeHandler({
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
|
||||
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||
hasInitialJellyfinPlayArg: () => deps.hasInitialJellyfinPlayArg(),
|
||||
isOverlayRuntimeInitialized: () => deps.isOverlayRuntimeInitialized(),
|
||||
isQuitOnDisconnectArmed: () => deps.isQuitOnDisconnectArmed(),
|
||||
@@ -94,6 +97,8 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
const handleMpvMediaPathChange = createHandleMpvMediaPathChangeHandler({
|
||||
updateCurrentMediaPath: (path) => deps.updateCurrentMediaPath(path),
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
||||
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
||||
resetAnilistMediaTracking: (mediaKey) => deps.resetAnilistMediaTracking(mediaKey),
|
||||
maybeProbeAnilistDuration: (mediaKey) => deps.maybeProbeAnilistDuration(mediaKey),
|
||||
|
||||
@@ -32,6 +32,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
},
|
||||
quitApp: () => calls.push('quit'),
|
||||
reportJellyfinRemoteStopped: () => calls.push('remote-stopped'),
|
||||
syncOverlayMpvSubtitleSuppression: () => calls.push('sync-overlay-mpv-sub'),
|
||||
maybeRunAnilistPostWatchUpdate: async () => {
|
||||
calls.push('anilist-post-watch');
|
||||
},
|
||||
@@ -40,6 +41,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
calls.push(`broadcast:${channel}:${String(payload)}`),
|
||||
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
||||
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
||||
getCurrentAnilistMediaKey: () => 'media-key',
|
||||
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${mediaKey}`),
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
@@ -59,6 +61,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
deps.scheduleQuitCheck(() => calls.push('scheduled-callback'));
|
||||
deps.quitApp();
|
||||
deps.reportJellyfinRemoteStopped();
|
||||
deps.syncOverlayMpvSubtitleSuppression();
|
||||
deps.recordImmersionSubtitleLine('x', 0, 1);
|
||||
assert.equal(deps.hasSubtitleTimingTracker(), true);
|
||||
deps.recordSubtitleTiming('y', 0, 1);
|
||||
@@ -72,6 +75,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
deps.broadcastSubtitleAss('ass');
|
||||
deps.broadcastSecondarySubtitle('sec');
|
||||
deps.updateCurrentMediaPath('/tmp/video');
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
||||
assert.equal(deps.getCurrentAnilistMediaKey(), 'media-key');
|
||||
deps.resetAnilistMediaTracking('media-key');
|
||||
deps.maybeProbeAnilistDuration('media-key');
|
||||
@@ -91,8 +95,10 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
assert.equal(appState.playbackPaused, true);
|
||||
assert.equal(appState.previousSecondarySubVisibility, true);
|
||||
assert.ok(calls.includes('remote-stopped'));
|
||||
assert.ok(calls.includes('sync-overlay-mpv-sub'));
|
||||
assert.ok(calls.includes('anilist-post-watch'));
|
||||
assert.ok(calls.includes('sync-immersion'));
|
||||
assert.ok(calls.includes('metrics'));
|
||||
assert.ok(calls.includes('presence-refresh'));
|
||||
assert.ok(calls.includes('restore-mpv-sub'));
|
||||
});
|
||||
|
||||
@@ -21,11 +21,13 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
scheduleQuitCheck: (callback: () => void) => void;
|
||||
quitApp: () => void;
|
||||
reportJellyfinRemoteStopped: () => void;
|
||||
syncOverlayMpvSubtitleSuppression: () => void;
|
||||
maybeRunAnilistPostWatchUpdate: () => Promise<void>;
|
||||
logSubtitleTimingError: (message: string, error: unknown) => void;
|
||||
broadcastToOverlayWindows: (channel: string, payload: unknown) => void;
|
||||
onSubtitleChange: (text: string) => void;
|
||||
updateCurrentMediaPath: (path: string) => void;
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
||||
getCurrentAnilistMediaKey: () => string | null;
|
||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||
@@ -39,6 +41,7 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
}) {
|
||||
return () => ({
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||
hasInitialJellyfinPlayArg: () => Boolean(deps.appState.initialArgs?.jellyfinPlay),
|
||||
isOverlayRuntimeInitialized: () => deps.appState.overlayRuntimeInitialized,
|
||||
isQuitOnDisconnectArmed: () => deps.getQuitOnDisconnectArmed(),
|
||||
@@ -68,6 +71,8 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
broadcastSecondarySubtitle: (text: string) =>
|
||||
deps.broadcastToOverlayWindows('secondary-subtitle:set', text),
|
||||
updateCurrentMediaPath: (path: string) => deps.updateCurrentMediaPath(path),
|
||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
||||
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
||||
resetAnilistMediaTracking: (mediaKey: string | null) =>
|
||||
deps.resetAnilistMediaTracking(mediaKey),
|
||||
|
||||
171
src/main/runtime/overlay-mpv-sub-visibility.test.ts
Normal file
171
src/main/runtime/overlay-mpv-sub-visibility.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
createEnsureOverlayMpvSubtitlesHiddenHandler,
|
||||
createRestoreOverlayMpvSubtitlesHandler,
|
||||
} from './overlay-mpv-sub-visibility';
|
||||
|
||||
type VisibilityState = {
|
||||
savedSubVisibility: boolean | null;
|
||||
savedSecondarySubVisibility: boolean | null;
|
||||
revision: number;
|
||||
};
|
||||
|
||||
test('ensure overlay mpv subtitle suppression captures previous visibility then hides subtitles', async () => {
|
||||
const state: VisibilityState = {
|
||||
savedSubVisibility: null,
|
||||
savedSecondarySubVisibility: null,
|
||||
revision: 0,
|
||||
};
|
||||
const calls: boolean[] = [];
|
||||
|
||||
const ensureHidden = createEnsureOverlayMpvSubtitlesHiddenHandler({
|
||||
getMpvClient: () => ({
|
||||
connected: true,
|
||||
requestProperty: async (_name: string) => 'no',
|
||||
}),
|
||||
getSavedSubVisibility: () => state.savedSubVisibility,
|
||||
setSavedSubVisibility: (visible) => {
|
||||
state.savedSubVisibility = visible;
|
||||
},
|
||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
||||
setSavedSecondarySubVisibility: (visible) => {
|
||||
state.savedSecondarySubVisibility = visible;
|
||||
},
|
||||
getRevision: () => state.revision,
|
||||
setRevision: (revision) => {
|
||||
state.revision = revision;
|
||||
},
|
||||
setMpvSubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
setMpvSecondarySubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
logWarn: () => {},
|
||||
});
|
||||
|
||||
await ensureHidden();
|
||||
|
||||
assert.equal(state.savedSubVisibility, false);
|
||||
assert.equal(state.savedSecondarySubVisibility, false);
|
||||
assert.equal(state.revision, 1);
|
||||
assert.deepEqual(calls, [false, false]);
|
||||
});
|
||||
|
||||
test('restore overlay mpv subtitle suppression restores saved visibility', () => {
|
||||
const state: VisibilityState = {
|
||||
savedSubVisibility: false,
|
||||
savedSecondarySubVisibility: true,
|
||||
revision: 4,
|
||||
};
|
||||
const calls: boolean[] = [];
|
||||
|
||||
const restore = createRestoreOverlayMpvSubtitlesHandler({
|
||||
getSavedSubVisibility: () => state.savedSubVisibility,
|
||||
setSavedSubVisibility: (visible) => {
|
||||
state.savedSubVisibility = visible;
|
||||
},
|
||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
||||
setSavedSecondarySubVisibility: (visible) => {
|
||||
state.savedSecondarySubVisibility = visible;
|
||||
},
|
||||
getRevision: () => state.revision,
|
||||
setRevision: (revision) => {
|
||||
state.revision = revision;
|
||||
},
|
||||
isMpvConnected: () => true,
|
||||
shouldKeepSuppressedFromVisibleOverlayBinding: () => false,
|
||||
setMpvSubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
setMpvSecondarySubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
});
|
||||
|
||||
restore();
|
||||
|
||||
assert.equal(state.savedSubVisibility, null);
|
||||
assert.equal(state.savedSecondarySubVisibility, null);
|
||||
assert.equal(state.revision, 5);
|
||||
assert.deepEqual(calls, [false, true]);
|
||||
});
|
||||
|
||||
test('restore keeps mpv subtitles hidden when visible-overlay binding still requires suppression', () => {
|
||||
const state: VisibilityState = {
|
||||
savedSubVisibility: true,
|
||||
savedSecondarySubVisibility: true,
|
||||
revision: 9,
|
||||
};
|
||||
const calls: boolean[] = [];
|
||||
|
||||
const restore = createRestoreOverlayMpvSubtitlesHandler({
|
||||
getSavedSubVisibility: () => state.savedSubVisibility,
|
||||
setSavedSubVisibility: (visible) => {
|
||||
state.savedSubVisibility = visible;
|
||||
},
|
||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
||||
setSavedSecondarySubVisibility: (visible) => {
|
||||
state.savedSecondarySubVisibility = visible;
|
||||
},
|
||||
getRevision: () => state.revision,
|
||||
setRevision: (revision) => {
|
||||
state.revision = revision;
|
||||
},
|
||||
isMpvConnected: () => true,
|
||||
shouldKeepSuppressedFromVisibleOverlayBinding: () => true,
|
||||
setMpvSubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
setMpvSecondarySubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
});
|
||||
|
||||
restore();
|
||||
|
||||
assert.equal(state.savedSubVisibility, true);
|
||||
assert.equal(state.savedSecondarySubVisibility, true);
|
||||
assert.equal(state.revision, 10);
|
||||
assert.deepEqual(calls, [false, false]);
|
||||
});
|
||||
|
||||
test('restore defers mpv subtitle restore while mpv is disconnected', () => {
|
||||
const state: VisibilityState = {
|
||||
savedSubVisibility: true,
|
||||
savedSecondarySubVisibility: false,
|
||||
revision: 2,
|
||||
};
|
||||
const calls: boolean[] = [];
|
||||
|
||||
const restore = createRestoreOverlayMpvSubtitlesHandler({
|
||||
getSavedSubVisibility: () => state.savedSubVisibility,
|
||||
setSavedSubVisibility: (visible) => {
|
||||
state.savedSubVisibility = visible;
|
||||
},
|
||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
||||
setSavedSecondarySubVisibility: (visible) => {
|
||||
state.savedSecondarySubVisibility = visible;
|
||||
},
|
||||
getRevision: () => state.revision,
|
||||
setRevision: (revision) => {
|
||||
state.revision = revision;
|
||||
},
|
||||
isMpvConnected: () => false,
|
||||
shouldKeepSuppressedFromVisibleOverlayBinding: () => false,
|
||||
setMpvSubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
setMpvSecondarySubVisibility: (visible) => {
|
||||
calls.push(visible);
|
||||
},
|
||||
});
|
||||
|
||||
restore();
|
||||
|
||||
assert.equal(state.savedSubVisibility, true);
|
||||
assert.equal(state.revision, 3);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
147
src/main/runtime/overlay-mpv-sub-visibility.ts
Normal file
147
src/main/runtime/overlay-mpv-sub-visibility.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
type MpvVisibilityClient = {
|
||||
connected: boolean;
|
||||
requestProperty: (name: string) => Promise<unknown>;
|
||||
};
|
||||
|
||||
type RestoreOptions = {
|
||||
respectVisibleOverlayBinding?: boolean;
|
||||
};
|
||||
|
||||
function parseSubVisibility(value: unknown): boolean {
|
||||
if (typeof value === 'string') {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === 'no' || normalized === 'false' || normalized === '0') {
|
||||
return false;
|
||||
}
|
||||
if (normalized === 'yes' || normalized === 'true' || normalized === '1') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value !== 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function createEnsureOverlayMpvSubtitlesHiddenHandler(deps: {
|
||||
getMpvClient: () => MpvVisibilityClient | null;
|
||||
getSavedSubVisibility: () => boolean | null;
|
||||
setSavedSubVisibility: (visible: boolean | null) => void;
|
||||
getSavedSecondarySubVisibility: () => boolean | null;
|
||||
setSavedSecondarySubVisibility: (visible: boolean | null) => void;
|
||||
getRevision: () => number;
|
||||
setRevision: (revision: number) => void;
|
||||
setMpvSubVisibility: (visible: boolean) => void;
|
||||
setMpvSecondarySubVisibility: (visible: boolean) => void;
|
||||
logWarn: (message: string, error: unknown) => void;
|
||||
}) {
|
||||
return async (): Promise<void> => {
|
||||
const revision = deps.getRevision() + 1;
|
||||
deps.setRevision(revision);
|
||||
|
||||
const mpvClient = deps.getMpvClient();
|
||||
if (!mpvClient || !mpvClient.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (deps.getSavedSubVisibility() === null) {
|
||||
try {
|
||||
const currentSubVisibility = await mpvClient.requestProperty('sub-visibility');
|
||||
if (revision !== deps.getRevision()) {
|
||||
return;
|
||||
}
|
||||
deps.setSavedSubVisibility(parseSubVisibility(currentSubVisibility));
|
||||
} catch (error) {
|
||||
if (revision !== deps.getRevision()) {
|
||||
return;
|
||||
}
|
||||
deps.logWarn(
|
||||
'[overlay] Failed to capture mpv sub-visibility; falling back to visible restore',
|
||||
error,
|
||||
);
|
||||
deps.setSavedSubVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (deps.getSavedSecondarySubVisibility() === null) {
|
||||
try {
|
||||
const currentSecondarySubVisibility = await mpvClient.requestProperty('secondary-sub-visibility');
|
||||
if (revision !== deps.getRevision()) {
|
||||
return;
|
||||
}
|
||||
deps.setSavedSecondarySubVisibility(parseSubVisibility(currentSecondarySubVisibility));
|
||||
} catch (error) {
|
||||
if (revision !== deps.getRevision()) {
|
||||
return;
|
||||
}
|
||||
deps.logWarn(
|
||||
'[overlay] Failed to capture secondary mpv sub-visibility; falling back to visible restore',
|
||||
error,
|
||||
);
|
||||
deps.setSavedSecondarySubVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (revision !== deps.getRevision()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deps.setMpvSubVisibility(false);
|
||||
deps.setMpvSecondarySubVisibility(false);
|
||||
};
|
||||
}
|
||||
|
||||
export function createRestoreOverlayMpvSubtitlesHandler(deps: {
|
||||
getSavedSubVisibility: () => boolean | null;
|
||||
setSavedSubVisibility: (visible: boolean | null) => void;
|
||||
getSavedSecondarySubVisibility: () => boolean | null;
|
||||
setSavedSecondarySubVisibility: (visible: boolean | null) => void;
|
||||
getRevision: () => number;
|
||||
setRevision: (revision: number) => void;
|
||||
isMpvConnected: () => boolean;
|
||||
shouldKeepSuppressedFromVisibleOverlayBinding: () => boolean;
|
||||
setMpvSubVisibility: (visible: boolean) => void;
|
||||
setMpvSecondarySubVisibility: (visible: boolean) => void;
|
||||
}) {
|
||||
return (options?: RestoreOptions): void => {
|
||||
deps.setRevision(deps.getRevision() + 1);
|
||||
|
||||
const savedVisibility = deps.getSavedSubVisibility();
|
||||
const respectVisibleOverlayBinding = options?.respectVisibleOverlayBinding ?? true;
|
||||
if (
|
||||
respectVisibleOverlayBinding &&
|
||||
deps.shouldKeepSuppressedFromVisibleOverlayBinding()
|
||||
) {
|
||||
deps.setMpvSubVisibility(false);
|
||||
deps.setMpvSecondarySubVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasSecondarySavedVisibility = deps.getSavedSecondarySubVisibility() !== null;
|
||||
|
||||
if (savedVisibility === null && !hasSecondarySavedVisibility) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deps.isMpvConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedVisibility !== null) {
|
||||
deps.setMpvSubVisibility(savedVisibility);
|
||||
}
|
||||
const savedSecondaryVisibility = deps.getSavedSecondarySubVisibility();
|
||||
if (savedSecondaryVisibility !== null) {
|
||||
deps.setMpvSecondarySubVisibility(savedSecondaryVisibility);
|
||||
}
|
||||
|
||||
deps.setSavedSubVisibility(null);
|
||||
deps.setSavedSecondarySubVisibility(null);
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBuildCreateInvisibleWindowMainDepsHandler,
|
||||
createBuildCreateMainWindowMainDepsHandler,
|
||||
createBuildCreateModalWindowMainDepsHandler,
|
||||
createBuildCreateOverlayWindowMainDepsHandler,
|
||||
createBuildCreateSecondaryWindowMainDepsHandler,
|
||||
} from './overlay-window-factory-main-deps';
|
||||
|
||||
test('overlay window factory main deps builders return mapped handlers', () => {
|
||||
@@ -13,7 +11,6 @@ test('overlay window factory main deps builders return mapped handlers', () => {
|
||||
const buildOverlayDeps = createBuildCreateOverlayWindowMainDepsHandler({
|
||||
createOverlayWindowCore: (kind) => ({ kind }),
|
||||
isDev: true,
|
||||
getOverlayDebugVisualizationEnabled: () => false,
|
||||
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
||||
onRuntimeOptionsChanged: () => calls.push('runtime-options-changed'),
|
||||
setOverlayDebugVisualizationEnabled: (enabled) => calls.push(`debug:${enabled}`),
|
||||
@@ -24,7 +21,6 @@ test('overlay window factory main deps builders return mapped handlers', () => {
|
||||
|
||||
const overlayDeps = buildOverlayDeps();
|
||||
assert.equal(overlayDeps.isDev, true);
|
||||
assert.equal(overlayDeps.getOverlayDebugVisualizationEnabled(), false);
|
||||
assert.equal(overlayDeps.isOverlayVisible('visible'), true);
|
||||
|
||||
const buildMainDeps = createBuildCreateMainWindowMainDepsHandler({
|
||||
@@ -34,20 +30,6 @@ test('overlay window factory main deps builders return mapped handlers', () => {
|
||||
const mainDeps = buildMainDeps();
|
||||
mainDeps.setMainWindow(null);
|
||||
|
||||
const buildInvisibleDeps = createBuildCreateInvisibleWindowMainDepsHandler({
|
||||
createOverlayWindow: () => ({ id: 'invisible' }),
|
||||
setInvisibleWindow: () => calls.push('set-invisible'),
|
||||
});
|
||||
const invisibleDeps = buildInvisibleDeps();
|
||||
invisibleDeps.setInvisibleWindow(null);
|
||||
|
||||
const buildSecondaryDeps = createBuildCreateSecondaryWindowMainDepsHandler({
|
||||
createOverlayWindow: () => ({ id: 'secondary' }),
|
||||
setSecondaryWindow: () => calls.push('set-secondary'),
|
||||
});
|
||||
const secondaryDeps = buildSecondaryDeps();
|
||||
secondaryDeps.setSecondaryWindow(null);
|
||||
|
||||
const buildModalDeps = createBuildCreateModalWindowMainDepsHandler({
|
||||
createOverlayWindow: () => ({ id: 'modal' }),
|
||||
setModalWindow: () => calls.push('set-modal'),
|
||||
@@ -55,5 +37,5 @@ test('overlay window factory main deps builders return mapped handlers', () => {
|
||||
const modalDeps = buildModalDeps();
|
||||
modalDeps.setModalWindow(null);
|
||||
|
||||
assert.deepEqual(calls, ['set-main', 'set-invisible', 'set-secondary', 'set-modal']);
|
||||
assert.deepEqual(calls, ['set-main', 'set-modal']);
|
||||
});
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
export function createBuildCreateOverlayWindowMainDepsHandler<TWindow>(deps: {
|
||||
createOverlayWindowCore: (
|
||||
kind: 'visible' | 'invisible' | 'secondary' | 'modal',
|
||||
kind: 'visible' | 'modal',
|
||||
options: {
|
||||
isDev: boolean;
|
||||
overlayDebugVisualizationEnabled: boolean;
|
||||
ensureOverlayWindowLevel: (window: TWindow) => void;
|
||||
onRuntimeOptionsChanged: () => void;
|
||||
setOverlayDebugVisualizationEnabled: (enabled: boolean) => void;
|
||||
isOverlayVisible: (windowKind: 'visible' | 'invisible' | 'secondary' | 'modal') => boolean;
|
||||
isOverlayVisible: (windowKind: 'visible' | 'modal') => boolean;
|
||||
tryHandleOverlayShortcutLocalFallback: (input: Electron.Input) => boolean;
|
||||
onWindowClosed: (windowKind: 'visible' | 'invisible' | 'secondary' | 'modal') => void;
|
||||
onWindowClosed: (windowKind: 'visible' | 'modal') => void;
|
||||
},
|
||||
) => TWindow;
|
||||
isDev: boolean;
|
||||
getOverlayDebugVisualizationEnabled: () => boolean;
|
||||
ensureOverlayWindowLevel: (window: TWindow) => void;
|
||||
onRuntimeOptionsChanged: () => void;
|
||||
setOverlayDebugVisualizationEnabled: (enabled: boolean) => void;
|
||||
isOverlayVisible: (windowKind: 'visible' | 'invisible' | 'secondary' | 'modal') => boolean;
|
||||
isOverlayVisible: (windowKind: 'visible' | 'modal') => boolean;
|
||||
tryHandleOverlayShortcutLocalFallback: (input: Electron.Input) => boolean;
|
||||
onWindowClosed: (windowKind: 'visible' | 'invisible' | 'secondary' | 'modal') => void;
|
||||
onWindowClosed: (windowKind: 'visible' | 'modal') => void;
|
||||
}) {
|
||||
return () => ({
|
||||
createOverlayWindowCore: deps.createOverlayWindowCore,
|
||||
isDev: deps.isDev,
|
||||
getOverlayDebugVisualizationEnabled: deps.getOverlayDebugVisualizationEnabled,
|
||||
ensureOverlayWindowLevel: deps.ensureOverlayWindowLevel,
|
||||
onRuntimeOptionsChanged: deps.onRuntimeOptionsChanged,
|
||||
setOverlayDebugVisualizationEnabled: deps.setOverlayDebugVisualizationEnabled,
|
||||
@@ -35,7 +32,7 @@ export function createBuildCreateOverlayWindowMainDepsHandler<TWindow>(deps: {
|
||||
}
|
||||
|
||||
export function createBuildCreateMainWindowMainDepsHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: 'visible' | 'invisible' | 'secondary' | 'modal') => TWindow;
|
||||
createOverlayWindow: (kind: 'visible' | 'modal') => TWindow;
|
||||
setMainWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
@@ -44,28 +41,8 @@ export function createBuildCreateMainWindowMainDepsHandler<TWindow>(deps: {
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildCreateInvisibleWindowMainDepsHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: 'visible' | 'invisible' | 'secondary' | 'modal') => TWindow;
|
||||
setInvisibleWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
createOverlayWindow: deps.createOverlayWindow,
|
||||
setInvisibleWindow: deps.setInvisibleWindow,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildCreateSecondaryWindowMainDepsHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: 'visible' | 'invisible' | 'secondary' | 'modal') => TWindow;
|
||||
setSecondaryWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
createOverlayWindow: deps.createOverlayWindow,
|
||||
setSecondaryWindow: deps.setSecondaryWindow,
|
||||
});
|
||||
}
|
||||
|
||||
export function createBuildCreateModalWindowMainDepsHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: 'visible' | 'invisible' | 'secondary' | 'modal') => TWindow;
|
||||
createOverlayWindow: (kind: 'visible' | 'modal') => TWindow;
|
||||
setModalWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return () => ({
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
createCreateInvisibleWindowHandler,
|
||||
createCreateMainWindowHandler,
|
||||
createCreateModalWindowHandler,
|
||||
createCreateOverlayWindowHandler,
|
||||
createCreateSecondaryWindowHandler,
|
||||
} from './overlay-window-factory';
|
||||
|
||||
test('create overlay window handler forwards options and kind', () => {
|
||||
@@ -15,16 +13,14 @@ test('create overlay window handler forwards options and kind', () => {
|
||||
createOverlayWindowCore: (kind, options) => {
|
||||
calls.push(`kind:${kind}`);
|
||||
assert.equal(options.isDev, true);
|
||||
assert.equal(options.overlayDebugVisualizationEnabled, false);
|
||||
assert.equal(options.isOverlayVisible('visible'), true);
|
||||
assert.equal(options.isOverlayVisible('invisible'), false);
|
||||
assert.equal(options.isOverlayVisible('modal'), false);
|
||||
options.onRuntimeOptionsChanged();
|
||||
options.setOverlayDebugVisualizationEnabled(true);
|
||||
options.onWindowClosed(kind);
|
||||
return window;
|
||||
},
|
||||
isDev: true,
|
||||
getOverlayDebugVisualizationEnabled: () => false,
|
||||
ensureOverlayWindowLevel: () => {},
|
||||
onRuntimeOptionsChanged: () => calls.push('runtime-options'),
|
||||
setOverlayDebugVisualizationEnabled: (enabled) => calls.push(`debug:${enabled}`),
|
||||
@@ -52,36 +48,6 @@ test('create main window handler stores visible window', () => {
|
||||
assert.deepEqual(calls, ['create:visible', 'set:visible']);
|
||||
});
|
||||
|
||||
test('create invisible window handler stores invisible window', () => {
|
||||
const calls: string[] = [];
|
||||
const invisibleWindow = { id: 'invisible' };
|
||||
const createInvisibleWindow = createCreateInvisibleWindowHandler({
|
||||
createOverlayWindow: (kind) => {
|
||||
calls.push(`create:${kind}`);
|
||||
return invisibleWindow;
|
||||
},
|
||||
setInvisibleWindow: (window) => calls.push(`set:${(window as { id: string }).id}`),
|
||||
});
|
||||
|
||||
assert.equal(createInvisibleWindow(), invisibleWindow);
|
||||
assert.deepEqual(calls, ['create:invisible', 'set:invisible']);
|
||||
});
|
||||
|
||||
test('create secondary window handler stores secondary window', () => {
|
||||
const calls: string[] = [];
|
||||
const secondaryWindow = { id: 'secondary' };
|
||||
const createSecondaryWindow = createCreateSecondaryWindowHandler({
|
||||
createOverlayWindow: (kind) => {
|
||||
calls.push(`create:${kind}`);
|
||||
return secondaryWindow;
|
||||
},
|
||||
setSecondaryWindow: (window) => calls.push(`set:${(window as { id: string }).id}`),
|
||||
});
|
||||
|
||||
assert.equal(createSecondaryWindow(), secondaryWindow);
|
||||
assert.deepEqual(calls, ['create:secondary', 'set:secondary']);
|
||||
});
|
||||
|
||||
test('create modal window handler stores modal window', () => {
|
||||
const calls: string[] = [];
|
||||
const modalWindow = { id: 'modal' };
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
type OverlayWindowKind = 'visible' | 'invisible' | 'secondary' | 'modal';
|
||||
type OverlayWindowKind = 'visible' | 'modal';
|
||||
|
||||
export function createCreateOverlayWindowHandler<TWindow>(deps: {
|
||||
createOverlayWindowCore: (
|
||||
kind: OverlayWindowKind,
|
||||
options: {
|
||||
isDev: boolean;
|
||||
overlayDebugVisualizationEnabled: boolean;
|
||||
ensureOverlayWindowLevel: (window: TWindow) => void;
|
||||
onRuntimeOptionsChanged: () => void;
|
||||
setOverlayDebugVisualizationEnabled: (enabled: boolean) => void;
|
||||
@@ -15,7 +14,6 @@ export function createCreateOverlayWindowHandler<TWindow>(deps: {
|
||||
},
|
||||
) => TWindow;
|
||||
isDev: boolean;
|
||||
getOverlayDebugVisualizationEnabled: () => boolean;
|
||||
ensureOverlayWindowLevel: (window: TWindow) => void;
|
||||
onRuntimeOptionsChanged: () => void;
|
||||
setOverlayDebugVisualizationEnabled: (enabled: boolean) => void;
|
||||
@@ -26,7 +24,6 @@ export function createCreateOverlayWindowHandler<TWindow>(deps: {
|
||||
return (kind: OverlayWindowKind): TWindow => {
|
||||
return deps.createOverlayWindowCore(kind, {
|
||||
isDev: deps.isDev,
|
||||
overlayDebugVisualizationEnabled: deps.getOverlayDebugVisualizationEnabled(),
|
||||
ensureOverlayWindowLevel: deps.ensureOverlayWindowLevel,
|
||||
onRuntimeOptionsChanged: deps.onRuntimeOptionsChanged,
|
||||
setOverlayDebugVisualizationEnabled: deps.setOverlayDebugVisualizationEnabled,
|
||||
@@ -48,28 +45,6 @@ export function createCreateMainWindowHandler<TWindow>(deps: {
|
||||
};
|
||||
}
|
||||
|
||||
export function createCreateInvisibleWindowHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: OverlayWindowKind) => TWindow;
|
||||
setInvisibleWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return (): TWindow => {
|
||||
const window = deps.createOverlayWindow('invisible');
|
||||
deps.setInvisibleWindow(window);
|
||||
return window;
|
||||
};
|
||||
}
|
||||
|
||||
export function createCreateSecondaryWindowHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: OverlayWindowKind) => TWindow;
|
||||
setSecondaryWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
return (): TWindow => {
|
||||
const window = deps.createOverlayWindow('secondary');
|
||||
deps.setSecondaryWindow(window);
|
||||
return window;
|
||||
};
|
||||
}
|
||||
|
||||
export function createCreateModalWindowHandler<TWindow>(deps: {
|
||||
createOverlayWindow: (kind: OverlayWindowKind) => TWindow;
|
||||
setModalWindow: (window: TWindow | null) => void;
|
||||
|
||||
@@ -2,10 +2,8 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createOverlayWindowRuntimeHandlers } from './overlay-window-runtime-handlers';
|
||||
|
||||
test('overlay window runtime handlers compose create/main/invisible handlers', () => {
|
||||
test('overlay window runtime handlers compose create/main/modal handlers', () => {
|
||||
let mainWindow: { kind: string } | null = null;
|
||||
let invisibleWindow: { kind: string } | null = null;
|
||||
let secondaryWindow: { kind: string } | null = null;
|
||||
let modalWindow: { kind: string } | null = null;
|
||||
let debugEnabled = false;
|
||||
const calls: string[] = [];
|
||||
@@ -14,7 +12,6 @@ test('overlay window runtime handlers compose create/main/invisible handlers', (
|
||||
createOverlayWindowDeps: {
|
||||
createOverlayWindowCore: (kind) => ({ kind }),
|
||||
isDev: true,
|
||||
getOverlayDebugVisualizationEnabled: () => debugEnabled,
|
||||
ensureOverlayWindowLevel: () => calls.push('ensure-level'),
|
||||
onRuntimeOptionsChanged: () => calls.push('runtime-options-changed'),
|
||||
setOverlayDebugVisualizationEnabled: (enabled) => {
|
||||
@@ -27,29 +24,17 @@ test('overlay window runtime handlers compose create/main/invisible handlers', (
|
||||
setMainWindow: (window) => {
|
||||
mainWindow = window;
|
||||
},
|
||||
setInvisibleWindow: (window) => {
|
||||
invisibleWindow = window;
|
||||
},
|
||||
setSecondaryWindow: (window) => {
|
||||
secondaryWindow = window;
|
||||
},
|
||||
setModalWindow: (window) => {
|
||||
modalWindow = window;
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(runtime.createOverlayWindow('visible'), { kind: 'visible' });
|
||||
assert.deepEqual(runtime.createOverlayWindow('invisible'), { kind: 'invisible' });
|
||||
assert.deepEqual(runtime.createOverlayWindow('secondary'), { kind: 'secondary' });
|
||||
assert.deepEqual(runtime.createOverlayWindow('modal'), { kind: 'modal' });
|
||||
|
||||
assert.deepEqual(runtime.createMainWindow(), { kind: 'visible' });
|
||||
assert.deepEqual(mainWindow, { kind: 'visible' });
|
||||
|
||||
assert.deepEqual(runtime.createInvisibleWindow(), { kind: 'invisible' });
|
||||
assert.deepEqual(invisibleWindow, { kind: 'invisible' });
|
||||
|
||||
assert.deepEqual(runtime.createSecondaryWindow(), { kind: 'secondary' });
|
||||
assert.deepEqual(secondaryWindow, { kind: 'secondary' });
|
||||
assert.deepEqual(runtime.createModalWindow(), { kind: 'modal' });
|
||||
assert.deepEqual(modalWindow, { kind: 'modal' });
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import {
|
||||
createCreateInvisibleWindowHandler,
|
||||
createCreateMainWindowHandler,
|
||||
createCreateModalWindowHandler,
|
||||
createCreateOverlayWindowHandler,
|
||||
createCreateSecondaryWindowHandler,
|
||||
} from './overlay-window-factory';
|
||||
import {
|
||||
createBuildCreateInvisibleWindowMainDepsHandler,
|
||||
createBuildCreateMainWindowMainDepsHandler,
|
||||
createBuildCreateModalWindowMainDepsHandler,
|
||||
createBuildCreateOverlayWindowMainDepsHandler,
|
||||
createBuildCreateSecondaryWindowMainDepsHandler,
|
||||
} from './overlay-window-factory-main-deps';
|
||||
|
||||
type CreateOverlayWindowMainDeps<TWindow> = Parameters<
|
||||
@@ -20,8 +16,6 @@ type CreateOverlayWindowMainDeps<TWindow> = Parameters<
|
||||
export function createOverlayWindowRuntimeHandlers<TWindow>(deps: {
|
||||
createOverlayWindowDeps: CreateOverlayWindowMainDeps<TWindow>;
|
||||
setMainWindow: (window: TWindow | null) => void;
|
||||
setInvisibleWindow: (window: TWindow | null) => void;
|
||||
setSecondaryWindow: (window: TWindow | null) => void;
|
||||
setModalWindow: (window: TWindow | null) => void;
|
||||
}) {
|
||||
const createOverlayWindow = createCreateOverlayWindowHandler<TWindow>(
|
||||
@@ -33,18 +27,6 @@ export function createOverlayWindowRuntimeHandlers<TWindow>(deps: {
|
||||
setMainWindow: (window) => deps.setMainWindow(window),
|
||||
})(),
|
||||
);
|
||||
const createInvisibleWindow = createCreateInvisibleWindowHandler<TWindow>(
|
||||
createBuildCreateInvisibleWindowMainDepsHandler<TWindow>({
|
||||
createOverlayWindow: (kind) => createOverlayWindow(kind),
|
||||
setInvisibleWindow: (window) => deps.setInvisibleWindow(window),
|
||||
})(),
|
||||
);
|
||||
const createSecondaryWindow = createCreateSecondaryWindowHandler<TWindow>(
|
||||
createBuildCreateSecondaryWindowMainDepsHandler<TWindow>({
|
||||
createOverlayWindow: (kind) => createOverlayWindow(kind),
|
||||
setSecondaryWindow: (window) => deps.setSecondaryWindow(window),
|
||||
})(),
|
||||
);
|
||||
const createModalWindow = createCreateModalWindowHandler<TWindow>(
|
||||
createBuildCreateModalWindowMainDepsHandler<TWindow>({
|
||||
createOverlayWindow: (kind) => createOverlayWindow(kind),
|
||||
@@ -55,8 +37,6 @@ export function createOverlayWindowRuntimeHandlers<TWindow>(deps: {
|
||||
return {
|
||||
createOverlayWindow,
|
||||
createMainWindow,
|
||||
createInvisibleWindow,
|
||||
createSecondaryWindow,
|
||||
createModalWindow,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,8 +156,6 @@ export interface AppState {
|
||||
currentSubText: string;
|
||||
currentSubAssText: string;
|
||||
currentSubtitleData: SubtitleData | null;
|
||||
hoveredSubtitleTokenIndex: number | null;
|
||||
hoveredSubtitleRevision: number;
|
||||
windowTracker: BaseWindowTracker | null;
|
||||
subtitlePosition: SubtitlePosition | null;
|
||||
currentMediaPath: string | null;
|
||||
@@ -173,6 +171,9 @@ export interface AppState {
|
||||
secondarySubMode: SecondarySubMode;
|
||||
lastSecondarySubToggleAtMs: number;
|
||||
previousSecondarySubVisibility: boolean | null;
|
||||
overlaySavedMpvSubVisibility: boolean | null;
|
||||
overlaySavedSecondaryMpvSubVisibility: boolean | null;
|
||||
overlayMpvSubVisibilityRevision: number;
|
||||
mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics;
|
||||
shortcutsRegistered: boolean;
|
||||
overlayRuntimeInitialized: boolean;
|
||||
@@ -230,8 +231,6 @@ export function createAppState(values: AppStateInitialValues): AppState {
|
||||
currentSubText: '',
|
||||
currentSubAssText: '',
|
||||
currentSubtitleData: null,
|
||||
hoveredSubtitleTokenIndex: null,
|
||||
hoveredSubtitleRevision: 0,
|
||||
windowTracker: null,
|
||||
subtitlePosition: null,
|
||||
currentMediaPath: null,
|
||||
@@ -247,6 +246,9 @@ export function createAppState(values: AppStateInitialValues): AppState {
|
||||
secondarySubMode: 'hover',
|
||||
lastSecondarySubToggleAtMs: 0,
|
||||
previousSecondarySubVisibility: null,
|
||||
overlaySavedMpvSubVisibility: null,
|
||||
overlaySavedSecondaryMpvSubVisibility: null,
|
||||
overlayMpvSubVisibilityRevision: 0,
|
||||
mpvSubtitleRenderMetrics: {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user