refactor: extract overlay shortcuts runtime for task 27.2

This commit is contained in:
2026-02-14 15:58:50 -08:00
parent 1fb8e2e168
commit 824443d93b
5 changed files with 565 additions and 339 deletions

View File

@@ -2,6 +2,99 @@ import { handleCliCommandService, createCliCommandDepsRuntimeService } from "../
import type { CliArgs, CliCommandSource } from "../cli/args";
import { createCliCommandRuntimeServiceDeps, CliCommandRuntimeServiceDepsParams } from "./dependencies";
export interface CliCommandRuntimeServiceContext {
getSocketPath: () => string;
setSocketPath: (socketPath: string) => void;
getClient: CliCommandRuntimeServiceDepsParams["mpv"]["getClient"];
showOsd: CliCommandRuntimeServiceDepsParams["mpv"]["showOsd"];
getTexthookerPort: () => number;
setTexthookerPort: (port: number) => void;
shouldOpenBrowser: () => boolean;
openInBrowser: (url: string) => void;
isOverlayInitialized: () => boolean;
initializeOverlay: () => void;
toggleVisibleOverlay: () => void;
toggleInvisibleOverlay: () => void;
setVisibleOverlay: (visible: boolean) => void;
setInvisibleOverlay: (visible: boolean) => void;
copyCurrentSubtitle: () => void;
startPendingMultiCopy: (timeoutMs: number) => void;
mineSentenceCard: () => Promise<void>;
startPendingMineSentenceMultiple: (timeoutMs: number) => void;
updateLastCardFromClipboard: () => Promise<void>;
triggerFieldGrouping: () => Promise<void>;
triggerSubsyncFromConfig: () => Promise<void>;
markLastCardAsAudioCard: () => Promise<void>;
openYomitanSettings: () => void;
cycleSecondarySubMode: () => void;
openRuntimeOptionsPalette: () => void;
printHelp: () => void;
stopApp: () => void;
hasMainWindow: () => boolean;
getMultiCopyTimeoutMs: () => number;
schedule: (fn: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
log: (message: string) => void;
warn: (message: string) => void;
error: (message: string, err: unknown) => void;
}
export interface CliCommandRuntimeServiceContextHandlers {
texthookerService: CliCommandRuntimeServiceDepsParams["texthooker"]["service"];
}
function createCliCommandDepsFromContext(
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): CliCommandRuntimeServiceDepsParams {
return {
mpv: {
getSocketPath: context.getSocketPath,
setSocketPath: context.setSocketPath,
getClient: context.getClient,
showOsd: context.showOsd,
},
texthooker: {
service: context.texthookerService,
getPort: context.getTexthookerPort,
setPort: context.setTexthookerPort,
shouldOpenBrowser: context.shouldOpenBrowser,
openInBrowser: context.openInBrowser,
},
overlay: {
isInitialized: context.isOverlayInitialized,
initialize: context.initializeOverlay,
toggleVisible: context.toggleVisibleOverlay,
toggleInvisible: context.toggleInvisibleOverlay,
setVisible: context.setVisibleOverlay,
setInvisible: context.setInvisibleOverlay,
},
mining: {
copyCurrentSubtitle: context.copyCurrentSubtitle,
startPendingMultiCopy: context.startPendingMultiCopy,
mineSentenceCard: context.mineSentenceCard,
startPendingMineSentenceMultiple: context.startPendingMineSentenceMultiple,
updateLastCardFromClipboard: context.updateLastCardFromClipboard,
triggerFieldGrouping: context.triggerFieldGrouping,
triggerSubsyncFromConfig: context.triggerSubsyncFromConfig,
markLastCardAsAudioCard: context.markLastCardAsAudioCard,
},
ui: {
openYomitanSettings: context.openYomitanSettings,
cycleSecondarySubMode: context.cycleSecondarySubMode,
openRuntimeOptionsPalette: context.openRuntimeOptionsPalette,
printHelp: context.printHelp,
},
app: {
stop: context.stopApp,
hasMainWindow: context.hasMainWindow,
},
getMultiCopyTimeoutMs: context.getMultiCopyTimeoutMs,
schedule: context.schedule,
log: context.log,
warn: context.warn,
error: context.error,
};
}
export function handleCliCommandRuntimeService(
args: CliArgs,
source: CliCommandSource,
@@ -13,3 +106,10 @@ export function handleCliCommandRuntimeService(
handleCliCommandService(args, source, deps);
}
export function handleCliCommandRuntimeServiceWithContext(
args: CliArgs,
source: CliCommandSource,
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): void {
handleCliCommandRuntimeService(args, source, createCliCommandDepsFromContext(context));
}

View File

@@ -0,0 +1,138 @@
import type { ConfiguredShortcuts } from "../core/utils/shortcut-config";
import {
createOverlayShortcutRuntimeHandlers,
shortcutMatchesInputForLocalFallback,
} from "../core/services";
import {
refreshOverlayShortcutsRuntimeService,
registerOverlayShortcutsService,
syncOverlayShortcutsRuntimeService,
unregisterOverlayShortcutsRuntimeService,
} from "../core/services";
import { runOverlayShortcutLocalFallback } from "../core/services/overlay-shortcut-handler";
export interface OverlayShortcutRuntimeServiceInput {
getConfiguredShortcuts: () => ConfiguredShortcuts;
getShortcutsRegistered: () => boolean;
setShortcutsRegistered: (registered: boolean) => void;
isOverlayRuntimeInitialized: () => boolean;
showMpvOsd: (text: string) => void;
openRuntimeOptionsPalette: () => void;
openJimaku: () => void;
markAudioCard: () => Promise<void>;
copySubtitleMultiple: (timeoutMs: number) => void;
copySubtitle: () => void;
toggleSecondarySubMode: () => void;
updateLastCardFromClipboard: () => Promise<void>;
triggerFieldGrouping: () => Promise<void>;
triggerSubsyncFromConfig: () => Promise<void>;
mineSentenceCard: () => Promise<void>;
mineSentenceMultiple: (timeoutMs: number) => void;
cancelPendingMultiCopy: () => void;
cancelPendingMineSentenceMultiple: () => void;
}
export interface OverlayShortcutsRuntimeService {
tryHandleOverlayShortcutLocalFallback: (input: Electron.Input) => boolean;
registerOverlayShortcuts: () => void;
unregisterOverlayShortcuts: () => void;
syncOverlayShortcuts: () => void;
refreshOverlayShortcuts: () => void;
}
export function createOverlayShortcutsRuntimeService(
input: OverlayShortcutRuntimeServiceInput,
): OverlayShortcutsRuntimeService {
const handlers = createOverlayShortcutRuntimeHandlers({
showMpvOsd: (text: string) => input.showMpvOsd(text),
openRuntimeOptions: () => {
input.openRuntimeOptionsPalette();
},
openJimaku: () => {
input.openJimaku();
},
markAudioCard: () => {
return input.markAudioCard();
},
copySubtitleMultiple: (timeoutMs: number) => {
input.copySubtitleMultiple(timeoutMs);
},
copySubtitle: () => {
input.copySubtitle();
},
toggleSecondarySub: () => {
input.toggleSecondarySubMode();
},
updateLastCardFromClipboard: () => {
return input.updateLastCardFromClipboard();
},
triggerFieldGrouping: () => {
return input.triggerFieldGrouping();
},
triggerSubsync: () => {
return input.triggerSubsyncFromConfig();
},
mineSentence: () => {
return input.mineSentenceCard();
},
mineSentenceMultiple: (timeoutMs: number) => {
input.mineSentenceMultiple(timeoutMs);
},
});
const getShortcutLifecycleDeps = () => {
return {
getConfiguredShortcuts: () => input.getConfiguredShortcuts(),
getOverlayHandlers: () => handlers.overlayHandlers,
cancelPendingMultiCopy: () => input.cancelPendingMultiCopy(),
cancelPendingMineSentenceMultiple: () =>
input.cancelPendingMineSentenceMultiple(),
};
};
const shouldOverlayShortcutsBeActive = () => input.isOverlayRuntimeInitialized();
return {
tryHandleOverlayShortcutLocalFallback: (inputEvent) =>
runOverlayShortcutLocalFallback(
inputEvent,
input.getConfiguredShortcuts(),
shortcutMatchesInputForLocalFallback,
handlers.fallbackHandlers,
),
registerOverlayShortcuts: () => {
input.setShortcutsRegistered(
registerOverlayShortcutsService(
input.getConfiguredShortcuts(),
handlers.overlayHandlers,
),
);
},
unregisterOverlayShortcuts: () => {
input.setShortcutsRegistered(
unregisterOverlayShortcutsRuntimeService(
input.getShortcutsRegistered(),
getShortcutLifecycleDeps(),
),
);
},
syncOverlayShortcuts: () => {
input.setShortcutsRegistered(
syncOverlayShortcutsRuntimeService(
shouldOverlayShortcutsBeActive(),
input.getShortcutsRegistered(),
getShortcutLifecycleDeps(),
),
);
},
refreshOverlayShortcuts: () => {
input.setShortcutsRegistered(
refreshOverlayShortcutsRuntimeService(
shouldOverlayShortcutsBeActive(),
input.getShortcutsRegistered(),
getShortcutLifecycleDeps(),
),
);
},
};
}