mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-27 11:35:23 -07:00
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:
1 parent
8fc20b2499
commit
7909fcd2cf
32 files changed
+8
-876
No files matched your search
@@ -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)!(
|
||||
{},
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user