mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
fix: address claude review feedback on overlay refactor
This commit is contained in:
@@ -43,7 +43,7 @@ export function buildCoreConfigOptionRegistry(
|
|||||||
kind: 'boolean',
|
kind: 'boolean',
|
||||||
defaultValue: defaultConfig.bind_visible_overlay_to_mpv_sub_visibility,
|
defaultValue: defaultConfig.bind_visible_overlay_to_mpv_sub_visibility,
|
||||||
description:
|
description:
|
||||||
'Link visible overlay toggles to MPV subtitle visibility (primary and secondary).',
|
'Link visible overlay toggles to MPV primary subtitle visibility.',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,10 +212,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
|
|||||||
deps.toggleDevTools();
|
deps.toggleDevTools();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.handle(IPC_CHANNELS.request.getOverlayVisibility, () => {
|
|
||||||
return deps.getVisibleOverlayVisibility();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on(IPC_CHANNELS.command.toggleOverlay, () => {
|
ipc.on(IPC_CHANNELS.command.toggleOverlay, () => {
|
||||||
deps.toggleVisibleOverlay();
|
deps.toggleVisibleOverlay();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,23 +131,7 @@ test('dispatchMpvProtocolMessage enforces sub-visibility hidden when overlay sup
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert.deepEqual(state.commands.pop(), {
|
assert.deepEqual(state.commands.pop(), {
|
||||||
command: ['set_property', 'sub-visibility', 'no'],
|
command: ['set_property', 'sub-visibility', false],
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('dispatchMpvProtocolMessage enforces secondary sub-visibility hidden when overlay suppression is enabled', async () => {
|
|
||||||
const { deps, state } = createDeps({
|
|
||||||
shouldBindVisibleOverlayToMpvSubVisibility: () => true,
|
|
||||||
isVisibleOverlayVisible: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await dispatchMpvProtocolMessage(
|
|
||||||
{ event: 'property-change', name: 'secondary-sub-visibility', data: 'yes' },
|
|
||||||
deps,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.deepEqual(state.commands.pop(), {
|
|
||||||
command: ['set_property', 'secondary-sub-visibility', 'no'],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -223,15 +223,7 @@ export async function dispatchMpvProtocolMessage(
|
|||||||
deps.isVisibleOverlayVisible() &&
|
deps.isVisibleOverlayVisible() &&
|
||||||
asBoolean(msg.data, false)
|
asBoolean(msg.data, false)
|
||||||
) {
|
) {
|
||||||
deps.sendCommand({ command: ['set_property', 'sub-visibility', 'no'] });
|
deps.sendCommand({ command: ['set_property', 'sub-visibility', false] });
|
||||||
}
|
|
||||||
} else if (msg.name === 'secondary-sub-visibility') {
|
|
||||||
if (
|
|
||||||
deps.shouldBindVisibleOverlayToMpvSubVisibility?.() &&
|
|
||||||
deps.isVisibleOverlayVisible() &&
|
|
||||||
asBoolean(msg.data, false)
|
|
||||||
) {
|
|
||||||
deps.sendCommand({ command: ['set_property', 'secondary-sub-visibility', 'no'] });
|
|
||||||
}
|
}
|
||||||
} else if (msg.name === 'sub-use-margins') {
|
} else if (msg.name === 'sub-use-margins') {
|
||||||
deps.emitSubtitleMetricsChange({
|
deps.emitSubtitleMetricsChange({
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ export class MpvIpcClient implements MpvClient {
|
|||||||
|
|
||||||
setSubVisibility(visible: boolean): void {
|
setSubVisibility(visible: boolean): void {
|
||||||
this.send({
|
this.send({
|
||||||
command: ['set_property', 'sub-visibility', visible ? 'yes' : 'no'],
|
command: ['set_property', 'sub-visibility', visible],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
syncOverlayShortcuts: () => void;
|
syncOverlayShortcuts: () => void;
|
||||||
isMacOSPlatform?: boolean;
|
isMacOSPlatform?: boolean;
|
||||||
showOverlayLoadingOsd?: (message: string) => void;
|
showOverlayLoadingOsd?: (message: string) => void;
|
||||||
resolveFallbackBounds: () => WindowGeometry;
|
resolveFallbackBounds?: () => WindowGeometry;
|
||||||
}): void {
|
}): void {
|
||||||
if (!args.mainWindow || args.mainWindow.isDestroyed()) {
|
if (!args.mainWindow || args.mainWindow.isDestroyed()) {
|
||||||
return;
|
return;
|
||||||
@@ -78,7 +78,9 @@ export function updateVisibleOverlayVisibility(args: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackBounds = args.resolveFallbackBounds();
|
const fallbackBounds = args.resolveFallbackBounds?.();
|
||||||
|
if (!fallbackBounds) return;
|
||||||
|
|
||||||
args.updateVisibleOverlayBounds(fallbackBounds);
|
args.updateVisibleOverlayBounds(fallbackBounds);
|
||||||
args.syncPrimaryOverlayWindowLayer('visible');
|
args.syncPrimaryOverlayWindowLayer('visible');
|
||||||
args.mainWindow.setIgnoreMouseEvents(false);
|
args.mainWindow.setIgnoreMouseEvents(false);
|
||||||
|
|||||||
29
src/main.ts
29
src/main.ts
@@ -354,7 +354,6 @@ import {
|
|||||||
runStartupBootstrapRuntime,
|
runStartupBootstrapRuntime,
|
||||||
saveSubtitlePosition as saveSubtitlePositionCore,
|
saveSubtitlePosition as saveSubtitlePositionCore,
|
||||||
sendMpvCommandRuntime,
|
sendMpvCommandRuntime,
|
||||||
setMpvSecondarySubVisibilityRuntime,
|
|
||||||
setMpvSubVisibilityRuntime,
|
setMpvSubVisibilityRuntime,
|
||||||
setOverlayDebugVisualizationEnabledRuntime,
|
setOverlayDebugVisualizationEnabledRuntime,
|
||||||
syncOverlayWindowLayer,
|
syncOverlayWindowLayer,
|
||||||
@@ -731,10 +730,6 @@ const ensureOverlayMpvSubtitlesHidden = createEnsureOverlayMpvSubtitlesHiddenHan
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
appState.overlaySavedMpvSubVisibility = visible;
|
appState.overlaySavedMpvSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => appState.overlaySavedSecondaryMpvSubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
appState.overlaySavedSecondaryMpvSubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => appState.overlayMpvSubVisibilityRevision,
|
getRevision: () => appState.overlayMpvSubVisibilityRevision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
appState.overlayMpvSubVisibilityRevision = revision;
|
appState.overlayMpvSubVisibilityRevision = revision;
|
||||||
@@ -742,9 +737,6 @@ const ensureOverlayMpvSubtitlesHidden = createEnsureOverlayMpvSubtitlesHiddenHan
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
setMpvSubVisibilityRuntime(appState.mpvClient, visible);
|
setMpvSubVisibilityRuntime(appState.mpvClient, visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
setMpvSecondarySubVisibilityRuntime(appState.mpvClient, visible);
|
|
||||||
},
|
|
||||||
logWarn: (message, error) => {
|
logWarn: (message, error) => {
|
||||||
logger.warn(message, error);
|
logger.warn(message, error);
|
||||||
},
|
},
|
||||||
@@ -754,10 +746,6 @@ const restoreOverlayMpvSubtitles = createRestoreOverlayMpvSubtitlesHandler({
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
appState.overlaySavedMpvSubVisibility = visible;
|
appState.overlaySavedMpvSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => appState.overlaySavedSecondaryMpvSubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
appState.overlaySavedSecondaryMpvSubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => appState.overlayMpvSubVisibilityRevision,
|
getRevision: () => appState.overlayMpvSubVisibilityRevision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
appState.overlayMpvSubVisibilityRevision = revision;
|
appState.overlayMpvSubVisibilityRevision = revision;
|
||||||
@@ -767,16 +755,12 @@ const restoreOverlayMpvSubtitles = createRestoreOverlayMpvSubtitlesHandler({
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
setMpvSubVisibilityRuntime(appState.mpvClient, visible);
|
setMpvSubVisibilityRuntime(appState.mpvClient, visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
setMpvSecondarySubVisibilityRuntime(appState.mpvClient, visible);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function shouldSuppressMpvSubtitlesForOverlay(): boolean {
|
function shouldSuppressMpvSubtitlesForOverlay(): boolean {
|
||||||
return (
|
return (
|
||||||
appState.secondarySubMode === 'visible' ||
|
overlayManager.getVisibleOverlayVisible() &&
|
||||||
(overlayManager.getVisibleOverlayVisible() &&
|
configDerivedRuntime.shouldBindVisibleOverlayToMpvSubVisibility()
|
||||||
configDerivedRuntime.shouldBindVisibleOverlayToMpvSubVisibility())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1884,8 +1868,8 @@ const {
|
|||||||
destroyTray: () => destroyTray(),
|
destroyTray: () => destroyTray(),
|
||||||
stopConfigHotReload: () => configHotReloadRuntime.stop(),
|
stopConfigHotReload: () => configHotReloadRuntime.stop(),
|
||||||
restorePreviousSecondarySubVisibility: () => restorePreviousSecondarySubVisibility(),
|
restorePreviousSecondarySubVisibility: () => restorePreviousSecondarySubVisibility(),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {
|
restoreMpvSubVisibility: () => {
|
||||||
restoreOverlayMpvSubtitles({ respectVisibleOverlayBinding: false });
|
restoreOverlayMpvSubtitles();
|
||||||
},
|
},
|
||||||
unregisterAllGlobalShortcuts: () => globalShortcut.unregisterAll(),
|
unregisterAllGlobalShortcuts: () => globalShortcut.unregisterAll(),
|
||||||
stopSubtitleWebsocket: () => subtitleWsService.stop(),
|
stopSubtitleWebsocket: () => subtitleWsService.stop(),
|
||||||
@@ -2181,8 +2165,8 @@ const {
|
|||||||
updateCurrentMediaPath: (path) => {
|
updateCurrentMediaPath: (path) => {
|
||||||
mediaRuntime.updateCurrentMediaPath(path);
|
mediaRuntime.updateCurrentMediaPath(path);
|
||||||
},
|
},
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {
|
restoreMpvSubVisibility: () => {
|
||||||
restoreOverlayMpvSubtitles({ respectVisibleOverlayBinding: false });
|
restoreOverlayMpvSubtitles();
|
||||||
},
|
},
|
||||||
getCurrentAnilistMediaKey: () => getCurrentAnilistMediaKey(),
|
getCurrentAnilistMediaKey: () => getCurrentAnilistMediaKey(),
|
||||||
resetAnilistMediaTracking: (mediaKey) => {
|
resetAnilistMediaTracking: (mediaKey) => {
|
||||||
@@ -2529,7 +2513,6 @@ const cycleSecondarySubMode = createCycleSecondarySubModeRuntimeHandler({
|
|||||||
|
|
||||||
function setSecondarySubMode(mode: SecondarySubMode): void {
|
function setSecondarySubMode(mode: SecondarySubMode): void {
|
||||||
appState.secondarySubMode = mode;
|
appState.secondarySubMode = mode;
|
||||||
syncOverlayMpvSubtitleSuppression();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCycleSecondarySubMode(): void {
|
function handleCycleSecondarySubMode(): void {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function createMockWindow(): MockWindow & {
|
|||||||
url: 'file:///overlay/index.html?layer=modal',
|
url: 'file:///overlay/index.html?layer=modal',
|
||||||
loadCallbacks: [],
|
loadCallbacks: [],
|
||||||
};
|
};
|
||||||
return {
|
const window = {
|
||||||
...state,
|
...state,
|
||||||
isDestroyed: () => state.destroyed,
|
isDestroyed: () => state.destroyed,
|
||||||
isVisible: () => state.visible,
|
isVisible: () => state.visible,
|
||||||
@@ -92,6 +92,29 @@ function createMockWindow(): MockWindow & {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'loading', {
|
||||||
|
get: () => state.loading,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
state.loading = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'url', {
|
||||||
|
get: () => state.url,
|
||||||
|
set: (value: string) => {
|
||||||
|
state.url = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'ignoreMouseEvents', {
|
||||||
|
get: () => state.ignoreMouseEvents,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
state.ignoreMouseEvents = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('sendToActiveOverlayWindow targets modal window with full geometry and tracks close restore', () => {
|
test('sendToActiveOverlayWindow targets modal window with full geometry and tracks close restore', () => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import type { WindowGeometry } from '../types';
|
import type { WindowGeometry } from '../types';
|
||||||
|
|
||||||
|
const MODAL_REVEAL_FALLBACK_DELAY_MS = 250;
|
||||||
|
|
||||||
type OverlayHostedModal = 'runtime-options' | 'subsync' | 'jimaku' | 'kiku';
|
type OverlayHostedModal = 'runtime-options' | 'subsync' | 'jimaku' | 'kiku';
|
||||||
|
|
||||||
export interface OverlayWindowResolver {
|
export interface OverlayWindowResolver {
|
||||||
@@ -67,14 +69,7 @@ export function createOverlayModalRuntimeService(
|
|||||||
if (window.webContents.isLoading()) {
|
if (window.webContents.isLoading()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return window.webContents.getURL() !== '' && window.webContents.getURL() !== 'about:blank';
|
||||||
const getURL = window.webContents.getURL;
|
|
||||||
if (typeof getURL !== 'function') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentURL = getURL.call(window.webContents);
|
|
||||||
return currentURL !== '' && currentURL !== 'about:blank';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendOrQueueForWindow = (
|
const sendOrQueueForWindow = (
|
||||||
@@ -142,7 +137,7 @@ export function createOverlayModalRuntimeService(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showModalWindow(targetWindow, { passThroughMouseEvents: true });
|
showModalWindow(targetWindow, { passThroughMouseEvents: true });
|
||||||
}, 250);
|
}, MODAL_REVEAL_FALLBACK_DELAY_MS);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendToActiveOverlayWindow = (
|
const sendToActiveOverlayWindow = (
|
||||||
@@ -208,8 +203,6 @@ export function createOverlayModalRuntimeService(
|
|||||||
if (restoreVisibleOverlayOnModalClose.size === 0) {
|
if (restoreVisibleOverlayOnModalClose.size === 0) {
|
||||||
clearPendingModalWindowReveal();
|
clearPendingModalWindowReveal();
|
||||||
notifyModalStateChange(false);
|
notifyModalStateChange(false);
|
||||||
}
|
|
||||||
if (restoreVisibleOverlayOnModalClose.size === 0) {
|
|
||||||
modalWindow.hide();
|
modalWindow.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
|
|||||||
destroyTray: () => calls.push('destroy-tray'),
|
destroyTray: () => calls.push('destroy-tray'),
|
||||||
stopConfigHotReload: () => calls.push('stop-config'),
|
stopConfigHotReload: () => calls.push('stop-config'),
|
||||||
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||||
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
||||||
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
||||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
|||||||
destroyTray: () => void;
|
destroyTray: () => void;
|
||||||
stopConfigHotReload: () => void;
|
stopConfigHotReload: () => void;
|
||||||
restorePreviousSecondarySubVisibility: () => void;
|
restorePreviousSecondarySubVisibility: () => void;
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
restoreMpvSubVisibility: () => void;
|
||||||
unregisterAllGlobalShortcuts: () => void;
|
unregisterAllGlobalShortcuts: () => void;
|
||||||
stopSubtitleWebsocket: () => void;
|
stopSubtitleWebsocket: () => void;
|
||||||
stopTexthookerService: () => void;
|
stopTexthookerService: () => void;
|
||||||
@@ -26,7 +26,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
|||||||
deps.destroyTray();
|
deps.destroyTray();
|
||||||
deps.stopConfigHotReload();
|
deps.stopConfigHotReload();
|
||||||
deps.restorePreviousSecondarySubVisibility();
|
deps.restorePreviousSecondarySubVisibility();
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
deps.restoreMpvSubVisibility();
|
||||||
deps.unregisterAllGlobalShortcuts();
|
deps.unregisterAllGlobalShortcuts();
|
||||||
deps.stopSubtitleWebsocket();
|
deps.stopSubtitleWebsocket();
|
||||||
deps.stopTexthookerService();
|
deps.stopTexthookerService();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ test('cleanup deps builder returns handlers that guard optional runtime objects'
|
|||||||
destroyTray: () => calls.push('destroy-tray'),
|
destroyTray: () => calls.push('destroy-tray'),
|
||||||
stopConfigHotReload: () => calls.push('stop-config'),
|
stopConfigHotReload: () => calls.push('stop-config'),
|
||||||
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
restorePreviousSecondarySubVisibility: () => calls.push('restore-sub'),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||||
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
unregisterAllGlobalShortcuts: () => calls.push('unregister-shortcuts'),
|
||||||
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
stopSubtitleWebsocket: () => calls.push('stop-ws'),
|
||||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||||
@@ -73,7 +73,7 @@ test('cleanup deps builder skips destroyed yomitan window', () => {
|
|||||||
destroyTray: () => {},
|
destroyTray: () => {},
|
||||||
stopConfigHotReload: () => {},
|
stopConfigHotReload: () => {},
|
||||||
restorePreviousSecondarySubVisibility: () => {},
|
restorePreviousSecondarySubVisibility: () => {},
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
restoreMpvSubVisibility: () => {},
|
||||||
unregisterAllGlobalShortcuts: () => {},
|
unregisterAllGlobalShortcuts: () => {},
|
||||||
stopSubtitleWebsocket: () => {},
|
stopSubtitleWebsocket: () => {},
|
||||||
stopTexthookerService: () => {},
|
stopTexthookerService: () => {},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
|||||||
destroyTray: () => void;
|
destroyTray: () => void;
|
||||||
stopConfigHotReload: () => void;
|
stopConfigHotReload: () => void;
|
||||||
restorePreviousSecondarySubVisibility: () => void;
|
restorePreviousSecondarySubVisibility: () => void;
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
restoreMpvSubVisibility: () => void;
|
||||||
unregisterAllGlobalShortcuts: () => void;
|
unregisterAllGlobalShortcuts: () => void;
|
||||||
stopSubtitleWebsocket: () => void;
|
stopSubtitleWebsocket: () => void;
|
||||||
stopTexthookerService: () => void;
|
stopTexthookerService: () => void;
|
||||||
@@ -52,8 +52,8 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
|||||||
destroyTray: () => deps.destroyTray(),
|
destroyTray: () => deps.destroyTray(),
|
||||||
stopConfigHotReload: () => deps.stopConfigHotReload(),
|
stopConfigHotReload: () => deps.stopConfigHotReload(),
|
||||||
restorePreviousSecondarySubVisibility: () => deps.restorePreviousSecondarySubVisibility(),
|
restorePreviousSecondarySubVisibility: () => deps.restorePreviousSecondarySubVisibility(),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
restoreMpvSubVisibility: () =>
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
deps.restoreMpvSubVisibility(),
|
||||||
unregisterAllGlobalShortcuts: () => deps.unregisterAllGlobalShortcuts(),
|
unregisterAllGlobalShortcuts: () => deps.unregisterAllGlobalShortcuts(),
|
||||||
stopSubtitleWebsocket: () => deps.stopSubtitleWebsocket(),
|
stopSubtitleWebsocket: () => deps.stopSubtitleWebsocket(),
|
||||||
stopTexthookerService: () => deps.stopTexthookerService(),
|
stopTexthookerService: () => deps.stopTexthookerService(),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
|||||||
onSubtitleChange: () => {},
|
onSubtitleChange: () => {},
|
||||||
refreshDiscordPresence: () => {},
|
refreshDiscordPresence: () => {},
|
||||||
updateCurrentMediaPath: () => {},
|
updateCurrentMediaPath: () => {},
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
restoreMpvSubVisibility: () => {},
|
||||||
getCurrentAnilistMediaKey: () => null,
|
getCurrentAnilistMediaKey: () => null,
|
||||||
resetAnilistMediaTracking: () => {},
|
resetAnilistMediaTracking: () => {},
|
||||||
maybeProbeAnilistDuration: () => {},
|
maybeProbeAnilistDuration: () => {},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler
|
|||||||
destroyTray: () => {},
|
destroyTray: () => {},
|
||||||
stopConfigHotReload: () => {},
|
stopConfigHotReload: () => {},
|
||||||
restorePreviousSecondarySubVisibility: () => {},
|
restorePreviousSecondarySubVisibility: () => {},
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => {},
|
restoreMpvSubVisibility: () => {},
|
||||||
unregisterAllGlobalShortcuts: () => {},
|
unregisterAllGlobalShortcuts: () => {},
|
||||||
stopSubtitleWebsocket: () => {},
|
stopSubtitleWebsocket: () => {},
|
||||||
stopTexthookerService: () => {},
|
stopTexthookerService: () => {},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ test('media path change handler reports stop for empty path and probes media key
|
|||||||
const handler = createHandleMpvMediaPathChangeHandler({
|
const handler = createHandleMpvMediaPathChangeHandler({
|
||||||
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
||||||
reportJellyfinRemoteStopped: () => calls.push('stopped'),
|
reportJellyfinRemoteStopped: () => calls.push('stopped'),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||||
getCurrentAnilistMediaKey: () => 'show:1',
|
getCurrentAnilistMediaKey: () => 'show:1',
|
||||||
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
|
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
|
||||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function createHandleMpvSecondarySubtitleChangeHandler(deps: {
|
|||||||
export function createHandleMpvMediaPathChangeHandler(deps: {
|
export function createHandleMpvMediaPathChangeHandler(deps: {
|
||||||
updateCurrentMediaPath: (path: string) => void;
|
updateCurrentMediaPath: (path: string) => void;
|
||||||
reportJellyfinRemoteStopped: () => void;
|
reportJellyfinRemoteStopped: () => void;
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
restoreMpvSubVisibility: () => void;
|
||||||
getCurrentAnilistMediaKey: () => string | null;
|
getCurrentAnilistMediaKey: () => string | null;
|
||||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||||
@@ -45,7 +45,7 @@ export function createHandleMpvMediaPathChangeHandler(deps: {
|
|||||||
deps.updateCurrentMediaPath(path);
|
deps.updateCurrentMediaPath(path);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
deps.reportJellyfinRemoteStopped();
|
deps.reportJellyfinRemoteStopped();
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
deps.restoreMpvSubVisibility();
|
||||||
}
|
}
|
||||||
const mediaKey = deps.getCurrentAnilistMediaKey();
|
const mediaKey = deps.getCurrentAnilistMediaKey();
|
||||||
deps.resetAnilistMediaTracking(mediaKey);
|
deps.resetAnilistMediaTracking(mediaKey);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
|||||||
broadcastSecondarySubtitle: (text) => calls.push(`broadcast-secondary:${text}`),
|
broadcastSecondarySubtitle: (text) => calls.push(`broadcast-secondary:${text}`),
|
||||||
|
|
||||||
updateCurrentMediaPath: (path) => calls.push(`media-path:${path}`),
|
updateCurrentMediaPath: (path) => calls.push(`media-path:${path}`),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||||
getCurrentAnilistMediaKey: () => 'media-key',
|
getCurrentAnilistMediaKey: () => 'media-key',
|
||||||
resetAnilistMediaTracking: (key) => calls.push(`reset-media:${String(key)}`),
|
resetAnilistMediaTracking: (key) => calls.push(`reset-media:${String(key)}`),
|
||||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
|||||||
broadcastSecondarySubtitle: (text: string) => void;
|
broadcastSecondarySubtitle: (text: string) => void;
|
||||||
|
|
||||||
updateCurrentMediaPath: (path: string) => void;
|
updateCurrentMediaPath: (path: string) => void;
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
restoreMpvSubVisibility: () => void;
|
||||||
getCurrentAnilistMediaKey: () => string | null;
|
getCurrentAnilistMediaKey: () => string | null;
|
||||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||||
@@ -97,8 +97,8 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
|||||||
const handleMpvMediaPathChange = createHandleMpvMediaPathChangeHandler({
|
const handleMpvMediaPathChange = createHandleMpvMediaPathChangeHandler({
|
||||||
updateCurrentMediaPath: (path) => deps.updateCurrentMediaPath(path),
|
updateCurrentMediaPath: (path) => deps.updateCurrentMediaPath(path),
|
||||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
restoreMpvSubVisibility: () =>
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
deps.restoreMpvSubVisibility(),
|
||||||
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
||||||
resetAnilistMediaTracking: (mediaKey) => deps.resetAnilistMediaTracking(mediaKey),
|
resetAnilistMediaTracking: (mediaKey) => deps.resetAnilistMediaTracking(mediaKey),
|
||||||
maybeProbeAnilistDuration: (mediaKey) => deps.maybeProbeAnilistDuration(mediaKey),
|
maybeProbeAnilistDuration: (mediaKey) => deps.maybeProbeAnilistDuration(mediaKey),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
|||||||
calls.push(`broadcast:${channel}:${String(payload)}`),
|
calls.push(`broadcast:${channel}:${String(payload)}`),
|
||||||
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
||||||
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => calls.push('restore-mpv-sub'),
|
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||||
getCurrentAnilistMediaKey: () => 'media-key',
|
getCurrentAnilistMediaKey: () => 'media-key',
|
||||||
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${mediaKey}`),
|
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${mediaKey}`),
|
||||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||||
@@ -75,7 +75,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
|||||||
deps.broadcastSubtitleAss('ass');
|
deps.broadcastSubtitleAss('ass');
|
||||||
deps.broadcastSecondarySubtitle('sec');
|
deps.broadcastSecondarySubtitle('sec');
|
||||||
deps.updateCurrentMediaPath('/tmp/video');
|
deps.updateCurrentMediaPath('/tmp/video');
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay();
|
deps.restoreMpvSubVisibility();
|
||||||
assert.equal(deps.getCurrentAnilistMediaKey(), 'media-key');
|
assert.equal(deps.getCurrentAnilistMediaKey(), 'media-key');
|
||||||
deps.resetAnilistMediaTracking('media-key');
|
deps.resetAnilistMediaTracking('media-key');
|
||||||
deps.maybeProbeAnilistDuration('media-key');
|
deps.maybeProbeAnilistDuration('media-key');
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
|||||||
broadcastToOverlayWindows: (channel: string, payload: unknown) => void;
|
broadcastToOverlayWindows: (channel: string, payload: unknown) => void;
|
||||||
onSubtitleChange: (text: string) => void;
|
onSubtitleChange: (text: string) => void;
|
||||||
updateCurrentMediaPath: (path: string) => void;
|
updateCurrentMediaPath: (path: string) => void;
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () => void;
|
restoreMpvSubVisibility: () => void;
|
||||||
getCurrentAnilistMediaKey: () => string | null;
|
getCurrentAnilistMediaKey: () => string | null;
|
||||||
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
resetAnilistMediaTracking: (mediaKey: string | null) => void;
|
||||||
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
maybeProbeAnilistDuration: (mediaKey: string) => void;
|
||||||
@@ -71,8 +71,8 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
|||||||
broadcastSecondarySubtitle: (text: string) =>
|
broadcastSecondarySubtitle: (text: string) =>
|
||||||
deps.broadcastToOverlayWindows('secondary-subtitle:set', text),
|
deps.broadcastToOverlayWindows('secondary-subtitle:set', text),
|
||||||
updateCurrentMediaPath: (path: string) => deps.updateCurrentMediaPath(path),
|
updateCurrentMediaPath: (path: string) => deps.updateCurrentMediaPath(path),
|
||||||
restoreMpvSubVisibilityForInvisibleOverlay: () =>
|
restoreMpvSubVisibility: () =>
|
||||||
deps.restoreMpvSubVisibilityForInvisibleOverlay(),
|
deps.restoreMpvSubVisibility(),
|
||||||
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
getCurrentAnilistMediaKey: () => deps.getCurrentAnilistMediaKey(),
|
||||||
resetAnilistMediaTracking: (mediaKey: string | null) =>
|
resetAnilistMediaTracking: (mediaKey: string | null) =>
|
||||||
deps.resetAnilistMediaTracking(mediaKey),
|
deps.resetAnilistMediaTracking(mediaKey),
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ import {
|
|||||||
|
|
||||||
type VisibilityState = {
|
type VisibilityState = {
|
||||||
savedSubVisibility: boolean | null;
|
savedSubVisibility: boolean | null;
|
||||||
savedSecondarySubVisibility: boolean | null;
|
|
||||||
revision: number;
|
revision: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
test('ensure overlay mpv subtitle suppression captures previous visibility then hides subtitles', async () => {
|
test('ensure overlay mpv subtitle suppression captures previous visibility then hides subtitles', async () => {
|
||||||
const state: VisibilityState = {
|
const state: VisibilityState = {
|
||||||
savedSubVisibility: null,
|
savedSubVisibility: null,
|
||||||
savedSecondarySubVisibility: null,
|
|
||||||
revision: 0,
|
revision: 0,
|
||||||
};
|
};
|
||||||
const calls: boolean[] = [];
|
const calls: boolean[] = [];
|
||||||
@@ -29,10 +27,6 @@ test('ensure overlay mpv subtitle suppression captures previous visibility then
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
state.savedSubVisibility = visible;
|
state.savedSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
state.savedSecondarySubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => state.revision,
|
getRevision: () => state.revision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
state.revision = revision;
|
state.revision = revision;
|
||||||
@@ -40,24 +34,19 @@ test('ensure overlay mpv subtitle suppression captures previous visibility then
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
calls.push(visible);
|
calls.push(visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
calls.push(visible);
|
|
||||||
},
|
|
||||||
logWarn: () => {},
|
logWarn: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await ensureHidden();
|
await ensureHidden();
|
||||||
|
|
||||||
assert.equal(state.savedSubVisibility, false);
|
assert.equal(state.savedSubVisibility, false);
|
||||||
assert.equal(state.savedSecondarySubVisibility, false);
|
|
||||||
assert.equal(state.revision, 1);
|
assert.equal(state.revision, 1);
|
||||||
assert.deepEqual(calls, [false, false]);
|
assert.deepEqual(calls, [false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('restore overlay mpv subtitle suppression restores saved visibility', () => {
|
test('restore overlay mpv subtitle suppression restores saved visibility', () => {
|
||||||
const state: VisibilityState = {
|
const state: VisibilityState = {
|
||||||
savedSubVisibility: false,
|
savedSubVisibility: false,
|
||||||
savedSecondarySubVisibility: true,
|
|
||||||
revision: 4,
|
revision: 4,
|
||||||
};
|
};
|
||||||
const calls: boolean[] = [];
|
const calls: boolean[] = [];
|
||||||
@@ -67,10 +56,6 @@ test('restore overlay mpv subtitle suppression restores saved visibility', () =>
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
state.savedSubVisibility = visible;
|
state.savedSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
state.savedSecondarySubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => state.revision,
|
getRevision: () => state.revision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
state.revision = revision;
|
state.revision = revision;
|
||||||
@@ -80,23 +65,18 @@ test('restore overlay mpv subtitle suppression restores saved visibility', () =>
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
calls.push(visible);
|
calls.push(visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
calls.push(visible);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
restore();
|
restore();
|
||||||
|
|
||||||
assert.equal(state.savedSubVisibility, null);
|
assert.equal(state.savedSubVisibility, null);
|
||||||
assert.equal(state.savedSecondarySubVisibility, null);
|
|
||||||
assert.equal(state.revision, 5);
|
assert.equal(state.revision, 5);
|
||||||
assert.deepEqual(calls, [false, true]);
|
assert.deepEqual(calls, [false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('restore keeps mpv subtitles hidden when visible-overlay binding still requires suppression', () => {
|
test('restore keeps mpv subtitles hidden when visible-overlay binding still requires suppression', () => {
|
||||||
const state: VisibilityState = {
|
const state: VisibilityState = {
|
||||||
savedSubVisibility: true,
|
savedSubVisibility: true,
|
||||||
savedSecondarySubVisibility: true,
|
|
||||||
revision: 9,
|
revision: 9,
|
||||||
};
|
};
|
||||||
const calls: boolean[] = [];
|
const calls: boolean[] = [];
|
||||||
@@ -106,10 +86,6 @@ test('restore keeps mpv subtitles hidden when visible-overlay binding still requ
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
state.savedSubVisibility = visible;
|
state.savedSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
state.savedSecondarySubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => state.revision,
|
getRevision: () => state.revision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
state.revision = revision;
|
state.revision = revision;
|
||||||
@@ -119,23 +95,18 @@ test('restore keeps mpv subtitles hidden when visible-overlay binding still requ
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
calls.push(visible);
|
calls.push(visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
calls.push(visible);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
restore();
|
restore();
|
||||||
|
|
||||||
assert.equal(state.savedSubVisibility, true);
|
assert.equal(state.savedSubVisibility, true);
|
||||||
assert.equal(state.savedSecondarySubVisibility, true);
|
|
||||||
assert.equal(state.revision, 10);
|
assert.equal(state.revision, 10);
|
||||||
assert.deepEqual(calls, [false, false]);
|
assert.deepEqual(calls, [false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('restore defers mpv subtitle restore while mpv is disconnected', () => {
|
test('restore defers mpv subtitle restore while mpv is disconnected', () => {
|
||||||
const state: VisibilityState = {
|
const state: VisibilityState = {
|
||||||
savedSubVisibility: true,
|
savedSubVisibility: true,
|
||||||
savedSecondarySubVisibility: false,
|
|
||||||
revision: 2,
|
revision: 2,
|
||||||
};
|
};
|
||||||
const calls: boolean[] = [];
|
const calls: boolean[] = [];
|
||||||
@@ -145,10 +116,6 @@ test('restore defers mpv subtitle restore while mpv is disconnected', () => {
|
|||||||
setSavedSubVisibility: (visible) => {
|
setSavedSubVisibility: (visible) => {
|
||||||
state.savedSubVisibility = visible;
|
state.savedSubVisibility = visible;
|
||||||
},
|
},
|
||||||
getSavedSecondarySubVisibility: () => state.savedSecondarySubVisibility,
|
|
||||||
setSavedSecondarySubVisibility: (visible) => {
|
|
||||||
state.savedSecondarySubVisibility = visible;
|
|
||||||
},
|
|
||||||
getRevision: () => state.revision,
|
getRevision: () => state.revision,
|
||||||
setRevision: (revision) => {
|
setRevision: (revision) => {
|
||||||
state.revision = revision;
|
state.revision = revision;
|
||||||
@@ -158,9 +125,6 @@ test('restore defers mpv subtitle restore while mpv is disconnected', () => {
|
|||||||
setMpvSubVisibility: (visible) => {
|
setMpvSubVisibility: (visible) => {
|
||||||
calls.push(visible);
|
calls.push(visible);
|
||||||
},
|
},
|
||||||
setMpvSecondarySubVisibility: (visible) => {
|
|
||||||
calls.push(visible);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
restore();
|
restore();
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ type MpvVisibilityClient = {
|
|||||||
requestProperty: (name: string) => Promise<unknown>;
|
requestProperty: (name: string) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RestoreOptions = {
|
|
||||||
respectVisibleOverlayBinding?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseSubVisibility(value: unknown): boolean {
|
function parseSubVisibility(value: unknown): boolean {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const normalized = value.trim().toLowerCase();
|
const normalized = value.trim().toLowerCase();
|
||||||
@@ -33,12 +29,9 @@ export function createEnsureOverlayMpvSubtitlesHiddenHandler(deps: {
|
|||||||
getMpvClient: () => MpvVisibilityClient | null;
|
getMpvClient: () => MpvVisibilityClient | null;
|
||||||
getSavedSubVisibility: () => boolean | null;
|
getSavedSubVisibility: () => boolean | null;
|
||||||
setSavedSubVisibility: (visible: boolean | null) => void;
|
setSavedSubVisibility: (visible: boolean | null) => void;
|
||||||
getSavedSecondarySubVisibility: () => boolean | null;
|
|
||||||
setSavedSecondarySubVisibility: (visible: boolean | null) => void;
|
|
||||||
getRevision: () => number;
|
getRevision: () => number;
|
||||||
setRevision: (revision: number) => void;
|
setRevision: (revision: number) => void;
|
||||||
setMpvSubVisibility: (visible: boolean) => void;
|
setMpvSubVisibility: (visible: boolean) => void;
|
||||||
setMpvSecondarySubVisibility: (visible: boolean) => void;
|
|
||||||
logWarn: (message: string, error: unknown) => void;
|
logWarn: (message: string, error: unknown) => void;
|
||||||
}) {
|
}) {
|
||||||
return async (): Promise<void> => {
|
return async (): Promise<void> => {
|
||||||
@@ -50,9 +43,17 @@ export function createEnsureOverlayMpvSubtitlesHiddenHandler(deps: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deps.getSavedSubVisibility() === null) {
|
const shouldCaptureSavedVisibility = deps.getSavedSubVisibility() === null;
|
||||||
|
const savedVisibilityPromise = shouldCaptureSavedVisibility
|
||||||
|
? mpvClient.requestProperty('sub-visibility')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Hide immediately on overlay toggle; capture/restore logic is handled separately.
|
||||||
|
deps.setMpvSubVisibility(false);
|
||||||
|
|
||||||
|
if (shouldCaptureSavedVisibility && savedVisibilityPromise) {
|
||||||
try {
|
try {
|
||||||
const currentSubVisibility = await mpvClient.requestProperty('sub-visibility');
|
const currentSubVisibility = await savedVisibilityPromise;
|
||||||
if (revision !== deps.getRevision()) {
|
if (revision !== deps.getRevision()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -68,64 +69,28 @@ export function createEnsureOverlayMpvSubtitlesHiddenHandler(deps: {
|
|||||||
deps.setSavedSubVisibility(true);
|
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: {
|
export function createRestoreOverlayMpvSubtitlesHandler(deps: {
|
||||||
getSavedSubVisibility: () => boolean | null;
|
getSavedSubVisibility: () => boolean | null;
|
||||||
setSavedSubVisibility: (visible: boolean | null) => void;
|
setSavedSubVisibility: (visible: boolean | null) => void;
|
||||||
getSavedSecondarySubVisibility: () => boolean | null;
|
|
||||||
setSavedSecondarySubVisibility: (visible: boolean | null) => void;
|
|
||||||
getRevision: () => number;
|
getRevision: () => number;
|
||||||
setRevision: (revision: number) => void;
|
setRevision: (revision: number) => void;
|
||||||
isMpvConnected: () => boolean;
|
isMpvConnected: () => boolean;
|
||||||
shouldKeepSuppressedFromVisibleOverlayBinding: () => boolean;
|
shouldKeepSuppressedFromVisibleOverlayBinding: () => boolean;
|
||||||
setMpvSubVisibility: (visible: boolean) => void;
|
setMpvSubVisibility: (visible: boolean) => void;
|
||||||
setMpvSecondarySubVisibility: (visible: boolean) => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (options?: RestoreOptions): void => {
|
return (): void => {
|
||||||
deps.setRevision(deps.getRevision() + 1);
|
deps.setRevision(deps.getRevision() + 1);
|
||||||
|
|
||||||
const savedVisibility = deps.getSavedSubVisibility();
|
const savedVisibility = deps.getSavedSubVisibility();
|
||||||
const respectVisibleOverlayBinding = options?.respectVisibleOverlayBinding ?? true;
|
if (deps.shouldKeepSuppressedFromVisibleOverlayBinding()) {
|
||||||
if (
|
|
||||||
respectVisibleOverlayBinding &&
|
|
||||||
deps.shouldKeepSuppressedFromVisibleOverlayBinding()
|
|
||||||
) {
|
|
||||||
deps.setMpvSubVisibility(false);
|
deps.setMpvSubVisibility(false);
|
||||||
deps.setMpvSecondarySubVisibility(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSecondarySavedVisibility = deps.getSavedSecondarySubVisibility() !== null;
|
if (savedVisibility === null) {
|
||||||
|
|
||||||
if (savedVisibility === null && !hasSecondarySavedVisibility) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,12 +101,7 @@ export function createRestoreOverlayMpvSubtitlesHandler(deps: {
|
|||||||
if (savedVisibility !== null) {
|
if (savedVisibility !== null) {
|
||||||
deps.setMpvSubVisibility(savedVisibility);
|
deps.setMpvSubVisibility(savedVisibility);
|
||||||
}
|
}
|
||||||
const savedSecondaryVisibility = deps.getSavedSecondarySubVisibility();
|
|
||||||
if (savedSecondaryVisibility !== null) {
|
|
||||||
deps.setMpvSecondarySubVisibility(savedSecondaryVisibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
deps.setSavedSubVisibility(null);
|
deps.setSavedSubVisibility(null);
|
||||||
deps.setSavedSecondarySubVisibility(null);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ export interface AppState {
|
|||||||
lastSecondarySubToggleAtMs: number;
|
lastSecondarySubToggleAtMs: number;
|
||||||
previousSecondarySubVisibility: boolean | null;
|
previousSecondarySubVisibility: boolean | null;
|
||||||
overlaySavedMpvSubVisibility: boolean | null;
|
overlaySavedMpvSubVisibility: boolean | null;
|
||||||
overlaySavedSecondaryMpvSubVisibility: boolean | null;
|
|
||||||
overlayMpvSubVisibilityRevision: number;
|
overlayMpvSubVisibilityRevision: number;
|
||||||
mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics;
|
mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics;
|
||||||
shortcutsRegistered: boolean;
|
shortcutsRegistered: boolean;
|
||||||
@@ -247,7 +246,6 @@ export function createAppState(values: AppStateInitialValues): AppState {
|
|||||||
lastSecondarySubToggleAtMs: 0,
|
lastSecondarySubToggleAtMs: 0,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
overlaySavedMpvSubVisibility: null,
|
overlaySavedMpvSubVisibility: null,
|
||||||
overlaySavedSecondaryMpvSubVisibility: null,
|
|
||||||
overlayMpvSubVisibilityRevision: 0,
|
overlayMpvSubVisibilityRevision: 0,
|
||||||
mpvSubtitleRenderMetrics: {
|
mpvSubtitleRenderMetrics: {
|
||||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ const electronAPI: ElectronAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getOverlayVisibility: (): Promise<boolean> =>
|
getOverlayVisibility: (): Promise<boolean> =>
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.request.getOverlayVisibility),
|
ipcRenderer.invoke(IPC_CHANNELS.request.getVisibleOverlayVisibility),
|
||||||
getCurrentSubtitle: (): Promise<SubtitleData> =>
|
getCurrentSubtitle: (): Promise<SubtitleData> =>
|
||||||
ipcRenderer.invoke(IPC_CHANNELS.request.getCurrentSubtitle),
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCurrentSubtitle),
|
||||||
getCurrentSubtitleRaw: (): Promise<string> =>
|
getCurrentSubtitleRaw: (): Promise<string> =>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { MergedToken } from '../types';
|
|||||||
import { PartOfSpeech } from '../types.js';
|
import { PartOfSpeech } from '../types.js';
|
||||||
import {
|
import {
|
||||||
alignTokensToSourceText,
|
alignTokensToSourceText,
|
||||||
buildInvisibleTokenHoverRanges,
|
buildSubtitleTokenHoverRanges,
|
||||||
computeWordClass,
|
computeWordClass,
|
||||||
normalizeSubtitle,
|
normalizeSubtitle,
|
||||||
sanitizeSubtitleHoverTokenColor,
|
sanitizeSubtitleHoverTokenColor,
|
||||||
@@ -266,26 +266,26 @@ test('alignTokensToSourceText avoids duplicate tail when later token surface doe
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildInvisibleTokenHoverRanges tracks token offsets across text separators', () => {
|
test('buildSubtitleTokenHoverRanges tracks token offsets across text separators', () => {
|
||||||
const tokens = [
|
const tokens = [
|
||||||
createToken({ surface: 'キリキリと' }),
|
createToken({ surface: 'キリキリと' }),
|
||||||
createToken({ surface: 'かかってこい' }),
|
createToken({ surface: 'かかってこい' }),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ranges = buildInvisibleTokenHoverRanges(tokens, 'キリキリと\nかかってこい');
|
const ranges = buildSubtitleTokenHoverRanges(tokens, 'キリキリと\nかかってこい');
|
||||||
assert.deepEqual(ranges, [
|
assert.deepEqual(ranges, [
|
||||||
{ start: 0, end: 5, tokenIndex: 0 },
|
{ start: 0, end: 5, tokenIndex: 0 },
|
||||||
{ start: 6, end: 12, tokenIndex: 1 },
|
{ start: 6, end: 12, tokenIndex: 1 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildInvisibleTokenHoverRanges ignores unmatched token surfaces', () => {
|
test('buildSubtitleTokenHoverRanges ignores unmatched token surfaces', () => {
|
||||||
const tokens = [
|
const tokens = [
|
||||||
createToken({ surface: '君たちが潰した拠点に' }),
|
createToken({ surface: '君たちが潰した拠点に' }),
|
||||||
createToken({ surface: '教団の主力は1人もいない' }),
|
createToken({ surface: '教団の主力は1人もいない' }),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ranges = buildInvisibleTokenHoverRanges(tokens, '君たちが潰した拠点に\n教団の主力は1人もいない');
|
const ranges = buildSubtitleTokenHoverRanges(tokens, '君たちが潰した拠点に\n教団の主力は1人もいない');
|
||||||
assert.deepEqual(ranges, [{ start: 0, end: 10, tokenIndex: 0 }]);
|
assert.deepEqual(ranges, [{ start: 0, end: 10, tokenIndex: 0 }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type FrequencyRenderSettings = {
|
|||||||
bandedColors: [string, string, string, string, string];
|
bandedColors: [string, string, string, string, string];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InvisibleTokenHoverRange = {
|
export type SubtitleTokenHoverRange = {
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
tokenIndex: number;
|
tokenIndex: number;
|
||||||
@@ -37,6 +37,8 @@ export function normalizeSubtitle(text: string, trim = true, collapseLineBreaks
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
||||||
|
const SAFE_CSS_COLOR_PATTERN =
|
||||||
|
/^(?:#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgba?|hsla?)\([^)]*\)|var\([^)]*\)|[a-zA-Z]+)$/;
|
||||||
|
|
||||||
function sanitizeHexColor(value: unknown, fallback: string): string {
|
function sanitizeHexColor(value: unknown, fallback: string): string {
|
||||||
return typeof value === 'string' && HEX_COLOR_PATTERN.test(value.trim())
|
return typeof value === 'string' && HEX_COLOR_PATTERN.test(value.trim())
|
||||||
@@ -58,7 +60,9 @@ function sanitizeSubtitleHoverTokenBackgroundColor(value: unknown): string {
|
|||||||
return 'rgba(54, 58, 79, 0.84)';
|
return 'rgba(54, 58, 79, 0.84)';
|
||||||
}
|
}
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
return trimmed.length > 0 ? trimmed : 'rgba(54, 58, 79, 0.84)';
|
return trimmed.length > 0 && SAFE_CSS_COLOR_PATTERN.test(trimmed)
|
||||||
|
? trimmed
|
||||||
|
: 'rgba(54, 58, 79, 0.84)';
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_FREQUENCY_RENDER_SETTINGS: FrequencyRenderSettings = {
|
const DEFAULT_FREQUENCY_RENDER_SETTINGS: FrequencyRenderSettings = {
|
||||||
@@ -293,16 +297,16 @@ export function alignTokensToSourceText(
|
|||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildInvisibleTokenHoverRanges(
|
export function buildSubtitleTokenHoverRanges(
|
||||||
tokens: MergedToken[],
|
tokens: MergedToken[],
|
||||||
sourceText: string,
|
sourceText: string,
|
||||||
): InvisibleTokenHoverRange[] {
|
): SubtitleTokenHoverRange[] {
|
||||||
if (tokens.length === 0 || sourceText.length === 0) {
|
if (tokens.length === 0 || sourceText.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const segments = alignTokensToSourceText(tokens, sourceText);
|
const segments = alignTokensToSourceText(tokens, sourceText);
|
||||||
const ranges: InvisibleTokenHoverRange[] = [];
|
const ranges: SubtitleTokenHoverRange[] = [];
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
||||||
for (const segment of segments) {
|
for (const segment of segments) {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export const IPC_CHANNELS = {
|
|||||||
overlayModalOpened: 'overlay:modal-opened',
|
overlayModalOpened: 'overlay:modal-opened',
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
getOverlayVisibility: 'get-overlay-visibility',
|
|
||||||
getVisibleOverlayVisibility: 'get-visible-overlay-visibility',
|
getVisibleOverlayVisibility: 'get-visible-overlay-visibility',
|
||||||
getCurrentSubtitle: 'get-current-subtitle',
|
getCurrentSubtitle: 'get-current-subtitle',
|
||||||
getCurrentSubtitleRaw: 'get-current-subtitle-raw',
|
getCurrentSubtitleRaw: 'get-current-subtitle-raw',
|
||||||
|
|||||||
Reference in New Issue
Block a user