Files
SubMiner/src/core/services/overlay-runtime-init.ts
2026-02-17 22:54:09 -08:00

108 lines
3.9 KiB
TypeScript

import { BrowserWindow } from 'electron';
import { AnkiIntegration } from '../../anki-integration';
import { BaseWindowTracker, createWindowTracker } from '../../window-trackers';
import {
AnkiConnectConfig,
KikuFieldGroupingChoice,
KikuFieldGroupingRequestData,
WindowGeometry,
} from '../../types';
export function initializeOverlayRuntime(options: {
backendOverride: string | null;
getInitialInvisibleOverlayVisibility: () => boolean;
createMainWindow: () => void;
createInvisibleWindow: () => void;
registerGlobalShortcuts: () => void;
updateVisibleOverlayBounds: (geometry: WindowGeometry) => void;
updateInvisibleOverlayBounds: (geometry: WindowGeometry) => void;
isVisibleOverlayVisible: () => boolean;
isInvisibleOverlayVisible: () => boolean;
updateVisibleOverlayVisibility: () => void;
updateInvisibleOverlayVisibility: () => void;
getOverlayWindows: () => BrowserWindow[];
syncOverlayShortcuts: () => void;
setWindowTracker: (tracker: BaseWindowTracker | null) => void;
getMpvSocketPath: () => string;
getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig };
getSubtitleTimingTracker: () => unknown | null;
getMpvClient: () => {
send?: (payload: { command: string[] }) => void;
} | null;
getRuntimeOptionsManager: () => {
getEffectiveAnkiConnectConfig: (config?: AnkiConnectConfig) => AnkiConnectConfig;
} | null;
setAnkiIntegration: (integration: unknown | null) => void;
showDesktopNotification: (title: string, options: { body?: string; icon?: string }) => void;
createFieldGroupingCallback: () => (
data: KikuFieldGroupingRequestData,
) => Promise<KikuFieldGroupingChoice>;
getKnownWordCacheStatePath: () => string;
}): {
invisibleOverlayVisible: boolean;
} {
options.createMainWindow();
options.createInvisibleWindow();
const invisibleOverlayVisible = options.getInitialInvisibleOverlayVisibility();
options.registerGlobalShortcuts();
const windowTracker = createWindowTracker(options.backendOverride, options.getMpvSocketPath());
options.setWindowTracker(windowTracker);
if (windowTracker) {
windowTracker.onGeometryChange = (geometry: WindowGeometry) => {
options.updateVisibleOverlayBounds(geometry);
options.updateInvisibleOverlayBounds(geometry);
};
windowTracker.onWindowFound = (geometry: WindowGeometry) => {
options.updateVisibleOverlayBounds(geometry);
options.updateInvisibleOverlayBounds(geometry);
if (options.isVisibleOverlayVisible()) {
options.updateVisibleOverlayVisibility();
}
if (options.isInvisibleOverlayVisible()) {
options.updateInvisibleOverlayVisibility();
}
};
windowTracker.onWindowLost = () => {
for (const window of options.getOverlayWindows()) {
window.hide();
}
options.syncOverlayShortcuts();
};
windowTracker.start();
}
const config = options.getResolvedConfig();
const subtitleTimingTracker = options.getSubtitleTimingTracker();
const mpvClient = options.getMpvClient();
const runtimeOptionsManager = options.getRuntimeOptionsManager();
if (config.ankiConnect && subtitleTimingTracker && mpvClient && runtimeOptionsManager) {
const effectiveAnkiConfig = runtimeOptionsManager.getEffectiveAnkiConnectConfig(
config.ankiConnect,
);
const integration = new AnkiIntegration(
effectiveAnkiConfig,
subtitleTimingTracker as never,
mpvClient as never,
(text: string) => {
if (mpvClient && typeof mpvClient.send === 'function') {
mpvClient.send({
command: ['show-text', text, '3000'],
});
}
},
options.showDesktopNotification,
options.createFieldGroupingCallback(),
options.getKnownWordCacheStatePath(),
);
integration.start();
options.setAnkiIntegration(integration);
}
options.updateVisibleOverlayVisibility();
options.updateInvisibleOverlayVisibility();
return { invisibleOverlayVisible };
}