mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-02 06:22:42 -08:00
feat(core): add Electron runtime, services, and app composition
This commit is contained in:
283
src/main/state.ts
Normal file
283
src/main/state.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import type { BrowserWindow, Extension } from 'electron';
|
||||
|
||||
import type {
|
||||
Keybinding,
|
||||
MpvSubtitleRenderMetrics,
|
||||
SecondarySubMode,
|
||||
SubtitleData,
|
||||
SubtitlePosition,
|
||||
KikuFieldGroupingChoice,
|
||||
JlptLevel,
|
||||
FrequencyDictionaryLookup,
|
||||
} from '../types';
|
||||
import type { CliArgs } from '../cli/args';
|
||||
import type { SubtitleTimingTracker } from '../subtitle-timing-tracker';
|
||||
import type { AnkiIntegration } from '../anki-integration';
|
||||
import type { ImmersionTrackerService } from '../core/services/immersion-tracker-service';
|
||||
import type { MpvIpcClient } from '../core/services/mpv';
|
||||
import type { JellyfinRemoteSessionService } from '../core/services/jellyfin-remote';
|
||||
import type { createDiscordPresenceService } from '../core/services/discord-presence';
|
||||
import { DEFAULT_MPV_SUBTITLE_RENDER_METRICS } from '../core/services/mpv-render-metrics';
|
||||
import type { RuntimeOptionsManager } from '../runtime-options';
|
||||
import type { MecabTokenizer } from '../mecab-tokenizer';
|
||||
import type { BaseWindowTracker } from '../window-trackers';
|
||||
import type { AnilistMediaGuess } from '../core/services/anilist/anilist-updater';
|
||||
|
||||
export interface AnilistSecretResolutionState {
|
||||
status: 'not_checked' | 'resolved' | 'error';
|
||||
source: 'none' | 'literal' | 'stored';
|
||||
message: string | null;
|
||||
resolvedAt: number | null;
|
||||
errorAt: number | null;
|
||||
}
|
||||
|
||||
export interface AnilistRetryQueueState {
|
||||
pending: number;
|
||||
ready: number;
|
||||
deadLetter: number;
|
||||
lastAttemptAt: number | null;
|
||||
lastError: string | null;
|
||||
}
|
||||
|
||||
export interface AnilistMediaGuessRuntimeState {
|
||||
mediaKey: string | null;
|
||||
mediaDurationSec: number | null;
|
||||
mediaGuess: AnilistMediaGuess | null;
|
||||
mediaGuessPromise: Promise<AnilistMediaGuess | null> | null;
|
||||
lastDurationProbeAtMs: number;
|
||||
}
|
||||
|
||||
export interface AnilistUpdateInFlightState {
|
||||
inFlight: boolean;
|
||||
}
|
||||
|
||||
export function createInitialAnilistSecretResolutionState(): AnilistSecretResolutionState {
|
||||
return {
|
||||
status: 'not_checked',
|
||||
source: 'none',
|
||||
message: null,
|
||||
resolvedAt: null,
|
||||
errorAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createInitialAnilistRetryQueueState(): AnilistRetryQueueState {
|
||||
return {
|
||||
pending: 0,
|
||||
ready: 0,
|
||||
deadLetter: 0,
|
||||
lastAttemptAt: null,
|
||||
lastError: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createInitialAnilistMediaGuessRuntimeState(): AnilistMediaGuessRuntimeState {
|
||||
return {
|
||||
mediaKey: null,
|
||||
mediaDurationSec: null,
|
||||
mediaGuess: null,
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function createInitialAnilistUpdateInFlightState(): AnilistUpdateInFlightState {
|
||||
return {
|
||||
inFlight: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function transitionAnilistClientSecretState(
|
||||
_current: AnilistSecretResolutionState,
|
||||
next: AnilistSecretResolutionState,
|
||||
): AnilistSecretResolutionState {
|
||||
return next;
|
||||
}
|
||||
|
||||
export function transitionAnilistRetryQueueState(
|
||||
_current: AnilistRetryQueueState,
|
||||
next: AnilistRetryQueueState,
|
||||
): AnilistRetryQueueState {
|
||||
return next;
|
||||
}
|
||||
|
||||
export function transitionAnilistRetryQueueLastAttemptAt(
|
||||
current: AnilistRetryQueueState,
|
||||
lastAttemptAt: number | null,
|
||||
): AnilistRetryQueueState {
|
||||
return {
|
||||
...current,
|
||||
lastAttemptAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function transitionAnilistRetryQueueLastError(
|
||||
current: AnilistRetryQueueState,
|
||||
lastError: string | null,
|
||||
): AnilistRetryQueueState {
|
||||
return {
|
||||
...current,
|
||||
lastError,
|
||||
};
|
||||
}
|
||||
|
||||
export function transitionAnilistMediaGuessRuntimeState(
|
||||
current: AnilistMediaGuessRuntimeState,
|
||||
partial: Partial<AnilistMediaGuessRuntimeState>,
|
||||
): AnilistMediaGuessRuntimeState {
|
||||
return {
|
||||
...current,
|
||||
...partial,
|
||||
};
|
||||
}
|
||||
|
||||
export function transitionAnilistUpdateInFlightState(
|
||||
current: AnilistUpdateInFlightState,
|
||||
inFlight: boolean,
|
||||
): AnilistUpdateInFlightState {
|
||||
return {
|
||||
...current,
|
||||
inFlight,
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
yomitanExt: Extension | null;
|
||||
yomitanSettingsWindow: BrowserWindow | null;
|
||||
yomitanParserWindow: BrowserWindow | null;
|
||||
anilistSetupWindow: BrowserWindow | null;
|
||||
jellyfinSetupWindow: BrowserWindow | null;
|
||||
yomitanParserReadyPromise: Promise<void> | null;
|
||||
yomitanParserInitPromise: Promise<boolean> | null;
|
||||
mpvClient: MpvIpcClient | null;
|
||||
jellyfinRemoteSession: JellyfinRemoteSessionService | null;
|
||||
discordPresenceService: ReturnType<typeof createDiscordPresenceService> | null;
|
||||
reconnectTimer: ReturnType<typeof setTimeout> | null;
|
||||
currentSubText: string;
|
||||
currentSubAssText: string;
|
||||
currentSubtitleData: SubtitleData | null;
|
||||
hoveredSubtitleTokenIndex: number | null;
|
||||
hoveredSubtitleRevision: number;
|
||||
windowTracker: BaseWindowTracker | null;
|
||||
subtitlePosition: SubtitlePosition | null;
|
||||
currentMediaPath: string | null;
|
||||
currentMediaTitle: string | null;
|
||||
playbackPaused: boolean | null;
|
||||
pendingSubtitlePosition: SubtitlePosition | null;
|
||||
anilistClientSecretState: AnilistSecretResolutionState;
|
||||
mecabTokenizer: MecabTokenizer | null;
|
||||
keybindings: Keybinding[];
|
||||
subtitleTimingTracker: SubtitleTimingTracker | null;
|
||||
immersionTracker: ImmersionTrackerService | null;
|
||||
ankiIntegration: AnkiIntegration | null;
|
||||
secondarySubMode: SecondarySubMode;
|
||||
lastSecondarySubToggleAtMs: number;
|
||||
previousSecondarySubVisibility: boolean | null;
|
||||
mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics;
|
||||
shortcutsRegistered: boolean;
|
||||
overlayRuntimeInitialized: boolean;
|
||||
fieldGroupingResolver: ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||
fieldGroupingResolverSequence: number;
|
||||
runtimeOptionsManager: RuntimeOptionsManager | null;
|
||||
trackerNotReadyWarningShown: boolean;
|
||||
overlayDebugVisualizationEnabled: boolean;
|
||||
subsyncInProgress: boolean;
|
||||
initialArgs: CliArgs | null;
|
||||
mpvSocketPath: string;
|
||||
texthookerPort: number;
|
||||
backendOverride: string | null;
|
||||
autoStartOverlay: boolean;
|
||||
texthookerOnlyMode: boolean;
|
||||
backgroundMode: boolean;
|
||||
jlptLevelLookup: (term: string) => JlptLevel | null;
|
||||
frequencyRankLookup: FrequencyDictionaryLookup;
|
||||
anilistSetupPageOpened: boolean;
|
||||
anilistRetryQueueState: AnilistRetryQueueState;
|
||||
}
|
||||
|
||||
export interface AppStateInitialValues {
|
||||
mpvSocketPath: string;
|
||||
texthookerPort: number;
|
||||
backendOverride?: string | null;
|
||||
autoStartOverlay?: boolean;
|
||||
texthookerOnlyMode?: boolean;
|
||||
backgroundMode?: boolean;
|
||||
}
|
||||
|
||||
export interface StartupState {
|
||||
initialArgs: Exclude<AppState['initialArgs'], null>;
|
||||
mpvSocketPath: AppState['mpvSocketPath'];
|
||||
texthookerPort: AppState['texthookerPort'];
|
||||
backendOverride: AppState['backendOverride'];
|
||||
autoStartOverlay: AppState['autoStartOverlay'];
|
||||
texthookerOnlyMode: AppState['texthookerOnlyMode'];
|
||||
backgroundMode: AppState['backgroundMode'];
|
||||
}
|
||||
|
||||
export function createAppState(values: AppStateInitialValues): AppState {
|
||||
return {
|
||||
yomitanExt: null,
|
||||
yomitanSettingsWindow: null,
|
||||
yomitanParserWindow: null,
|
||||
anilistSetupWindow: null,
|
||||
jellyfinSetupWindow: null,
|
||||
yomitanParserReadyPromise: null,
|
||||
yomitanParserInitPromise: null,
|
||||
mpvClient: null,
|
||||
jellyfinRemoteSession: null,
|
||||
discordPresenceService: null,
|
||||
reconnectTimer: null,
|
||||
currentSubText: '',
|
||||
currentSubAssText: '',
|
||||
currentSubtitleData: null,
|
||||
hoveredSubtitleTokenIndex: null,
|
||||
hoveredSubtitleRevision: 0,
|
||||
windowTracker: null,
|
||||
subtitlePosition: null,
|
||||
currentMediaPath: null,
|
||||
currentMediaTitle: null,
|
||||
playbackPaused: null,
|
||||
pendingSubtitlePosition: null,
|
||||
anilistClientSecretState: createInitialAnilistSecretResolutionState(),
|
||||
mecabTokenizer: null,
|
||||
keybindings: [],
|
||||
subtitleTimingTracker: null,
|
||||
immersionTracker: null,
|
||||
ankiIntegration: null,
|
||||
secondarySubMode: 'hover',
|
||||
lastSecondarySubToggleAtMs: 0,
|
||||
previousSecondarySubVisibility: null,
|
||||
mpvSubtitleRenderMetrics: {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
},
|
||||
runtimeOptionsManager: null,
|
||||
trackerNotReadyWarningShown: false,
|
||||
overlayDebugVisualizationEnabled: false,
|
||||
shortcutsRegistered: false,
|
||||
overlayRuntimeInitialized: false,
|
||||
fieldGroupingResolver: null,
|
||||
fieldGroupingResolverSequence: 0,
|
||||
subsyncInProgress: false,
|
||||
initialArgs: null,
|
||||
mpvSocketPath: values.mpvSocketPath,
|
||||
texthookerPort: values.texthookerPort,
|
||||
backendOverride: values.backendOverride ?? null,
|
||||
autoStartOverlay: values.autoStartOverlay ?? false,
|
||||
texthookerOnlyMode: values.texthookerOnlyMode ?? false,
|
||||
backgroundMode: values.backgroundMode ?? false,
|
||||
jlptLevelLookup: () => null,
|
||||
frequencyRankLookup: () => null,
|
||||
anilistSetupPageOpened: false,
|
||||
anilistRetryQueueState: createInitialAnilistRetryQueueState(),
|
||||
};
|
||||
}
|
||||
|
||||
export function applyStartupState(appState: AppState, startupState: StartupState): void {
|
||||
appState.initialArgs = startupState.initialArgs;
|
||||
appState.mpvSocketPath = startupState.mpvSocketPath;
|
||||
appState.texthookerPort = startupState.texthookerPort;
|
||||
appState.backendOverride = startupState.backendOverride;
|
||||
appState.autoStartOverlay = startupState.autoStartOverlay;
|
||||
appState.texthookerOnlyMode = startupState.texthookerOnlyMode;
|
||||
appState.backgroundMode = startupState.backgroundMode;
|
||||
}
|
||||
Reference in New Issue
Block a user