refactor(ipc): remove dead preload methods and the ASS line broadcast (#293)

Drop 16 overlay preload methods with no caller (onSubtitleAss,
getCurrentSubtitleAss, onVisibility, getOverlayVisibility,
openYomitanSettings, quitApp, toggleOverlay, getMecabStatus,
setMecabEnabled, getKeybindings, saveControllerPreference,
getAnkiConnectStatus, setAnkiConnectEnabled, clearAnkiConnectHistory,
cycleRuntimeOption, appendClipboardVideoToQueue) along with their
IPC_CHANNELS keys, handlers, validators and dep plumbing.

With both ASS consumers gone, stop observing sub-text/ass in mpv and
remove the subtitle-ass-change event chain and appState.currentSubAssText.
The main-process functions these wrapped stay; tray, CLI, shortcuts and
session actions still use them.
This commit is contained in:
sudacode authored and GitHub committed 2026-10-12 22:11:17 -07:00
1 parent 8fc20b2499
commit 7909fcd2cf
32 files changed
+8 -876

No files matched your search

-13
View File
@@ -32,8 +32,6 @@ test('anki/jimaku IPC handlers reject malformed invoke payloads', async () => {
let previewCalls = 0;
registerAnkiJimakuIpcHandlers(
{
setAnkiConnectEnabled: () => {},
clearAnkiHistory: () => {},
refreshKnownWords: async () => {},
respondFieldGrouping: () => {},
buildKikuMergePreview: async () => {
@@ -138,8 +136,6 @@ test('tsukihime downloads always route Japanese as primary', async () => {
const secondaryLoads: string[] = [];
registerAnkiJimakuIpcHandlers(
{
setAnkiConnectEnabled: () => {},
clearAnkiHistory: () => {},
refreshKnownWords: async () => {},
respondFieldGrouping: () => {},
buildKikuMergePreview: async () => ({ ok: true }),
@@ -205,13 +201,8 @@ test('tsukihime downloads always route Japanese as primary', async () => {
test('anki/jimaku IPC command handlers ignore malformed payloads', () => {
const { registrar, onHandlers } = createFakeRegistrar();
const fieldGroupingChoices: unknown[] = [];
const enabledStates: boolean[] = [];
registerAnkiJimakuIpcHandlers(
{
setAnkiConnectEnabled: (enabled) => {
enabledStates.push(enabled);
},
clearAnkiHistory: () => {},
refreshKnownWords: async () => {},
respondFieldGrouping: (choice) => {
fieldGroupingChoices.push(choice);
@@ -241,10 +232,6 @@ test('anki/jimaku IPC command handlers ignore malformed payloads', () => {
registrar,
);
onHandlers.get(IPC_CHANNELS.command.setAnkiConnectEnabled)!({}, 'true');
onHandlers.get(IPC_CHANNELS.command.setAnkiConnectEnabled)!({}, true);
assert.deepEqual(enabledStates, [true]);
onHandlers.get(IPC_CHANNELS.command.kikuFieldGroupingRespond)!({}, null);
onHandlers.get(IPC_CHANNELS.command.kikuFieldGroupingRespond)!(
{},
-11
View File
@@ -40,8 +40,6 @@ const { ipcMain } = electron;
const logger = createLogger('main:anki-jimaku-ipc');
export interface AnkiJimakuIpcDeps {
setAnkiConnectEnabled: (enabled: boolean) => void;
clearAnkiHistory: () => void;
refreshKnownWords: () => Promise<void> | void;
respondFieldGrouping: (choice: KikuFieldGroupingChoice) => void;
buildKikuMergePreview: (request: KikuMergePreviewRequest) => Promise<KikuMergePreviewResponse>;
@@ -77,15 +75,6 @@ export function registerAnkiJimakuIpcHandlers(
deps: AnkiJimakuIpcDeps,
ipc: IpcMainRegistrar = ipcMain,
): void {
ipc.on(IPC_CHANNELS.command.setAnkiConnectEnabled, (_event: unknown, enabled: unknown) => {
if (typeof enabled !== 'boolean') return;
deps.setAnkiConnectEnabled(enabled);
});
ipc.on(IPC_CHANNELS.command.clearAnkiConnectHistory, () => {
deps.clearAnkiHistory();
});
ipc.on(IPC_CHANNELS.command.refreshKnownWords, async () => {
await deps.refreshKnownWords();
});
+1 -54
View File
@@ -8,8 +8,6 @@ interface RuntimeHarness {
state: {
ankiIntegration: unknown;
fieldGroupingResolver: ((choice: unknown) => void) | null;
patches: boolean[];
broadcasts: number;
fetchCalls: Array<{ endpoint: string; query?: Record<string, unknown> }>;
tsukihimeFetchCalls: Array<{ endpoint: string; query?: Record<string, unknown> }>;
sentCommands: Array<{ command: (string | number)[] }>;
@@ -20,8 +18,6 @@ function createHarness(): RuntimeHarness {
const state = {
ankiIntegration: null as unknown,
fieldGroupingResolver: null as ((choice: unknown) => void) | null,
patches: [] as boolean[],
broadcasts: 0,
fetchCalls: [] as Array<{
endpoint: string;
query?: Record<string, unknown>;
@@ -34,11 +30,7 @@ function createHarness(): RuntimeHarness {
};
const options: AnkiJimakuIpcRuntimeOptions = {
patchAnkiConnectEnabled: (enabled) => {
state.patches.push(enabled);
},
getResolvedConfig: () => ({ tsukihime: { maxSearchResults: 2 } }),
getRuntimeOptionsManager: () => null,
tsukihimeFetchJson: async (endpoint, query) => {
state.tsukihimeFetchCalls.push({
endpoint,
@@ -76,7 +68,6 @@ function createHarness(): RuntimeHarness {
} as never,
};
},
getSubtitleTimingTracker: () => null,
getMpvClient: () => ({
connected: true,
send: (payload) => {
@@ -99,20 +90,6 @@ function createHarness(): RuntimeHarness {
},
}),
getAnkiIntegration: () => state.ankiIntegration as never,
setAnkiIntegration: (integration) => {
state.ankiIntegration = integration;
},
getKnownWordCacheStatePath: () => '/tmp/subminer-known-words-cache.json',
showDesktopNotification: () => {},
createFieldGroupingCallback: () => async () => ({
keepNoteId: 1,
deleteNoteId: 2,
deleteDuplicate: false,
cancelled: false,
}),
broadcastRuntimeOptionsChanged: () => {
state.broadcasts += 1;
},
getFieldGroupingResolver: () => state.fieldGroupingResolver as never,
setFieldGroupingResolver: (resolver) => {
state.fieldGroupingResolver = resolver as never;
@@ -169,51 +146,21 @@ test('refreshKnownWords throws when integration is unavailable', async () => {
);
});
test('setAnkiConnectEnabled disables active integration and broadcasts changes', () => {
test('respondFieldGrouping resolves and clears the pending resolver', () => {
const { registered, state } = createHarness();
let destroyed = 0;
state.ankiIntegration = {
destroy: () => {
destroyed += 1;
},
};
registered.setAnkiConnectEnabled!(false);
assert.deepEqual(state.patches, [false]);
assert.equal(destroyed, 1);
assert.equal(state.ankiIntegration, null);
assert.equal(state.broadcasts, 1);
});
test('clearAnkiHistory and respondFieldGrouping execute runtime callbacks', () => {
const { registered, state, options } = createHarness();
let cleaned = 0;
let resolvedChoice: unknown = null;
state.fieldGroupingResolver = (choice) => {
resolvedChoice = choice;
};
const originalGetTracker = options.getSubtitleTimingTracker;
options.getSubtitleTimingTracker = () =>
({
cleanup: () => {
cleaned += 1;
},
}) as never;
const choice = {
keepNoteId: 10,
deleteNoteId: 11,
deleteDuplicate: true,
cancelled: false,
};
registered.clearAnkiHistory!();
registered.respondFieldGrouping!(choice);
options.getSubtitleTimingTracker = originalGetTracker;
assert.equal(cleaned, 1);
assert.deepEqual(resolvedChoice, choice);
assert.equal(state.fieldGroupingResolver, null);
});
+1 -86
View File
@@ -1,19 +1,14 @@
import * as fs from 'fs';
import { AnkiIntegration } from '../../anki-integration';
import { mergeAiConfig } from '../../ai/config';
import type { AnkiIntegration } from '../../anki-integration';
import {
AiConfig,
TsukihimeApiResponse,
TsukihimeConfig,
AnkiConnectConfig,
JimakuApiResponse,
JimakuEntry,
JimakuFileEntry,
JimakuLanguagePreference,
JimakuMediaInfo,
KikuFieldGroupingChoice,
KikuFieldGroupingRequestData,
OverlayNotificationPayload,
} from '../../types';
import { sortJimakuFiles } from '../../jimaku/utils';
import {
@@ -35,41 +30,13 @@ interface MpvClientLike {
request?: (command: unknown[]) => Promise<{ data?: unknown }>;
}
interface RuntimeOptionsManagerLike {
getEffectiveAnkiConnectConfig: (config?: AnkiConnectConfig) => AnkiConnectConfig;
}
interface SubtitleTimingTrackerLike {
cleanup: () => void;
}
export interface AnkiJimakuIpcRuntimeOptions {
patchAnkiConnectEnabled: (enabled: boolean) => void;
getResolvedConfig: () => {
ankiConnect?: AnkiConnectConfig;
ai?: AiConfig;
tsukihime?: TsukihimeConfig;
secondarySub?: { secondarySubLanguages?: string[] };
};
getRuntimeOptionsManager: () => RuntimeOptionsManagerLike | null;
getSubtitleTimingTracker: () => SubtitleTimingTrackerLike | null;
getMpvClient: () => MpvClientLike | null;
getAnkiIntegration: () => AnkiIntegration | null;
setAnkiIntegration: (integration: AnkiIntegration | null) => void;
getKnownWordCacheStatePath: () => string;
getCachedMediaPath?: (
currentVideoPath: string,
kind: 'audio' | 'video',
) => Promise<string | null>;
shouldRequireRemoteMediaCache?: () => boolean;
getYoutubeMediaSourceUrl?: () => Promise<string | null | undefined> | string | null | undefined;
showDesktopNotification: (title: string, options: { body?: string; icon?: string }) => void;
showOverlayNotification?: (payload: OverlayNotificationPayload) => void;
dismissOverlayNotification?: (id: string) => void;
createFieldGroupingCallback: () => (
data: KikuFieldGroupingRequestData,
) => Promise<KikuFieldGroupingChoice>;
broadcastRuntimeOptionsChanged: () => void;
getFieldGroupingResolver: () => ((choice: KikuFieldGroupingChoice) => void) | null;
setFieldGroupingResolver: (resolver: ((choice: KikuFieldGroupingChoice) => void) | null) => void;
parseMediaInfo: (mediaPath: string | null) => JimakuMediaInfo;
@@ -135,58 +102,6 @@ export function registerAnkiJimakuIpcRuntime(
registerHandlers: RegisterAnkiJimakuIpcRuntimeHandler,
): void {
registerHandlers({
setAnkiConnectEnabled: (enabled) => {
options.patchAnkiConnectEnabled(enabled);
const config = options.getResolvedConfig();
const subtitleTimingTracker = options.getSubtitleTimingTracker();
const mpvClient = options.getMpvClient();
const ankiIntegration = options.getAnkiIntegration();
if (enabled && !ankiIntegration && subtitleTimingTracker && mpvClient) {
const runtimeOptionsManager = options.getRuntimeOptionsManager();
const effectiveAnkiConfig = runtimeOptionsManager
? runtimeOptionsManager.getEffectiveAnkiConnectConfig(config.ankiConnect)
: config.ankiConnect;
const integration = new AnkiIntegration(
effectiveAnkiConfig as never,
subtitleTimingTracker as never,
mpvClient as never,
(text: string) => {
if (mpvClient) {
mpvClient.send({
command: ['show-text', text, '3000'],
});
}
},
options.showDesktopNotification,
options.createFieldGroupingCallback(),
options.getKnownWordCacheStatePath(),
mergeAiConfig(config.ai, config.ankiConnect?.ai) as AiConfig,
undefined,
options.showOverlayNotification,
options.getCachedMediaPath,
options.shouldRequireRemoteMediaCache,
options.getYoutubeMediaSourceUrl,
options.dismissOverlayNotification,
);
integration.start();
options.setAnkiIntegration(integration);
logger.info('AnkiConnect integration enabled');
} else if (!enabled && ankiIntegration) {
ankiIntegration.destroy();
options.setAnkiIntegration(null);
logger.info('AnkiConnect integration disabled');
}
options.broadcastRuntimeOptionsChanged();
},
clearAnkiHistory: () => {
const subtitleTimingTracker = options.getSubtitleTimingTracker();
if (subtitleTimingTracker) {
subtitleTimingTracker.cleanup();
logger.info('AnkiConnect subtitle timing history cleared');
}
},
refreshKnownWords: async () => {
const integration = options.getAnkiIntegration();
if (!integration) {
-213
View File
@@ -114,23 +114,15 @@ function createSubtitleSidebarSnapshotFixture(): SubtitleSidebarSnapshot {
function createRegisterIpcDeps(overrides: Partial<IpcServiceDeps> = {}): IpcServiceDeps {
return {
onOverlayModalClosed: () => {},
openYomitanSettings: () => {},
quitApp: () => {},
toggleDevTools: () => {},
getVisibleOverlayVisibility: () => false,
toggleVisibleOverlay: () => {},
tokenizeCurrentSubtitle: async () => null,
getCurrentSubtitleRaw: () => '',
getCurrentSubtitleAss: () => '',
getSubtitleSidebarSnapshot: async () => createSubtitleSidebarSnapshotFixture(),
getPlaybackPaused: () => false,
getSubtitlePosition: () => null,
getSubtitleStyle: () => null,
saveSubtitlePosition: () => {},
getMecabStatus: () => ({ available: false, enabled: false, path: null }),
setMecabEnabled: () => {},
handleMpvCommand: () => {},
getKeybindings: () => [],
getSessionBindings: () => [],
getConfiguredShortcuts: () => ({}),
dispatchSessionAction: async () => {},
@@ -139,23 +131,19 @@ function createRegisterIpcDeps(overrides: Partial<IpcServiceDeps> = {}): IpcServ
getOverlayNotificationPosition: () => 'top-right',
getControllerConfig: () => createControllerConfigFixture(),
saveControllerConfig: async () => {},
saveControllerPreference: async () => {},
getSecondarySubMode: () => 'hover',
getCurrentSecondarySub: () => '',
focusMainWindow: () => {},
activatePlaybackWindowForOverlayInteraction: () => false,
runSubsyncManual: async () => ({ ok: true, message: 'ok' }),
getAnkiConnectStatus: () => false,
getRuntimeOptions: () => [],
setRuntimeOption: () => ({ ok: true }),
cycleRuntimeOption: () => ({ ok: true }),
reportOverlayContentBounds: () => {},
getAnilistStatus: () => ({}),
clearAnilistToken: () => {},
openAnilistSetup: () => {},
getAnilistQueueStatus: () => ({}),
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
getPlaylistBrowserSnapshot: async () => ({
directoryPath: null,
directoryAvailable: false,
@@ -312,26 +300,17 @@ test('registerIpcHandlers skips AniList update when manual mark watched has no a
test('registerIpcHandlers rejects malformed runtime-option payloads', async () => {
const { registrar, handlers } = createFakeIpcRegistrar();
const calls: Array<{ id: string; value: unknown }> = [];
const cycles: Array<{ id: string; direction: 1 | -1 }> = [];
registerIpcHandlers(
{
onOverlayModalClosed: () => {},
openYomitanSettings: () => {},
quitApp: () => {},
toggleDevTools: () => {},
getVisibleOverlayVisibility: () => false,
toggleVisibleOverlay: () => {},
tokenizeCurrentSubtitle: async () => null,
getCurrentSubtitleRaw: () => '',
getCurrentSubtitleAss: () => '',
getPlaybackPaused: () => null,
getSubtitlePosition: () => null,
getSubtitleStyle: () => null,
saveSubtitlePosition: () => {},
getMecabStatus: () => ({ available: false, enabled: false, path: null }),
setMecabEnabled: () => {},
handleMpvCommand: () => {},
getKeybindings: () => [],
getSessionBindings: () => [],
getConfiguredShortcuts: () => ({}),
dispatchSessionAction: async () => {},
@@ -340,21 +319,15 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
getOverlayNotificationPosition: () => 'top-right',
getControllerConfig: () => createControllerConfigFixture(),
saveControllerConfig: () => {},
saveControllerPreference: () => {},
getSecondarySubMode: () => 'hover',
getCurrentSecondarySub: () => '',
focusMainWindow: () => {},
runSubsyncManual: async () => ({ ok: true, message: 'ok' }),
getAnkiConnectStatus: () => false,
getRuntimeOptions: () => [],
setRuntimeOption: (id, value) => {
calls.push({ id, value });
return { ok: true };
},
cycleRuntimeOption: (id, direction) => {
cycles.push({ id, direction });
return { ok: true };
},
getSubtitleSidebarSnapshot: async () => createSubtitleSidebarSnapshotFixture(),
reportOverlayContentBounds: () => {},
getAnilistStatus: () => ({}),
@@ -362,7 +335,6 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
openAnilistSetup: () => {},
getAnilistQueueStatus: () => ({}),
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
getPlaylistBrowserSnapshot: async () => ({
directoryPath: null,
directoryAvailable: false,
@@ -399,16 +371,6 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
{ id: 'subtitle.annotation.jlpt', value: false },
]);
const cycleHandler = handlers.handle.get(IPC_CHANNELS.request.cycleRuntimeOption);
assert.ok(cycleHandler);
const invalidDirection = await cycleHandler!({}, 'anki.kikuFieldGrouping', 2);
assert.deepEqual(invalidDirection, {
ok: false,
error: 'Invalid runtime option cycle direction',
});
await cycleHandler!({}, 'anki.kikuFieldGrouping', -1);
assert.deepEqual(cycles, [{ id: 'anki.kikuFieldGrouping', direction: -1 }]);
const getPlaybackPausedHandler = handlers.handle.get(IPC_CHANNELS.request.getPlaybackPaused);
assert.ok(getPlaybackPausedHandler);
assert.equal(getPlaybackPausedHandler!({}), null);
@@ -609,7 +571,6 @@ test('registerIpcHandlers records yomitan lookup when subtitle context recording
test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
const { registrar, handlers } = createFakeIpcRegistrar();
const saves: unknown[] = [];
const controllerSaves: unknown[] = [];
const closedModals: unknown[] = [];
const openedModals: unknown[] = [];
registerIpcHandlers(
@@ -620,14 +581,9 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
onOverlayModalOpened: (modal) => {
openedModals.push(modal);
},
openYomitanSettings: () => {},
quitApp: () => {},
toggleDevTools: () => {},
getVisibleOverlayVisibility: () => false,
toggleVisibleOverlay: () => {},
tokenizeCurrentSubtitle: async () => null,
getCurrentSubtitleRaw: () => '',
getCurrentSubtitleAss: () => '',
getSubtitleSidebarSnapshot: async () => createSubtitleSidebarSnapshotFixture(),
getPlaybackPaused: () => false,
getSubtitlePosition: () => null,
@@ -635,10 +591,7 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
saveSubtitlePosition: (position) => {
saves.push(position);
},
getMecabStatus: () => ({ available: false, enabled: false, path: null }),
setMecabEnabled: () => {},
handleMpvCommand: () => {},
getKeybindings: () => [],
getSessionBindings: () => [],
getConfiguredShortcuts: () => ({}),
dispatchSessionAction: async () => {},
@@ -647,24 +600,18 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
getOverlayNotificationPosition: () => 'top-right',
getControllerConfig: () => createControllerConfigFixture(),
saveControllerConfig: () => {},
saveControllerPreference: (update) => {
controllerSaves.push(update);
},
getSecondarySubMode: () => 'hover',
getCurrentSecondarySub: () => '',
focusMainWindow: () => {},
runSubsyncManual: async () => ({ ok: true, message: 'ok' }),
getAnkiConnectStatus: () => false,
getRuntimeOptions: () => [],
setRuntimeOption: () => ({ ok: true }),
cycleRuntimeOption: () => ({ ok: true }),
reportOverlayContentBounds: () => {},
getAnilistStatus: () => ({}),
clearAnilistToken: () => {},
openAnilistSetup: () => {},
getAnilistQueueStatus: () => ({}),
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
getPlaylistBrowserSnapshot: async () => ({
directoryPath: null,
directoryAvailable: false,
@@ -698,96 +645,6 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
assert.deepEqual(openedModals, ['subsync', 'runtime-options']);
});
test('registerIpcHandlers awaits saveControllerPreference through request-response IPC', async () => {
const { registrar, handlers } = createFakeIpcRegistrar();
const controllerSaves: unknown[] = [];
registerIpcHandlers(
{
onOverlayModalClosed: () => {},
openYomitanSettings: () => {},
quitApp: () => {},
toggleDevTools: () => {},
getVisibleOverlayVisibility: () => false,
toggleVisibleOverlay: () => {},
tokenizeCurrentSubtitle: async () => null,
getCurrentSubtitleRaw: () => '',
getCurrentSubtitleAss: () => '',
getSubtitleSidebarSnapshot: async () => createSubtitleSidebarSnapshotFixture(),
getPlaybackPaused: () => false,
getSubtitlePosition: () => null,
getSubtitleStyle: () => null,
saveSubtitlePosition: () => {},
getMecabStatus: () => ({ available: false, enabled: false, path: null }),
setMecabEnabled: () => {},
handleMpvCommand: () => {},
getKeybindings: () => [],
getSessionBindings: () => [],
getConfiguredShortcuts: () => ({}),
dispatchSessionAction: async () => {},
getStatsToggleKey: () => 'Backquote',
getMarkWatchedKey: () => 'KeyW',
getOverlayNotificationPosition: () => 'top-right',
getControllerConfig: () => createControllerConfigFixture(),
saveControllerConfig: async () => {},
saveControllerPreference: async (update) => {
await Promise.resolve();
controllerSaves.push(update);
},
getSecondarySubMode: () => 'hover',
getCurrentSecondarySub: () => '',
focusMainWindow: () => {},
runSubsyncManual: async () => ({ ok: true, message: 'ok' }),
getAnkiConnectStatus: () => false,
getRuntimeOptions: () => [],
setRuntimeOption: () => ({ ok: true }),
cycleRuntimeOption: () => ({ ok: true }),
reportOverlayContentBounds: () => {},
getAnilistStatus: () => ({}),
clearAnilistToken: () => {},
openAnilistSetup: () => {},
getAnilistQueueStatus: () => ({}),
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
getPlaylistBrowserSnapshot: async () => ({
directoryPath: null,
directoryAvailable: false,
directoryStatus: '',
directoryItems: [],
playlistItems: [],
playingIndex: null,
currentFilePath: null,
}),
appendPlaylistBrowserFile: async () => ({ ok: true, message: 'ok' }),
playPlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
removePlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
movePlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
onYoutubePickerResolve: async () => ({ ok: true, message: 'ok' }),
},
registrar,
);
const saveHandler = handlers.handle.get(IPC_CHANNELS.command.saveControllerPreference);
assert.ok(saveHandler);
await assert.rejects(async () => {
await saveHandler!({}, { preferredGamepadId: 12 });
}, /Invalid controller preference payload/);
await saveHandler!(
{},
{
preferredGamepadId: 'pad-1',
preferredGamepadLabel: 'Pad 1',
},
);
assert.deepEqual(controllerSaves, [
{
preferredGamepadId: 'pad-1',
preferredGamepadLabel: 'Pad 1',
},
]);
});
test('registerIpcHandlers accepts per-controller profile config updates', async () => {
const { registrar, handlers } = createFakeIpcRegistrar();
const controllerSaves: unknown[] = [];
@@ -975,76 +832,6 @@ test('registerIpcHandlers forwards valid overlay notification actions', () => {
]);
});
test('registerIpcHandlers rejects malformed controller preference payloads', async () => {
const { registrar, handlers } = createFakeIpcRegistrar();
registerIpcHandlers(
{
onOverlayModalClosed: () => {},
openYomitanSettings: () => {},
quitApp: () => {},
toggleDevTools: () => {},
getVisibleOverlayVisibility: () => false,
toggleVisibleOverlay: () => {},
tokenizeCurrentSubtitle: async () => null,
getCurrentSubtitleRaw: () => '',
getCurrentSubtitleAss: () => '',
getSubtitleSidebarSnapshot: async () => createSubtitleSidebarSnapshotFixture(),
getPlaybackPaused: () => false,
getSubtitlePosition: () => null,
getSubtitleStyle: () => null,
saveSubtitlePosition: () => {},
getMecabStatus: () => ({ available: false, enabled: false, path: null }),
setMecabEnabled: () => {},
handleMpvCommand: () => {},
getKeybindings: () => [],
getSessionBindings: () => [],
getConfiguredShortcuts: () => ({}),
dispatchSessionAction: async () => {},
getStatsToggleKey: () => 'Backquote',
getMarkWatchedKey: () => 'KeyW',
getOverlayNotificationPosition: () => 'top-right',
getControllerConfig: () => createControllerConfigFixture(),
saveControllerConfig: async () => {},
saveControllerPreference: async () => {},
getSecondarySubMode: () => 'hover',
getCurrentSecondarySub: () => '',
focusMainWindow: () => {},
runSubsyncManual: async () => ({ ok: true, message: 'ok' }),
getAnkiConnectStatus: () => false,
getRuntimeOptions: () => [],
setRuntimeOption: () => ({ ok: true }),
cycleRuntimeOption: () => ({ ok: true }),
reportOverlayContentBounds: () => {},
getAnilistStatus: () => ({}),
clearAnilistToken: () => {},
openAnilistSetup: () => {},
getAnilistQueueStatus: () => ({}),
retryAnilistQueueNow: async () => ({ ok: true, message: 'ok' }),
appendClipboardVideoToQueue: () => ({ ok: true, message: 'ok' }),
getPlaylistBrowserSnapshot: async () => ({
directoryPath: null,
directoryAvailable: false,
directoryStatus: '',
directoryItems: [],
playlistItems: [],
playingIndex: null,
currentFilePath: null,
}),
appendPlaylistBrowserFile: async () => ({ ok: true, message: 'ok' }),
playPlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
removePlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
movePlaylistBrowserIndex: async () => ({ ok: true, message: 'ok' }),
onYoutubePickerResolve: async () => ({ ok: true, message: 'ok' }),
},
registrar,
);
const saveHandler = handlers.handle.get(IPC_CHANNELS.command.saveControllerPreference);
await assert.rejects(async () => {
await saveHandler!({}, { preferredGamepadId: 12 });
}, /Invalid controller preference payload/);
});
test('registerIpcHandlers exposes character dictionary selection handlers', async () => {
const { registrar, handlers } = createFakeIpcRegistrar();
const calls: number[] = [];
-124
View File
@@ -7,7 +7,6 @@ import type {
ControllerConfigUpdate,
PlaylistBrowserMutationResult,
PlaylistBrowserSnapshot,
ControllerPreferenceUpdate,
ResolvedControllerConfig,
RuntimeOptionId,
RuntimeOptionValue,
@@ -34,10 +33,8 @@ import { IPC_CHANNELS, type OverlayHostedModal } from '../../shared/ipc/contract
import {
parseMpvCommand,
parseControllerConfigUpdate,
parseControllerPreferenceUpdate,
parseOptionalForwardingOptions,
parseOverlayHostedModal,
parseRuntimeOptionDirection,
parseRuntimeOptionId,
parseRuntimeOptionValue,
parseSessionActionDispatchRequest,
@@ -71,14 +68,9 @@ export interface IpcServiceDeps {
actionId: string,
noteId?: number,
) => void | Promise<void>;
openYomitanSettings: () => void;
quitApp: () => void;
toggleDevTools: () => void;
getVisibleOverlayVisibility: () => boolean;
toggleVisibleOverlay: () => void;
tokenizeCurrentSubtitle: () => Promise<unknown>;
getCurrentSubtitleRaw: () => string;
getCurrentSubtitleAss: () => string;
getSubtitleSidebarSnapshot?: () => Promise<SubtitleSidebarSnapshot>;
getSubtitleSidebarPlaybackState?: () => SubtitleSidebarPlaybackState;
getSubtitleSidebarOpen?: () => boolean;
@@ -86,14 +78,7 @@ export interface IpcServiceDeps {
getSubtitlePosition: () => unknown;
getSubtitleStyle: () => unknown;
saveSubtitlePosition: (position: SubtitlePosition) => void;
getMecabStatus: () => {
available: boolean;
enabled: boolean;
path: string | null;
};
setMecabEnabled: (enabled: boolean) => void;
handleMpvCommand: (command: Array<string | number>) => void;
getKeybindings: () => unknown;
getMpvInputBindings?: () => Promise<MpvInputBindingsSnapshot>;
getSessionBindings?: () => CompiledSessionBinding[] | Promise<CompiledSessionBinding[]>;
getConfiguredShortcuts: () => unknown;
@@ -103,7 +88,6 @@ export interface IpcServiceDeps {
getOverlayNotificationPosition: () => string;
getControllerConfig: () => ResolvedControllerConfig;
saveControllerConfig: (update: ControllerConfigUpdate) => void | Promise<void>;
saveControllerPreference: (update: ControllerPreferenceUpdate) => void | Promise<void>;
getSecondarySubMode: () => unknown;
getCurrentSecondarySub: () => string;
focusMainWindow: () => void;
@@ -125,10 +109,8 @@ export interface IpcServiceDeps {
resolveMediaTimingReview?: (
request: MediaTimingReviewResolveRequest,
) => MediaTimingReviewActionResult | Promise<MediaTimingReviewActionResult>;
getAnkiConnectStatus: () => boolean;
getRuntimeOptions: () => unknown;
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
reportOverlayContentBounds: (
payload: unknown,
senderWindow: ElectronBrowserWindow | null,
@@ -149,7 +131,6 @@ export interface IpcServiceDeps {
getCharacterDictionaryManagerSnapshot?: () => Promise<unknown>;
removeCharacterDictionaryManagedEntry?: (mediaId: number) => Promise<unknown>;
moveCharacterDictionaryManagedEntry?: (mediaId: number, direction: 1 | -1) => Promise<unknown>;
appendClipboardVideoToQueue: () => { ok: boolean; message: string };
getChangelogSnapshot?: (options?: { refresh?: boolean }) => Promise<ChangelogSnapshot>;
getPlaylistBrowserSnapshot: () => Promise<PlaylistBrowserSnapshot>;
appendPlaylistBrowserFile: (filePath: string) => Promise<PlaylistBrowserMutationResult>;
@@ -174,15 +155,6 @@ interface WindowLike {
};
}
interface MecabTokenizerLike {
getStatus: () => {
available: boolean;
enabled: boolean;
path: string | null;
};
setEnabled: (enabled: boolean) => void;
}
interface MpvClientLike {
currentSecondarySubText?: string;
}
@@ -342,7 +314,6 @@ function parseMediaTimingReviewResolveRequest(
export interface IpcDepsRuntimeOptions {
getMainWindow: () => WindowLike | null;
getVisibleOverlayVisibility: () => boolean;
onOverlayModalClosed: (
modal: OverlayHostedModal,
senderWindow: ElectronBrowserWindow | null,
@@ -364,12 +335,8 @@ export interface IpcDepsRuntimeOptions {
actionId: string,
noteId?: number,
) => void | Promise<void>;
openYomitanSettings: () => void;
quitApp: () => void;
toggleVisibleOverlay: () => void;
tokenizeCurrentSubtitle: () => Promise<unknown>;
getCurrentSubtitleRaw: () => string;
getCurrentSubtitleAss: () => string;
getSubtitleSidebarSnapshot?: () => Promise<SubtitleSidebarSnapshot>;
getSubtitleSidebarPlaybackState?: () => SubtitleSidebarPlaybackState;
getSubtitleSidebarOpen?: () => boolean;
@@ -377,9 +344,7 @@ export interface IpcDepsRuntimeOptions {
getSubtitlePosition: () => unknown;
getSubtitleStyle: () => unknown;
saveSubtitlePosition: (position: SubtitlePosition) => void;
getMecabTokenizer: () => MecabTokenizerLike | null;
handleMpvCommand: (command: Array<string | number>) => void;
getKeybindings: () => unknown;
getMpvInputBindings?: () => Promise<MpvInputBindingsSnapshot>;
getSessionBindings?: () => CompiledSessionBinding[] | Promise<CompiledSessionBinding[]>;
getConfiguredShortcuts: () => unknown;
@@ -389,7 +354,6 @@ export interface IpcDepsRuntimeOptions {
getOverlayNotificationPosition: () => string;
getControllerConfig: () => ResolvedControllerConfig;
saveControllerConfig: (update: ControllerConfigUpdate) => void | Promise<void>;
saveControllerPreference: (update: ControllerPreferenceUpdate) => void | Promise<void>;
getSecondarySubMode: () => unknown;
getMpvClient: () => MpvClientLike | null;
focusMainWindow: () => void;
@@ -403,10 +367,8 @@ export interface IpcDepsRuntimeOptions {
getMediaTimingReviewWaveform?: IpcServiceDeps['getMediaTimingReviewWaveform'];
stopMediaTimingReviewPreview?: IpcServiceDeps['stopMediaTimingReviewPreview'];
resolveMediaTimingReview?: IpcServiceDeps['resolveMediaTimingReview'];
getAnkiConnectStatus: () => boolean;
getRuntimeOptions: () => unknown;
setRuntimeOption: (id: RuntimeOptionId, value: RuntimeOptionValue) => unknown;
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => unknown;
reportOverlayContentBounds: (payload: unknown) => void;
getAnilistStatus: () => unknown;
clearAnilistToken: () => void;
@@ -424,7 +386,6 @@ export interface IpcDepsRuntimeOptions {
getCharacterDictionaryManagerSnapshot?: () => Promise<unknown>;
removeCharacterDictionaryManagedEntry?: (mediaId: number) => Promise<unknown>;
moveCharacterDictionaryManagedEntry?: (mediaId: number, direction: 1 | -1) => Promise<unknown>;
appendClipboardVideoToQueue: () => { ok: boolean; message: string };
getChangelogSnapshot?: (options?: { refresh?: boolean }) => Promise<ChangelogSnapshot>;
getPlaylistBrowserSnapshot: () => Promise<PlaylistBrowserSnapshot>;
appendPlaylistBrowserFile: (filePath: string) => Promise<PlaylistBrowserMutationResult>;
@@ -444,19 +405,14 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
onOverlayMouseInteractionChanged: options.onOverlayMouseInteractionChanged,
onOverlayInteractiveHint: options.onOverlayInteractiveHint,
handleOverlayNotificationAction: options.handleOverlayNotificationAction,
openYomitanSettings: options.openYomitanSettings,
recordSubtitleMiningContext: options.recordSubtitleMiningContext,
quitApp: options.quitApp,
toggleDevTools: () => {
const mainWindow = options.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) return;
mainWindow.webContents.toggleDevTools();
},
getVisibleOverlayVisibility: options.getVisibleOverlayVisibility,
toggleVisibleOverlay: options.toggleVisibleOverlay,
tokenizeCurrentSubtitle: options.tokenizeCurrentSubtitle,
getCurrentSubtitleRaw: options.getCurrentSubtitleRaw,
getCurrentSubtitleAss: options.getCurrentSubtitleAss,
getSubtitleSidebarSnapshot: options.getSubtitleSidebarSnapshot,
getSubtitleSidebarPlaybackState: options.getSubtitleSidebarPlaybackState,
getSubtitleSidebarOpen: options.getSubtitleSidebarOpen ?? (() => false),
@@ -464,19 +420,7 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
getSubtitlePosition: options.getSubtitlePosition,
getSubtitleStyle: options.getSubtitleStyle,
saveSubtitlePosition: options.saveSubtitlePosition,
getMecabStatus: () => {
const mecabTokenizer = options.getMecabTokenizer();
return mecabTokenizer
? mecabTokenizer.getStatus()
: { available: false, enabled: false, path: null };
},
setMecabEnabled: (enabled) => {
const mecabTokenizer = options.getMecabTokenizer();
if (!mecabTokenizer) return;
mecabTokenizer.setEnabled(enabled);
},
handleMpvCommand: options.handleMpvCommand,
getKeybindings: options.getKeybindings,
getMpvInputBindings: options.getMpvInputBindings,
getSessionBindings: options.getSessionBindings ?? (() => []),
getConfiguredShortcuts: options.getConfiguredShortcuts,
@@ -486,7 +430,6 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
getOverlayNotificationPosition: options.getOverlayNotificationPosition,
getControllerConfig: options.getControllerConfig,
saveControllerConfig: options.saveControllerConfig,
saveControllerPreference: options.saveControllerPreference,
getSecondarySubMode: options.getSecondarySubMode,
getCurrentSecondarySub: () => options.getMpvClient()?.currentSecondarySubText || '',
focusMainWindow: () => {
@@ -503,10 +446,8 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
getMediaTimingReviewWaveform: options.getMediaTimingReviewWaveform,
stopMediaTimingReviewPreview: options.stopMediaTimingReviewPreview,
resolveMediaTimingReview: options.resolveMediaTimingReview,
getAnkiConnectStatus: options.getAnkiConnectStatus,
getRuntimeOptions: options.getRuntimeOptions,
setRuntimeOption: options.setRuntimeOption,
cycleRuntimeOption: options.cycleRuntimeOption,
reportOverlayContentBounds: (payload, senderWindow) => {
const mainWindow = options.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) return;
@@ -553,7 +494,6 @@ export function createIpcDepsRuntime(options: IpcDepsRuntimeOptions): IpcService
message: 'Character dictionary manager unavailable.',
entries: [],
})),
appendClipboardVideoToQueue: options.appendClipboardVideoToQueue,
getChangelogSnapshot: options.getChangelogSnapshot,
getPlaylistBrowserSnapshot: options.getPlaylistBrowserSnapshot,
appendPlaylistBrowserFile: options.appendPlaylistBrowserFile,
@@ -680,10 +620,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
},
);
ipc.on(IPC_CHANNELS.command.openYomitanSettings, () => {
deps.openYomitanSettings();
});
ipc.on(IPC_CHANNELS.command.recordYomitanLookup, (_event: unknown, payload: unknown) => {
try {
deps.recordSubtitleMiningContext?.(parseSubtitleMiningContext(payload));
@@ -711,22 +647,10 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
return marked;
});
ipc.on(IPC_CHANNELS.command.quitApp, () => {
deps.quitApp();
});
ipc.on(IPC_CHANNELS.command.toggleDevTools, () => {
deps.toggleDevTools();
});
ipc.on(IPC_CHANNELS.command.toggleOverlay, () => {
deps.toggleVisibleOverlay();
});
ipc.handle(IPC_CHANNELS.request.getVisibleOverlayVisibility, () => {
return deps.getVisibleOverlayVisibility();
});
ipc.handle(IPC_CHANNELS.request.getCurrentSubtitle, async () => {
return await deps.tokenizeCurrentSubtitle();
});
@@ -735,10 +659,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
return deps.getCurrentSubtitleRaw();
});
ipc.handle(IPC_CHANNELS.request.getCurrentSubtitleAss, () => {
return deps.getCurrentSubtitleAss();
});
ipc.handle(IPC_CHANNELS.request.getSubtitleSidebarSnapshot, async () => {
if (!deps.getSubtitleSidebarSnapshot) {
throw new Error('Subtitle sidebar snapshot is unavailable.');
@@ -775,17 +695,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
deps.saveSubtitlePosition(parsedPosition);
});
ipc.handle(
IPC_CHANNELS.command.saveControllerPreference,
async (_event: unknown, update: unknown) => {
const parsedUpdate = parseControllerPreferenceUpdate(update);
if (!parsedUpdate) {
throw new Error('Invalid controller preference payload');
}
await deps.saveControllerPreference(parsedUpdate);
},
);
ipc.handle(
IPC_CHANNELS.command.saveControllerConfig,
async (_event: unknown, update: unknown) => {
@@ -797,15 +706,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
},
);
ipc.handle(IPC_CHANNELS.request.getMecabStatus, () => {
return deps.getMecabStatus();
});
ipc.on(IPC_CHANNELS.command.setMecabEnabled, (_event: unknown, enabled: unknown) => {
if (typeof enabled !== 'boolean') return;
deps.setMecabEnabled(enabled);
});
ipc.on(IPC_CHANNELS.command.mpvCommand, (_event: unknown, command: unknown) => {
const parsedCommand = parseMpvCommand(command);
if (!parsedCommand) return;
@@ -823,10 +723,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
},
);
ipc.handle(IPC_CHANNELS.request.getKeybindings, () => {
return deps.getKeybindings();
});
ipc.handle(IPC_CHANNELS.request.getMpvInputBindings, () => {
return deps.getMpvInputBindings?.() ?? { keys: [], blockedKeys: [] };
});
@@ -879,10 +775,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
return await deps.runSubsyncManual(parsedRequest);
});
ipc.handle(IPC_CHANNELS.request.getAnkiConnectStatus, () => {
return deps.getAnkiConnectStatus();
});
ipc.handle(IPC_CHANNELS.request.getRuntimeOptions, () => {
return deps.getRuntimeOptions();
});
@@ -899,18 +791,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
return deps.setRuntimeOption(parsedId, parsedValue);
});
ipc.handle(IPC_CHANNELS.request.cycleRuntimeOption, (_event, id: unknown, direction: unknown) => {
const parsedId = parseRuntimeOptionId(id);
if (!parsedId) {
return { ok: false, error: 'Invalid runtime option id' };
}
const parsedDirection = parseRuntimeOptionDirection(direction);
if (!parsedDirection) {
return { ok: false, error: 'Invalid runtime option cycle direction' };
}
return deps.cycleRuntimeOption(parsedId, parsedDirection);
});
ipc.on(IPC_CHANNELS.command.reportOverlayContentBounds, (event: unknown, payload: unknown) => {
const senderWindow =
electron.BrowserWindow?.fromWebContents((event as IpcMainEvent).sender) ?? null;
@@ -1020,10 +900,6 @@ export function registerIpcHandlers(deps: IpcServiceDeps, ipc: IpcMainRegistrar
},
);
ipc.handle(IPC_CHANNELS.request.appendClipboardVideoToQueue, () => {
return deps.appendClipboardVideoToQueue();
});
ipc.handle(IPC_CHANNELS.request.getChangelogSnapshot, async (_event, payload: unknown) => {
const refresh =
typeof payload === 'object' && payload !== null && 'refresh' in payload
-6
View File
@@ -20,7 +20,6 @@ import {
MPV_REQUEST_ID_SUB_SHADOW_OFFSET,
MPV_REQUEST_ID_SUB_SPACING,
MPV_REQUEST_ID_SUBTEXT,
MPV_REQUEST_ID_SUBTEXT_ASS,
MPV_REQUEST_ID_SUB_USE_MARGINS,
MPV_REQUEST_ID_PAUSE,
} from './mpv-protocol';
@@ -54,7 +53,6 @@ const MPV_SUBTITLE_PROPERTY_OBSERVATIONS: string[] = [
'sub-scale-by-window',
'osd-height',
'osd-dimensions',
'sub-text/ass',
'sub-border-size',
'sub-shadow-offset',
'sub-ass-override',
@@ -76,10 +74,6 @@ const MPV_INITIAL_PROPERTY_REQUESTS: Array<MpvProtocolCommand> = [
command: ['get_property', 'sub-text'],
request_id: MPV_REQUEST_ID_SUBTEXT,
},
{
command: ['get_property', 'sub-text/ass'],
request_id: MPV_REQUEST_ID_SUBTEXT_ASS,
},
{
command: ['get_property', 'path'],
request_id: MPV_REQUEST_ID_PATH,
-23
View File
@@ -59,7 +59,6 @@ function createDeps(overrides: Partial<MpvProtocolHandleMessageDeps> = {}): {
getSubtitleMetrics: () => metrics,
isVisibleOverlayVisible: () => false,
emitSubtitleChange: (payload) => state.events.push(payload),
emitSubtitleAssChange: (payload) => state.events.push(payload),
emitSubtitleTiming: (payload) => state.events.push(payload),
emitSecondarySubtitleChange: (payload) => state.events.push(payload),
emitSubtitleTrackChange: (payload) => state.events.push(payload),
@@ -132,28 +131,6 @@ test('dispatchMpvProtocolMessage emits subtitle text on property change', async
assert.deepEqual(state.events, [{ text: '字幕', isOverlayVisible: false }]);
});
test('dispatchMpvProtocolMessage emits ASS subtitle text from the current mpv property', async () => {
const { deps, state } = createDeps();
await dispatchMpvProtocolMessage(
{ event: 'property-change', name: 'sub-text/ass', data: '{\\b1}字幕' },
deps,
);
assert.deepEqual(state.events, [{ text: '{\\b1}字幕' }]);
});
test('dispatchMpvProtocolMessage emits ASS subtitle text from the legacy mpv property', async () => {
const { deps, state } = createDeps();
await dispatchMpvProtocolMessage(
{ event: 'property-change', name: 'sub-text-ass', data: '{\\b1}字幕' },
deps,
);
assert.deepEqual(state.events, [{ text: '{\\b1}字幕' }]);
});
test('dispatchMpvProtocolMessage emits subtitle track changes', async () => {
const { deps, state } = createDeps({
emitSubtitleTrackChange: (payload) => state.events.push(payload),
-6
View File
@@ -24,7 +24,6 @@ export const MPV_REQUEST_ID_SUB_FONT = 111;
export const MPV_REQUEST_ID_SUB_SCALE_BY_WINDOW = 112;
export const MPV_REQUEST_ID_OSD_HEIGHT = 113;
export const MPV_REQUEST_ID_OSD_DIMENSIONS = 114;
export const MPV_REQUEST_ID_SUBTEXT_ASS = 115;
export const MPV_REQUEST_ID_SUB_SPACING = 116;
export const MPV_REQUEST_ID_SUB_BOLD = 117;
export const MPV_REQUEST_ID_SUB_ITALIC = 118;
@@ -52,7 +51,6 @@ export interface MpvProtocolHandleMessageDeps {
getSubtitleMetrics: () => MpvSubtitleRenderMetrics;
isVisibleOverlayVisible: () => boolean;
emitSubtitleChange: (payload: { text: string; isOverlayVisible: boolean }) => void;
emitSubtitleAssChange: (payload: { text: string }) => void;
emitSubtitleTiming: (payload: { text: string; start: number; end: number }) => void;
emitSecondarySubtitleChange: (payload: { text: string }) => void;
emitSubtitleTrackChange: (payload: { sid: number | null }) => void;
@@ -253,8 +251,6 @@ export async function dispatchMpvProtocolMessage(
isOverlayVisible: overlayVisible,
});
deps.setCurrentSubText(nextSubText);
} else if (msg.name === 'sub-text/ass' || msg.name === 'sub-text-ass') {
deps.emitSubtitleAssChange({ text: (msg.data as string) || '' });
} else if (msg.name === 'sub-start') {
deps.setCurrentSubStart((msg.data as number) || 0);
deps.emitSubtitleTiming({
@@ -468,8 +464,6 @@ export async function dispatchMpvProtocolMessage(
text: nextSubText,
isOverlayVisible: deps.isVisibleOverlayVisible(),
});
} else if (msg.request_id === MPV_REQUEST_ID_SUBTEXT_ASS) {
deps.emitSubtitleAssChange({ text: (msg.data as string) || '' });
} else if (msg.request_id === MPV_REQUEST_ID_PATH) {
deps.emitMediaPathChange({ path: (msg.data as string) || '' });
} else if (msg.request_id === MPV_REQUEST_ID_MEDIA_TITLE) {
-13
View File
@@ -444,17 +444,6 @@ test('MpvIpcClient reconnect replays property subscriptions and initial state re
(command as { command: unknown[] }).command[1] === 1 &&
(command as { command: unknown[] }).command[2] === 'sub-text',
);
const hasAssSubtitleSubscription = commands.some(
(command) =>
Array.isArray((command as { command: unknown[] }).command) &&
(command as { command: unknown[] }).command[0] === 'observe_property' &&
(command as { command: unknown[] }).command[2] === 'sub-text/ass',
);
const hasDeprecatedAssSubtitleProperty = commands.some(
(command) =>
Array.isArray((command as { command: unknown[] }).command) &&
(command as { command: unknown[] }).command.includes('sub-text-ass'),
);
const hasPathRequest = commands.some(
(command) =>
Array.isArray((command as { command: unknown[] }).command) &&
@@ -464,8 +453,6 @@ test('MpvIpcClient reconnect replays property subscriptions and initial state re
assert.equal(hasSecondaryVisibilityReset, true);
assert.equal(hasTrackSubscription, true);
assert.equal(hasAssSubtitleSubscription, true);
assert.equal(hasDeprecatedAssSubtitleProperty, false);
assert.equal(hasPathRequest, true);
});
-4
View File
@@ -123,7 +123,6 @@ export interface MpvIpcClientDeps extends MpvIpcClientProtocolDeps {}
export interface MpvIpcClientEventMap {
'connection-change': { connected: boolean };
'subtitle-change': { text: string; isOverlayVisible: boolean };
'subtitle-ass-change': { text: string };
'subtitle-timing': { text: string; start: number; end: number };
'time-pos-change': { time: number };
'duration-change': { duration: number };
@@ -417,9 +416,6 @@ export class MpvIpcClient implements MpvClient {
emitSubtitleChange: (payload) => {
this.emit('subtitle-change', payload);
},
emitSubtitleAssChange: (payload) => {
this.emit('subtitle-ass-change', payload);
},
emitSubtitleTiming: (payload) => {
this.emit('subtitle-timing', payload);
},
@@ -2,7 +2,6 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import {
applyRuntimeOptionResultRuntime,
cycleRuntimeOptionFromIpcRuntime,
setRuntimeOptionFromIpcRuntime,
} from './runtime-options-ipc';
@@ -25,21 +24,3 @@ test('setRuntimeOptionFromIpcRuntime returns unavailable when manager missing',
assert.equal(result.error, 'Runtime options manager unavailable');
assert.deepEqual(osd, []);
});
test('cycleRuntimeOptionFromIpcRuntime reports errors once', () => {
const osd: string[] = [];
const result = cycleRuntimeOptionFromIpcRuntime(
{
setOptionValue: () => ({ ok: true }),
cycleOption: () => ({ ok: false, error: 'bad option' }),
},
'anki.kikuFieldGrouping',
1,
(text) => {
osd.push(text);
},
);
assert.equal(result.ok, false);
assert.equal(result.error, 'bad option');
assert.deepEqual(osd, ['bad option']);
});
-16
View File
@@ -34,19 +34,3 @@ export function setRuntimeOptionFromIpcRuntime(
}
return result;
}
export function cycleRuntimeOptionFromIpcRuntime(
manager: RuntimeOptionsManagerLike | null,
id: RuntimeOptionId,
direction: 1 | -1,
showMpvOsd: (text: string) => void,
): RuntimeOptionApplyResult {
if (!manager) {
return { ok: false, error: 'Runtime options manager unavailable' };
}
const result = applyRuntimeOptionResultRuntime(manager.cycleOption(id, direction), showMpvOsd);
if (!result.ok && result.error) {
showMpvOsd(result.error);
}
return result;
}