mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor: extract reusable numeric shortcut session runtime
This commit is contained in:
99
src/core/services/numeric-shortcut-session-service.ts
Normal file
99
src/core/services/numeric-shortcut-session-service.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
export interface NumericShortcutSessionMessages {
|
||||||
|
prompt: string;
|
||||||
|
timeout: string;
|
||||||
|
cancelled?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumericShortcutSessionDeps {
|
||||||
|
registerShortcut: (accelerator: string, handler: () => void) => boolean;
|
||||||
|
unregisterShortcut: (accelerator: string) => void;
|
||||||
|
setTimer: (handler: () => void, timeoutMs: number) => ReturnType<typeof setTimeout>;
|
||||||
|
clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
|
||||||
|
showMpvOsd: (text: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumericShortcutSessionStartParams {
|
||||||
|
timeoutMs: number;
|
||||||
|
onDigit: (digit: number) => void;
|
||||||
|
messages: NumericShortcutSessionMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNumericShortcutSessionService(
|
||||||
|
deps: NumericShortcutSessionDeps,
|
||||||
|
) {
|
||||||
|
let active = false;
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let digitShortcuts: string[] = [];
|
||||||
|
let escapeShortcut: string | null = null;
|
||||||
|
|
||||||
|
let cancelledMessage = "Cancelled";
|
||||||
|
|
||||||
|
const cancel = (showCancelled = false): void => {
|
||||||
|
if (!active) return;
|
||||||
|
active = false;
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
deps.clearTimer(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const shortcut of digitShortcuts) {
|
||||||
|
deps.unregisterShortcut(shortcut);
|
||||||
|
}
|
||||||
|
digitShortcuts = [];
|
||||||
|
|
||||||
|
if (escapeShortcut) {
|
||||||
|
deps.unregisterShortcut(escapeShortcut);
|
||||||
|
escapeShortcut = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showCancelled) {
|
||||||
|
deps.showMpvOsd(cancelledMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = ({
|
||||||
|
timeoutMs,
|
||||||
|
onDigit,
|
||||||
|
messages,
|
||||||
|
}: NumericShortcutSessionStartParams): void => {
|
||||||
|
cancel();
|
||||||
|
cancelledMessage = messages.cancelled ?? "Cancelled";
|
||||||
|
active = true;
|
||||||
|
|
||||||
|
for (let i = 1; i <= 9; i++) {
|
||||||
|
const shortcut = i.toString();
|
||||||
|
if (
|
||||||
|
deps.registerShortcut(shortcut, () => {
|
||||||
|
if (!active) return;
|
||||||
|
cancel();
|
||||||
|
onDigit(i);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
digitShortcuts.push(shortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
deps.registerShortcut("Escape", () => {
|
||||||
|
cancel(true);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
escapeShortcut = "Escape";
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = deps.setTimer(() => {
|
||||||
|
if (!active) return;
|
||||||
|
cancel();
|
||||||
|
deps.showMpvOsd(messages.timeout);
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
deps.showMpvOsd(messages.prompt);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
cancel,
|
||||||
|
isActive: (): boolean => active,
|
||||||
|
};
|
||||||
|
}
|
||||||
152
src/main.ts
152
src/main.ts
@@ -116,6 +116,7 @@ import {
|
|||||||
} from "./core/services/overlay-shortcut-service";
|
} from "./core/services/overlay-shortcut-service";
|
||||||
import { runOverlayShortcutLocalFallback } from "./core/services/overlay-shortcut-fallback-runner";
|
import { runOverlayShortcutLocalFallback } from "./core/services/overlay-shortcut-fallback-runner";
|
||||||
import { createOverlayShortcutRuntimeHandlers } from "./core/services/overlay-shortcut-runtime-service";
|
import { createOverlayShortcutRuntimeHandlers } from "./core/services/overlay-shortcut-runtime-service";
|
||||||
|
import { createNumericShortcutSessionService } from "./core/services/numeric-shortcut-session-service";
|
||||||
import { showDesktopNotification } from "./core/utils/notification";
|
import { showDesktopNotification } from "./core/utils/notification";
|
||||||
import { openYomitanSettingsWindow } from "./core/services/yomitan-settings-service";
|
import { openYomitanSettingsWindow } from "./core/services/yomitan-settings-service";
|
||||||
import { tokenizeSubtitleService } from "./core/services/tokenizer-service";
|
import { tokenizeSubtitleService } from "./core/services/tokenizer-service";
|
||||||
@@ -250,16 +251,7 @@ let mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let shortcutsRegistered = false;
|
let shortcutsRegistered = false;
|
||||||
let pendingMultiCopy = false;
|
|
||||||
let pendingMultiCopyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
let multiCopyDigitShortcuts: string[] = [];
|
|
||||||
let multiCopyEscapeShortcut: string | null = null;
|
|
||||||
let pendingMineSentenceMultiple = false;
|
|
||||||
let pendingMineSentenceMultipleTimeout: ReturnType<typeof setTimeout> | null =
|
|
||||||
null;
|
|
||||||
let overlayRuntimeInitialized = false;
|
let overlayRuntimeInitialized = false;
|
||||||
let mineSentenceDigitShortcuts: string[] = [];
|
|
||||||
let mineSentenceEscapeShortcut: string | null = null;
|
|
||||||
let fieldGroupingResolver: ((choice: KikuFieldGroupingChoice) => void) | null =
|
let fieldGroupingResolver: ((choice: KikuFieldGroupingChoice) => void) | null =
|
||||||
null;
|
null;
|
||||||
let runtimeOptionsManager: RuntimeOptionsManager | null = null;
|
let runtimeOptionsManager: RuntimeOptionsManager | null = null;
|
||||||
@@ -964,6 +956,24 @@ function showMpvOsd(text: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const multiCopySession = createNumericShortcutSessionService({
|
||||||
|
registerShortcut: (accelerator, handler) =>
|
||||||
|
globalShortcut.register(accelerator, handler),
|
||||||
|
unregisterShortcut: (accelerator) => globalShortcut.unregister(accelerator),
|
||||||
|
setTimer: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
||||||
|
clearTimer: (timer) => clearTimeout(timer),
|
||||||
|
showMpvOsd: (text) => showMpvOsd(text),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mineSentenceSession = createNumericShortcutSessionService({
|
||||||
|
registerShortcut: (accelerator, handler) =>
|
||||||
|
globalShortcut.register(accelerator, handler),
|
||||||
|
unregisterShortcut: (accelerator) => globalShortcut.unregister(accelerator),
|
||||||
|
setTimer: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
||||||
|
clearTimer: (timer) => clearTimeout(timer),
|
||||||
|
showMpvOsd: (text) => showMpvOsd(text),
|
||||||
|
});
|
||||||
|
|
||||||
function getSubsyncServiceDeps() {
|
function getSubsyncServiceDeps() {
|
||||||
return {
|
return {
|
||||||
getMpvClient: () => mpvClient,
|
getMpvClient: () => mpvClient,
|
||||||
@@ -988,61 +998,23 @@ async function triggerSubsyncFromConfig(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelPendingMultiCopy(): void {
|
function cancelPendingMultiCopy(): void {
|
||||||
if (!pendingMultiCopy) return;
|
multiCopySession.cancel();
|
||||||
|
|
||||||
pendingMultiCopy = false;
|
|
||||||
if (pendingMultiCopyTimeout) {
|
|
||||||
clearTimeout(pendingMultiCopyTimeout);
|
|
||||||
pendingMultiCopyTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const shortcut of multiCopyDigitShortcuts) {
|
|
||||||
globalShortcut.unregister(shortcut);
|
|
||||||
}
|
|
||||||
multiCopyDigitShortcuts = [];
|
|
||||||
|
|
||||||
if (multiCopyEscapeShortcut) {
|
|
||||||
globalShortcut.unregister(multiCopyEscapeShortcut);
|
|
||||||
multiCopyEscapeShortcut = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPendingMultiCopy(timeoutMs: number): void {
|
function startPendingMultiCopy(timeoutMs: number): void {
|
||||||
cancelPendingMultiCopy();
|
multiCopySession.start({
|
||||||
pendingMultiCopy = true;
|
timeoutMs,
|
||||||
|
onDigit: (count) => handleMultiCopyDigit(count),
|
||||||
for (let i = 1; i <= 9; i++) {
|
messages: {
|
||||||
const shortcut = i.toString();
|
prompt: "Copy how many lines? Press 1-9 (Esc to cancel)",
|
||||||
if (
|
timeout: "Copy timeout",
|
||||||
globalShortcut.register(shortcut, () => {
|
cancelled: "Cancelled",
|
||||||
handleMultiCopyDigit(i);
|
},
|
||||||
})
|
});
|
||||||
) {
|
|
||||||
multiCopyDigitShortcuts.push(shortcut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
globalShortcut.register("Escape", () => {
|
|
||||||
cancelPendingMultiCopy();
|
|
||||||
showMpvOsd("Cancelled");
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
multiCopyEscapeShortcut = "Escape";
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingMultiCopyTimeout = setTimeout(() => {
|
|
||||||
cancelPendingMultiCopy();
|
|
||||||
showMpvOsd("Copy timeout");
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
showMpvOsd("Copy how many lines? Press 1-9 (Esc to cancel)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMultiCopyDigit(count: number): void {
|
function handleMultiCopyDigit(count: number): void {
|
||||||
if (!pendingMultiCopy || !subtitleTimingTracker) return;
|
if (!subtitleTimingTracker) return;
|
||||||
|
|
||||||
cancelPendingMultiCopy();
|
|
||||||
|
|
||||||
const availableCount = Math.min(count, 200); // Max history size
|
const availableCount = Math.min(count, 200); // Max history size
|
||||||
const blocks = subtitleTimingTracker.getRecentBlocks(availableCount);
|
const blocks = subtitleTimingTracker.getRecentBlocks(availableCount);
|
||||||
@@ -1135,67 +1107,25 @@ async function mineSentenceCard(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelPendingMineSentenceMultiple(): void {
|
function cancelPendingMineSentenceMultiple(): void {
|
||||||
if (!pendingMineSentenceMultiple) return;
|
mineSentenceSession.cancel();
|
||||||
|
|
||||||
pendingMineSentenceMultiple = false;
|
|
||||||
if (pendingMineSentenceMultipleTimeout) {
|
|
||||||
clearTimeout(pendingMineSentenceMultipleTimeout);
|
|
||||||
pendingMineSentenceMultipleTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const shortcut of mineSentenceDigitShortcuts) {
|
|
||||||
globalShortcut.unregister(shortcut);
|
|
||||||
}
|
|
||||||
mineSentenceDigitShortcuts = [];
|
|
||||||
|
|
||||||
if (mineSentenceEscapeShortcut) {
|
|
||||||
globalShortcut.unregister(mineSentenceEscapeShortcut);
|
|
||||||
mineSentenceEscapeShortcut = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPendingMineSentenceMultiple(timeoutMs: number): void {
|
function startPendingMineSentenceMultiple(timeoutMs: number): void {
|
||||||
cancelPendingMineSentenceMultiple();
|
mineSentenceSession.start({
|
||||||
pendingMineSentenceMultiple = true;
|
timeoutMs,
|
||||||
|
onDigit: (count) => handleMineSentenceDigit(count),
|
||||||
for (let i = 1; i <= 9; i++) {
|
messages: {
|
||||||
const shortcut = i.toString();
|
prompt: "Mine how many lines? Press 1-9 (Esc to cancel)",
|
||||||
if (
|
timeout: "Mine sentence timeout",
|
||||||
globalShortcut.register(shortcut, () => {
|
cancelled: "Cancelled",
|
||||||
handleMineSentenceDigit(i);
|
},
|
||||||
})
|
});
|
||||||
) {
|
|
||||||
mineSentenceDigitShortcuts.push(shortcut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
globalShortcut.register("Escape", () => {
|
|
||||||
cancelPendingMineSentenceMultiple();
|
|
||||||
showMpvOsd("Cancelled");
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
mineSentenceEscapeShortcut = "Escape";
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingMineSentenceMultipleTimeout = setTimeout(() => {
|
|
||||||
cancelPendingMineSentenceMultiple();
|
|
||||||
showMpvOsd("Mine sentence timeout");
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
showMpvOsd("Mine how many lines? Press 1-9 (Esc to cancel)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMineSentenceDigit(count: number): void {
|
function handleMineSentenceDigit(count: number): void {
|
||||||
if (
|
if (!subtitleTimingTracker || !ankiIntegration)
|
||||||
!pendingMineSentenceMultiple ||
|
|
||||||
!subtitleTimingTracker ||
|
|
||||||
!ankiIntegration
|
|
||||||
)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cancelPendingMineSentenceMultiple();
|
|
||||||
|
|
||||||
const blocks = subtitleTimingTracker.getRecentBlocks(count);
|
const blocks = subtitleTimingTracker.getRecentBlocks(count);
|
||||||
|
|
||||||
if (blocks.length === 0) {
|
if (blocks.length === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user