mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor(core): normalize core service naming
Standardize core service module and export names to reduce naming ambiguity and make imports predictable across runtime, tests, scripts, and docs.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: TASK-55
|
||||
title: Normalize service naming conventions across core/services
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-16 04:47'
|
||||
updated_date: '2026-02-17 09:12'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/index.ts
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The core/services directory has inconsistent naming patterns that create confusion:
|
||||
- Some files use `*Service.ts` suffix (e.g., `mpv-service.ts`, `tokenizer-service.ts`)
|
||||
- Others use `*RuntimeService.ts` or just descriptive names (e.g., `overlay-visibility-service.ts` exports functions with 'Service' in name)
|
||||
- Some functions in files have 'Service' suffix, others don't
|
||||
|
||||
This inconsistency makes it hard to predict file/function names and creates cognitive overhead.
|
||||
|
||||
Standardize on:
|
||||
- File names: `kebab-case.ts` without 'service' suffix (e.g., `mpv.ts`, `tokenizer.ts`)
|
||||
- Function names: descriptive verbs without 'Service' suffix (e.g., `createMpvClient()`, `tokenizeSubtitle()`)
|
||||
- Barrel exports: clean, predictable names
|
||||
|
||||
Files needing audit (47 services):
|
||||
- All files in src/core/services/ need review
|
||||
|
||||
Note: This is a large-scale refactor that should be done carefully to avoid breaking changes.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Establish naming convention rules (document in code or docs)
|
||||
- [x] #2 Audit all service files for naming inconsistencies
|
||||
- [x] #3 Rename files to follow convention (kebab-case, no 'service' suffix)
|
||||
- [x] #4 Rename exported functions to remove 'Service' suffix where present
|
||||
- [x] #5 Update all imports across the entire codebase
|
||||
- [x] #6 Update barrel exports
|
||||
- [x] #7 Run full test suite
|
||||
- [x] #8 Update any documentation referencing old names
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Starting implementation. Planning and executing a mechanical refactor: file names and exported symbols with Service suffix in `src/core/services`, then cascading import updates across `src/`.
|
||||
|
||||
Implemented naming convention refactor across `src/core/services`: removed `-service` from service file names, renamed Service-suffixed exported symbols to non-Service names, and updated barrel exports in `src/core/services/index.ts`.
|
||||
|
||||
Updated call sites across `src/main/**`, `src/core/services/**` tests, `scripts/**`, `package.json` test paths, and docs references (`docs/development.md`, `docs/architecture.md`, `docs/structure-roadmap.md`).
|
||||
|
||||
Validation completed: `pnpm run build` and `pnpm run test:fast` both pass after refactor.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Normalized `src/core/services` naming by removing `-service` from module filenames, dropping `Service` suffixes from exported service functions, and updating `src/core/services/index.ts` barrel exports to the new names. Updated all import/call sites across `src/main/**`, service tests, scripts, and docs/package test paths to match the new module and symbol names. Verified no behavior regressions with `pnpm run build` and `pnpm run test:fast` (all passing).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
id: TASK-55
|
||||
title: Normalize service naming conventions across core/services
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-02-16 04:47'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/index.ts
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The core/services directory has inconsistent naming patterns that create confusion:
|
||||
- Some files use `*Service.ts` suffix (e.g., `mpv-service.ts`, `tokenizer-service.ts`)
|
||||
- Others use `*RuntimeService.ts` or just descriptive names (e.g., `overlay-visibility-service.ts` exports functions with 'Service' in name)
|
||||
- Some functions in files have 'Service' suffix, others don't
|
||||
|
||||
This inconsistency makes it hard to predict file/function names and creates cognitive overhead.
|
||||
|
||||
Standardize on:
|
||||
- File names: `kebab-case.ts` without 'service' suffix (e.g., `mpv.ts`, `tokenizer.ts`)
|
||||
- Function names: descriptive verbs without 'Service' suffix (e.g., `createMpvClient()`, `tokenizeSubtitle()`)
|
||||
- Barrel exports: clean, predictable names
|
||||
|
||||
Files needing audit (47 services):
|
||||
- All files in src/core/services/ need review
|
||||
|
||||
Note: This is a large-scale refactor that should be done carefully to avoid breaking changes.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Establish naming convention rules (document in code or docs)
|
||||
- [ ] #2 Audit all service files for naming inconsistencies
|
||||
- [ ] #3 Rename files to follow convention (kebab-case, no 'service' suffix)
|
||||
- [ ] #4 Rename exported functions to remove 'Service' suffix where present
|
||||
- [ ] #5 Update all imports across the entire codebase
|
||||
- [ ] #6 Update barrel exports
|
||||
- [ ] #7 Run full test suite
|
||||
- [ ] #8 Update any documentation referencing old names
|
||||
<!-- AC:END -->
|
||||
@@ -222,7 +222,7 @@ flowchart TD
|
||||
- **Better reviewability:** PRs can be scoped to one subsystem.
|
||||
- **Backward compatibility:** CLI flags and IPC channels can remain stable while internals evolve.
|
||||
- **Extracted composition root:** TASK-27 refactored `main.ts` into focused modules under `src/main/`, isolating startup, lifecycle, IPC, CLI, and domain-specific runtime wiring.
|
||||
- **Split MPV service:** TASK-27.4 separated `mpv-service.ts` into transport (`mpv-transport.ts`), protocol (`mpv-protocol.ts`), state (`mpv-state.ts`), and properties (`mpv-properties.ts`) layers for improved maintainability.
|
||||
- **Split MPV service:** TASK-27.4 separated `mpv.ts` into transport (`mpv-transport.ts`), protocol (`mpv-protocol.ts`), state (`mpv-state.ts`), and properties (`mpv-properties.ts`) layers for improved maintainability.
|
||||
|
||||
## Extension Rules
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ Run `make help` for a full list of targets. Key ones:
|
||||
## Contributor Notes
|
||||
|
||||
- To add or change a config option, update `src/config/definitions.ts` first. Defaults, runtime-option metadata, and generated `config.example.jsonc` are derived from this centralized source.
|
||||
- Overlay window/visibility state is owned by `src/core/services/overlay-manager-service.ts`.
|
||||
- Overlay window/visibility state is owned by `src/core/services/overlay-manager.ts`.
|
||||
- Main process composition is now split across `src/main/` modules (`startup.ts`, `app-lifecycle.ts`, `startup-lifecycle.ts`, `state.ts`, `ipc-runtime.ts`, `cli-runtime.ts`, `overlay-runtime.ts`, `subsync-runtime.ts`).
|
||||
- MPV service has been split into transport, protocol, state, and properties layers in `src/core/services/`.
|
||||
- Prefer direct inline deps objects in `src/main/` modules for simple pass-through wiring.
|
||||
|
||||
@@ -8,8 +8,8 @@ Date: 2026-02-14
|
||||
| --- | --- | --- | --- |
|
||||
| `src/main.ts` | Bootstrap / composition root / orchestration | Active | Main entrypoint owns startup, lifecycle orchestration, service construction, state mutation surfaces, and IPC bindings |
|
||||
| `src/anki-integration.ts` | Domain service orchestration / integrations | Active | 2.6k+ LOC, high cyclomatic coupling to mpv/subtitle timing and mining flows |
|
||||
| `src/core/services/mpv-service.ts` | MPV protocol + app state bridge | Active | ~780 LOC, large protocol and behavior mix, 22-entry dep interface |
|
||||
| `src/core/services/subsync-service.ts` | Subsync orchestration (ffsubsync/alass workflows) | Active | ~494 LOC, file IO + mpv command orchestration + result shaping |
|
||||
| `src/core/services/mpv.ts` | MPV protocol + app state bridge | Active | ~780 LOC, large protocol and behavior mix, 22-entry dep interface |
|
||||
| `src/core/services/subsync.ts` | Subsync orchestration (ffsubsync/alass workflows) | Active | ~494 LOC, file IO + mpv command orchestration + result shaping |
|
||||
| `src/renderer/positioning.ts` | Renderer positioning layout policy | Active (downstream of TASK-27.5) | 513 LOC, layout/rules + platform-specific behavior in one module |
|
||||
| `src/config/service.ts` | Config load/validation | Active support | ~601 LOC, central schema validation + mutation APIs |
|
||||
| `src/types.ts` | Shared contract surface | Active support | ~640 LOC, foundational type exports driving module boundaries |
|
||||
@@ -34,7 +34,7 @@ Date: 2026-02-14
|
||||
- Electron event loop (`app.whenReady`, `process` signals)
|
||||
- startup/bootstrap services, service adapters in `src/core/services`
|
||||
|
||||
### `src/core/services/mpv-service.ts` (protocol + facade)
|
||||
### `src/core/services/mpv.ts` (protocol + facade)
|
||||
|
||||
- **Core exports**: `MpvIpcClient`, `MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY`, `MpvIpcClientDeps`
|
||||
- **Primary responsibilities / API**:
|
||||
@@ -45,9 +45,9 @@ Date: 2026-02-14
|
||||
- state restoration (`restorePreviousSecondarySubVisibility`)
|
||||
- **Current caller set**:
|
||||
- `src/main.ts` (construction + lifecycle + service invocations)
|
||||
- `src/core/services/mpv-control-service.ts` (runtime control API)
|
||||
- `src/core/services/subsync-service.ts` (`requestProperty`, request ID usage)
|
||||
- tests under `src/core/services/mpv-service.test.ts`
|
||||
- `src/main/ipc-mpv-command.ts` (runtime control API)
|
||||
- `src/core/services/subsync.ts` (`requestProperty`, request ID usage)
|
||||
- tests under `src/core/services/mpv.test.ts`
|
||||
- **Observed coupling risk**:
|
||||
- `MpvIpcClientDeps` mixes protocol config with app-level side effects (subtitle broadcast, tokenizer, overlay updates, config reads)
|
||||
|
||||
@@ -61,15 +61,15 @@ Date: 2026-02-14
|
||||
- **State dependencies (constructor)**:
|
||||
- config, subtitle timing tracker, mpv client, OSD/notification callbacks
|
||||
- **Primary callers**:
|
||||
- `src/core/services/overlay-runtime-init-service.ts` (initial integration creation)
|
||||
- `src/core/services/anki-jimaku-service.ts` (enable/disable and field-grouping RPC)
|
||||
- `src/core/services/mining-service.ts` (delegates mining actions)
|
||||
- `src/core/services/overlay-runtime-init.ts` (initial integration creation)
|
||||
- `src/core/services/anki-jimaku.ts` (enable/disable and field-grouping RPC)
|
||||
- `src/core/services/mining.ts` (delegates mining actions)
|
||||
|
||||
### `src/core/services/subsync-service.ts`
|
||||
### `src/core/services/subsync.ts`
|
||||
|
||||
- **Exports**: `runSubsyncManualService`, `openSubsyncManualPickerService`, `triggerSubsyncFromConfigService`
|
||||
- **Caller set**:
|
||||
- `src/core/services/subsync-runner-service.ts` (runtime wrappers)
|
||||
- `src/core/services/subsync-runner.ts` (runtime wrappers)
|
||||
- `src/core/services/mpv-jimaku/`? (through runtime services and IPC command handlers)
|
||||
|
||||
### `src/config/service.ts`
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"docs:build": "VITE_EXTRA_EXTENSIONS=jsonc vitepress build docs",
|
||||
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
|
||||
"test:config:dist": "node --test dist/config/config.test.js",
|
||||
"test:core:dist": "node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-service.test.js dist/core/services/field-grouping-overlay-service.test.js dist/core/services/numeric-shortcut-session-service.test.js dist/core/services/secondary-subtitle-service.test.js dist/core/services/mpv-render-metrics-service.test.js dist/core/services/overlay-content-measurement-service.test.js dist/core/services/mpv-control-service.test.js dist/core/services/mpv-service.test.js dist/core/services/runtime-options-ipc-service.test.js dist/core/services/runtime-config-service.test.js dist/core/services/tokenizer-service.test.js dist/core/services/subsync-service.test.js dist/core/services/overlay-bridge-service.test.js dist/core/services/overlay-manager-service.test.js dist/core/services/overlay-shortcut-handler.test.js dist/core/services/mining-service.test.js dist/core/services/anki-jimaku-service.test.js dist/core/services/immersion-tracker-service.test.js dist/core/services/app-ready-service.test.js dist/core/services/startup-bootstrap-service.test.js dist/subsync/utils.test.js dist/main/anilist-url-guard.test.js",
|
||||
"test:core:dist": "node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command.test.js dist/core/services/field-grouping-overlay.test.js dist/core/services/numeric-shortcut-session.test.js dist/core/services/secondary-subtitle.test.js dist/core/services/mpv-render-metrics.test.js dist/core/services/overlay-content-measurement.test.js dist/core/services/mpv-control.test.js dist/core/services/mpv.test.js dist/core/services/runtime-options-ipc.test.js dist/core/services/runtime-config.test.js dist/core/services/tokenizer.test.js dist/core/services/subsync.test.js dist/core/services/overlay-bridge.test.js dist/core/services/overlay-manager.test.js dist/core/services/overlay-shortcut-handler.test.js dist/core/services/mining.test.js dist/core/services/anki-jimaku.test.js dist/core/services/immersion-tracker-service.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js dist/subsync/utils.test.js dist/main/anilist-url-guard.test.js",
|
||||
"test:subtitle:dist": "echo \"Subtitle tests are currently not configured\"",
|
||||
"test": "pnpm run test:config && pnpm run test:core",
|
||||
"test:config": "pnpm run build && pnpm run test:config:dist",
|
||||
|
||||
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
import { createTokenizerDepsRuntimeService, tokenizeSubtitleService } from "../src/core/services/tokenizer-service.js";
|
||||
import { createFrequencyDictionaryLookupService } from "../src/core/services/frequency-dictionary-service.js";
|
||||
import { createTokenizerDepsRuntime, tokenizeSubtitle } from "../src/core/services/tokenizer.js";
|
||||
import { createFrequencyDictionaryLookup } from "../src/core/services/index.js";
|
||||
import { MecabTokenizer } from "../src/mecab-tokenizer.js";
|
||||
import type { MergedToken, FrequencyDictionaryLookup } from "../src/types.js";
|
||||
|
||||
@@ -537,11 +537,11 @@ async function createYomitanRuntimeState(
|
||||
|
||||
try {
|
||||
await electronImport.app.whenReady();
|
||||
const loadYomitanExtensionService = (
|
||||
const loadYomitanExtension = (
|
||||
await import(
|
||||
"../src/core/services/yomitan-extension-loader-service.js"
|
||||
"../src/core/services/yomitan-extension-loader.js"
|
||||
)
|
||||
).loadYomitanExtensionService as (
|
||||
).loadYomitanExtension as (
|
||||
options: {
|
||||
userDataPath: string;
|
||||
getYomitanParserWindow: () => unknown;
|
||||
@@ -552,7 +552,7 @@ async function createYomitanRuntimeState(
|
||||
},
|
||||
) => Promise<unknown>;
|
||||
|
||||
const extension = await loadYomitanExtensionService({
|
||||
const extension = await loadYomitanExtension({
|
||||
userDataPath,
|
||||
getYomitanParserWindow: () => state.parserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
@@ -623,7 +623,7 @@ async function createYomitanRuntimeStateWithSearch(
|
||||
}
|
||||
|
||||
async function getFrequencyLookup(dictionaryPath: string): Promise<FrequencyDictionaryLookup> {
|
||||
return createFrequencyDictionaryLookupService({
|
||||
return createFrequencyDictionaryLookup({
|
||||
searchPaths: [dictionaryPath],
|
||||
log: (message) => {
|
||||
// Keep script output pure JSON by default
|
||||
@@ -786,7 +786,7 @@ async function main(): Promise<void> {
|
||||
: null;
|
||||
const hasYomitan = Boolean(yomitanState?.available && yomitanState?.yomitanExt);
|
||||
|
||||
const deps = createTokenizerDepsRuntimeService({
|
||||
const deps = createTokenizerDepsRuntime({
|
||||
getYomitanExt: () =>
|
||||
(hasYomitan ? yomitanState!.yomitanExt : null) as never,
|
||||
getYomitanParserWindow: () =>
|
||||
@@ -823,7 +823,7 @@ async function main(): Promise<void> {
|
||||
}),
|
||||
});
|
||||
|
||||
const subtitleData = await tokenizeSubtitleService(args.input, deps);
|
||||
const subtitleData = await tokenizeSubtitle(args.input, deps);
|
||||
const tokenCount = subtitleData.tokens?.length ?? 0;
|
||||
const mergedCount = subtitleData.tokens?.filter((token) => token.isMerged).length ?? 0;
|
||||
const tokens =
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
import { createTokenizerDepsRuntimeService, tokenizeSubtitleService } from "../src/core/services/tokenizer-service.js";
|
||||
import { createTokenizerDepsRuntime, tokenizeSubtitle } from "../src/core/services/tokenizer.js";
|
||||
import { MecabTokenizer } from "../src/mecab-tokenizer.js";
|
||||
import type { MergedToken } from "../src/types.js";
|
||||
|
||||
@@ -563,7 +563,7 @@ async function main(): Promise<void> {
|
||||
yomitan.parserReadyPromise = runtime.parserReadyPromise;
|
||||
yomitan.parserInitPromise = runtime.parserInitPromise;
|
||||
|
||||
const deps = createTokenizerDepsRuntimeService({
|
||||
const deps = createTokenizerDepsRuntime({
|
||||
getYomitanExt: () => yomitan.extension,
|
||||
getYomitanParserWindow: () => yomitan.parserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
@@ -585,7 +585,7 @@ async function main(): Promise<void> {
|
||||
}),
|
||||
});
|
||||
|
||||
const subtitleData = await tokenizeSubtitleService(args.input, deps);
|
||||
const subtitleData = await tokenizeSubtitle(args.input, deps);
|
||||
const tokenizeText = normalizeTokenizerText(args.input);
|
||||
let rawParseResults: unknown = null;
|
||||
if (
|
||||
|
||||
@@ -2,8 +2,8 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
AnkiJimakuIpcRuntimeOptions,
|
||||
registerAnkiJimakuIpcRuntimeService,
|
||||
} from "./anki-jimaku-service";
|
||||
registerAnkiJimakuIpcRuntime,
|
||||
} from "./anki-jimaku";
|
||||
|
||||
interface RuntimeHarness {
|
||||
options: AnkiJimakuIpcRuntimeOptions;
|
||||
@@ -92,7 +92,7 @@ function createHarness(): RuntimeHarness {
|
||||
};
|
||||
|
||||
let registered: Record<string, (...args: unknown[]) => unknown> = {};
|
||||
registerAnkiJimakuIpcRuntimeService(
|
||||
registerAnkiJimakuIpcRuntime(
|
||||
options,
|
||||
(deps) => {
|
||||
registered = deps as unknown as Record<string, (...args: unknown[]) => unknown>;
|
||||
@@ -102,7 +102,7 @@ function createHarness(): RuntimeHarness {
|
||||
return { options, registered, state };
|
||||
}
|
||||
|
||||
test("registerAnkiJimakuIpcRuntimeService provides full handler surface", () => {
|
||||
test("registerAnkiJimakuIpcRuntime provides full handler surface", () => {
|
||||
const { registered } = createHarness();
|
||||
const expected = [
|
||||
"setAnkiConnectEnabled",
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
KikuFieldGroupingRequestData,
|
||||
} from "../../types";
|
||||
import { sortJimakuFiles } from "../../jimaku/utils";
|
||||
import type { AnkiJimakuIpcDeps } from "./anki-jimaku-ipc-service";
|
||||
import type { AnkiJimakuIpcDeps } from "./anki-jimaku-ipc";
|
||||
import { createLogger } from "../../logger";
|
||||
|
||||
export type RegisterAnkiJimakuIpcRuntimeHandler = (
|
||||
@@ -65,7 +65,7 @@ export interface AnkiJimakuIpcRuntimeOptions {
|
||||
|
||||
const logger = createLogger("main:anki-jimaku");
|
||||
|
||||
export function registerAnkiJimakuIpcRuntimeService(
|
||||
export function registerAnkiJimakuIpcRuntime(
|
||||
options: AnkiJimakuIpcRuntimeOptions,
|
||||
registerHandlers: RegisterAnkiJimakuIpcRuntimeHandler,
|
||||
): void {
|
||||
@@ -44,7 +44,7 @@ export interface AppLifecycleDepsRuntimeOptions {
|
||||
restoreWindowsOnActivate: () => void;
|
||||
}
|
||||
|
||||
export function createAppLifecycleDepsRuntimeService(
|
||||
export function createAppLifecycleDepsRuntime(
|
||||
options: AppLifecycleDepsRuntimeOptions,
|
||||
): AppLifecycleServiceDeps {
|
||||
return {
|
||||
@@ -80,7 +80,7 @@ export function createAppLifecycleDepsRuntimeService(
|
||||
};
|
||||
}
|
||||
|
||||
export function startAppLifecycleService(
|
||||
export function startAppLifecycle(
|
||||
initialArgs: CliArgs,
|
||||
deps: AppLifecycleServiceDeps,
|
||||
): void {
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { AppReadyRuntimeDeps, runAppReadyRuntimeService } from "./startup-service";
|
||||
import { AppReadyRuntimeDeps, runAppReadyRuntime } from "./startup";
|
||||
|
||||
function makeDeps(overrides: Partial<AppReadyRuntimeDeps> = {}) {
|
||||
const calls: string[] = [];
|
||||
@@ -37,11 +37,11 @@ function makeDeps(overrides: Partial<AppReadyRuntimeDeps> = {}) {
|
||||
return { deps, calls };
|
||||
}
|
||||
|
||||
test("runAppReadyRuntimeService starts websocket in auto mode when plugin missing", async () => {
|
||||
test("runAppReadyRuntime starts websocket in auto mode when plugin missing", async () => {
|
||||
const { deps, calls } = makeDeps({
|
||||
hasMpvWebsocketPlugin: () => false,
|
||||
});
|
||||
await runAppReadyRuntimeService(deps);
|
||||
await runAppReadyRuntime(deps);
|
||||
assert.ok(calls.includes("startSubtitleWebsocket:9001"));
|
||||
assert.ok(calls.includes("initializeOverlayRuntime"));
|
||||
assert.ok(calls.includes("createImmersionTracker"));
|
||||
@@ -80,11 +80,11 @@ test("runAppReadyRuntimeService logs and continues when createImmersionTracker t
|
||||
assert.ok(calls.includes("handleInitialArgs"));
|
||||
});
|
||||
|
||||
test("runAppReadyRuntimeService logs defer message when overlay not auto-started", async () => {
|
||||
test("runAppReadyRuntime logs defer message when overlay not auto-started", async () => {
|
||||
const { deps, calls } = makeDeps({
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig: () => false,
|
||||
});
|
||||
await runAppReadyRuntimeService(deps);
|
||||
await runAppReadyRuntime(deps);
|
||||
assert.ok(
|
||||
calls.includes(
|
||||
"log:Overlay runtime deferred: waiting for explicit overlay command.",
|
||||
@@ -92,7 +92,7 @@ test("runAppReadyRuntimeService logs defer message when overlay not auto-started
|
||||
);
|
||||
});
|
||||
|
||||
test("runAppReadyRuntimeService applies config logging level during app-ready", async () => {
|
||||
test("runAppReadyRuntime applies config logging level during app-ready", async () => {
|
||||
const { deps, calls } = makeDeps({
|
||||
getResolvedConfig: () => ({
|
||||
websocket: { enabled: "auto" },
|
||||
@@ -100,6 +100,6 @@ test("runAppReadyRuntimeService applies config logging level during app-ready",
|
||||
logging: { level: "warn" },
|
||||
}),
|
||||
});
|
||||
await runAppReadyRuntimeService(deps);
|
||||
await runAppReadyRuntime(deps);
|
||||
assert.ok(calls.includes("setLogLevel:warn:config"));
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { CliArgs } from "../../cli/args";
|
||||
import { CliCommandServiceDeps, handleCliCommandService } from "./cli-command-service";
|
||||
import { CliCommandServiceDeps, handleCliCommand } from "./cli-command";
|
||||
|
||||
function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
return {
|
||||
@@ -148,21 +148,21 @@ function createDeps(overrides: Partial<CliCommandServiceDeps> = {}) {
|
||||
return { deps, calls, osd };
|
||||
}
|
||||
|
||||
test("handleCliCommandService ignores --start for second-instance without actions", () => {
|
||||
test("handleCliCommand ignores --start for second-instance without actions", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
const args = makeArgs({ start: true });
|
||||
|
||||
handleCliCommandService(args, "second-instance", deps);
|
||||
handleCliCommand(args, "second-instance", deps);
|
||||
|
||||
assert.ok(calls.includes("log:Ignoring --start because SubMiner is already running."));
|
||||
assert.equal(calls.some((value) => value.includes("connectMpvClient")), false);
|
||||
});
|
||||
|
||||
test("handleCliCommandService runs texthooker flow with browser open", () => {
|
||||
test("handleCliCommand runs texthooker flow with browser open", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
const args = makeArgs({ texthooker: true });
|
||||
|
||||
handleCliCommandService(args, "initial", deps);
|
||||
handleCliCommand(args, "initial", deps);
|
||||
|
||||
assert.ok(calls.includes("ensureTexthookerRunning:5174"));
|
||||
assert.ok(
|
||||
@@ -170,24 +170,24 @@ test("handleCliCommandService runs texthooker flow with browser open", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("handleCliCommandService reports async mine errors to OSD", async () => {
|
||||
test("handleCliCommand reports async mine errors to OSD", async () => {
|
||||
const { deps, calls, osd } = createDeps({
|
||||
mineSentenceCard: async () => {
|
||||
throw new Error("boom");
|
||||
},
|
||||
});
|
||||
|
||||
handleCliCommandService(makeArgs({ mineSentence: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ mineSentence: true }), "initial", deps);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.ok(calls.some((value) => value.startsWith("error:mineSentenceCard failed:")));
|
||||
assert.ok(osd.some((value) => value.includes("Mine sentence failed: boom")));
|
||||
});
|
||||
|
||||
test("handleCliCommandService applies socket path and connects on start", () => {
|
||||
test("handleCliCommand applies socket path and connects on start", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
|
||||
handleCliCommandService(
|
||||
handleCliCommand(
|
||||
makeArgs({ start: true, socketPath: "/tmp/custom.sock" }),
|
||||
"initial",
|
||||
deps,
|
||||
@@ -198,12 +198,12 @@ test("handleCliCommandService applies socket path and connects on start", () =>
|
||||
assert.ok(calls.includes("connectMpvClient"));
|
||||
});
|
||||
|
||||
test("handleCliCommandService warns when texthooker port override used while running", () => {
|
||||
test("handleCliCommand warns when texthooker port override used while running", () => {
|
||||
const { deps, calls } = createDeps({
|
||||
isTexthookerRunning: () => true,
|
||||
});
|
||||
|
||||
handleCliCommandService(
|
||||
handleCliCommand(
|
||||
makeArgs({ texthookerPort: 9999, texthooker: true }),
|
||||
"initial",
|
||||
deps,
|
||||
@@ -217,25 +217,25 @@ test("handleCliCommandService warns when texthooker port override used while run
|
||||
assert.equal(calls.some((value) => value === "setTexthookerPort:9999"), false);
|
||||
});
|
||||
|
||||
test("handleCliCommandService prints help and stops app when no window exists", () => {
|
||||
test("handleCliCommand prints help and stops app when no window exists", () => {
|
||||
const { deps, calls } = createDeps({
|
||||
hasMainWindow: () => false,
|
||||
});
|
||||
|
||||
handleCliCommandService(makeArgs({ help: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ help: true }), "initial", deps);
|
||||
|
||||
assert.ok(calls.includes("printHelp"));
|
||||
assert.ok(calls.includes("stopApp"));
|
||||
});
|
||||
|
||||
test("handleCliCommandService reports async trigger-subsync errors to OSD", async () => {
|
||||
test("handleCliCommand reports async trigger-subsync errors to OSD", async () => {
|
||||
const { deps, calls, osd } = createDeps({
|
||||
triggerSubsyncFromConfig: async () => {
|
||||
throw new Error("subsync boom");
|
||||
},
|
||||
});
|
||||
|
||||
handleCliCommandService(makeArgs({ triggerSubsync: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ triggerSubsync: true }), "initial", deps);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.ok(
|
||||
@@ -244,16 +244,16 @@ test("handleCliCommandService reports async trigger-subsync errors to OSD", asyn
|
||||
assert.ok(osd.some((value) => value.includes("Subsync failed: subsync boom")));
|
||||
});
|
||||
|
||||
test("handleCliCommandService stops app for --stop command", () => {
|
||||
test("handleCliCommand stops app for --stop command", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
handleCliCommandService(makeArgs({ stop: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ stop: true }), "initial", deps);
|
||||
assert.ok(calls.includes("log:Stopping SubMiner..."));
|
||||
assert.ok(calls.includes("stopApp"));
|
||||
});
|
||||
|
||||
test("handleCliCommandService still runs non-start actions on second-instance", () => {
|
||||
test("handleCliCommand still runs non-start actions on second-instance", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
handleCliCommandService(
|
||||
handleCliCommand(
|
||||
makeArgs({ start: true, toggleVisibleOverlay: true }),
|
||||
"second-instance",
|
||||
deps,
|
||||
@@ -262,7 +262,7 @@ test("handleCliCommandService still runs non-start actions on second-instance",
|
||||
assert.equal(calls.some((value) => value === "connectMpvClient"), true);
|
||||
});
|
||||
|
||||
test("handleCliCommandService handles visibility and utility command dispatches", () => {
|
||||
test("handleCliCommand handles visibility and utility command dispatches", () => {
|
||||
const cases: Array<{
|
||||
args: Partial<CliArgs>;
|
||||
expected: string;
|
||||
@@ -285,7 +285,7 @@ test("handleCliCommandService handles visibility and utility command dispatches"
|
||||
|
||||
for (const entry of cases) {
|
||||
const { deps, calls } = createDeps();
|
||||
handleCliCommandService(makeArgs(entry.args), "initial", deps);
|
||||
handleCliCommand(makeArgs(entry.args), "initial", deps);
|
||||
assert.ok(
|
||||
calls.includes(entry.expected),
|
||||
`expected call missing for args ${JSON.stringify(entry.args)}: ${entry.expected}`,
|
||||
@@ -293,22 +293,22 @@ test("handleCliCommandService handles visibility and utility command dispatches"
|
||||
}
|
||||
});
|
||||
|
||||
test("handleCliCommandService runs refresh-known-words command", () => {
|
||||
test("handleCliCommand runs refresh-known-words command", () => {
|
||||
const { deps, calls } = createDeps();
|
||||
|
||||
handleCliCommandService(makeArgs({ refreshKnownWords: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ refreshKnownWords: true }), "initial", deps);
|
||||
|
||||
assert.ok(calls.includes("refreshKnownWords"));
|
||||
});
|
||||
|
||||
test("handleCliCommandService reports async refresh-known-words errors to OSD", async () => {
|
||||
test("handleCliCommand reports async refresh-known-words errors to OSD", async () => {
|
||||
const { deps, calls, osd } = createDeps({
|
||||
refreshKnownWords: async () => {
|
||||
throw new Error("refresh boom");
|
||||
},
|
||||
});
|
||||
|
||||
handleCliCommandService(makeArgs({ refreshKnownWords: true }), "initial", deps);
|
||||
handleCliCommand(makeArgs({ refreshKnownWords: true }), "initial", deps);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.ok(
|
||||
@@ -116,7 +116,7 @@ export interface CliCommandDepsRuntimeOptions {
|
||||
error: (message: string, err: unknown) => void;
|
||||
}
|
||||
|
||||
export function createCliCommandDepsRuntimeService(
|
||||
export function createCliCommandDepsRuntime(
|
||||
options: CliCommandDepsRuntimeOptions,
|
||||
): CliCommandServiceDeps {
|
||||
return {
|
||||
@@ -189,7 +189,7 @@ function runAsyncWithOsd(
|
||||
});
|
||||
}
|
||||
|
||||
export function handleCliCommandService(
|
||||
export function handleCliCommand(
|
||||
args: CliArgs,
|
||||
source: CliCommandSource = "initial",
|
||||
deps: CliCommandServiceDeps,
|
||||
@@ -1,15 +1,15 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { KikuFieldGroupingChoice } from "../../types";
|
||||
import { createFieldGroupingOverlayRuntimeService } from "./field-grouping-overlay-service";
|
||||
import { createFieldGroupingOverlayRuntime } from "./field-grouping-overlay";
|
||||
|
||||
test("createFieldGroupingOverlayRuntimeService sends overlay messages and sets restore flag", () => {
|
||||
test("createFieldGroupingOverlayRuntime sends overlay messages and sets restore flag", () => {
|
||||
const sent: unknown[][] = [];
|
||||
let visible = false;
|
||||
const restore = new Set<"runtime-options" | "subsync">();
|
||||
|
||||
const runtime =
|
||||
createFieldGroupingOverlayRuntimeService<"runtime-options" | "subsync">({
|
||||
createFieldGroupingOverlayRuntime<"runtime-options" | "subsync">({
|
||||
getMainWindow: () => ({
|
||||
isDestroyed: () => false,
|
||||
webContents: {
|
||||
@@ -40,10 +40,10 @@ test("createFieldGroupingOverlayRuntimeService sends overlay messages and sets r
|
||||
assert.deepEqual(sent, [["runtime-options:open"]]);
|
||||
});
|
||||
|
||||
test("createFieldGroupingOverlayRuntimeService callback cancels when send fails", async () => {
|
||||
test("createFieldGroupingOverlayRuntime callback cancels when send fails", async () => {
|
||||
let resolver: ((choice: KikuFieldGroupingChoice) => void) | null = null;
|
||||
const runtime =
|
||||
createFieldGroupingOverlayRuntimeService<"runtime-options" | "subsync">({
|
||||
createFieldGroupingOverlayRuntime<"runtime-options" | "subsync">({
|
||||
getMainWindow: () => null,
|
||||
getVisibleOverlayVisible: () => false,
|
||||
getInvisibleOverlayVisible: () => false,
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
KikuFieldGroupingRequestData,
|
||||
} from "../../types";
|
||||
import {
|
||||
createFieldGroupingCallbackRuntimeService,
|
||||
sendToVisibleOverlayRuntimeService,
|
||||
} from "./overlay-bridge-service";
|
||||
createFieldGroupingCallbackRuntime,
|
||||
sendToVisibleOverlayRuntime,
|
||||
} from "./overlay-bridge";
|
||||
|
||||
interface WindowLike {
|
||||
isDestroyed: () => boolean;
|
||||
@@ -32,7 +32,7 @@ export interface FieldGroupingOverlayRuntimeOptions<T extends string> {
|
||||
) => boolean;
|
||||
}
|
||||
|
||||
export function createFieldGroupingOverlayRuntimeService<T extends string>(
|
||||
export function createFieldGroupingOverlayRuntime<T extends string>(
|
||||
options: FieldGroupingOverlayRuntimeOptions<T>,
|
||||
): {
|
||||
sendToVisibleOverlay: (
|
||||
@@ -52,7 +52,7 @@ export function createFieldGroupingOverlayRuntimeService<T extends string>(
|
||||
if (options.sendToVisibleOverlay) {
|
||||
return options.sendToVisibleOverlay(channel, payload, runtimeOptions);
|
||||
}
|
||||
return sendToVisibleOverlayRuntimeService({
|
||||
return sendToVisibleOverlayRuntime({
|
||||
mainWindow: options.getMainWindow() as never,
|
||||
visibleOverlayVisible: options.getVisibleOverlayVisible(),
|
||||
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
||||
@@ -67,7 +67,7 @@ export function createFieldGroupingOverlayRuntimeService<T extends string>(
|
||||
const createFieldGroupingCallback = (): ((
|
||||
data: KikuFieldGroupingRequestData,
|
||||
) => Promise<KikuFieldGroupingChoice>) => {
|
||||
return createFieldGroupingCallbackRuntimeService({
|
||||
return createFieldGroupingCallbackRuntime({
|
||||
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
||||
getInvisibleOverlayVisible: options.getInvisibleOverlayVisible,
|
||||
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
KikuFieldGroupingRequestData,
|
||||
} from "../../types";
|
||||
|
||||
export function createFieldGroupingCallbackService(options: {
|
||||
export function createFieldGroupingCallback(options: {
|
||||
getVisibleOverlayVisible: () => boolean;
|
||||
getInvisibleOverlayVisible: () => boolean;
|
||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||
@@ -4,15 +4,15 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { createFrequencyDictionaryLookupService } from "./frequency-dictionary-service";
|
||||
import { createFrequencyDictionaryLookup } from "./frequency-dictionary";
|
||||
|
||||
test("createFrequencyDictionaryLookupService logs parse errors and returns no-op for invalid dictionaries", async () => {
|
||||
test("createFrequencyDictionaryLookup logs parse errors and returns no-op for invalid dictionaries", async () => {
|
||||
const logs: string[] = [];
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "subminer-frequency-dict-"));
|
||||
const bankPath = path.join(tempDir, "term_meta_bank_1.json");
|
||||
fs.writeFileSync(bankPath, "{ invalid json");
|
||||
|
||||
const lookup = await createFrequencyDictionaryLookupService({
|
||||
const lookup = await createFrequencyDictionaryLookup({
|
||||
searchPaths: [tempDir],
|
||||
log: (message) => {
|
||||
logs.push(message);
|
||||
@@ -31,10 +31,10 @@ test("createFrequencyDictionaryLookupService logs parse errors and returns no-op
|
||||
);
|
||||
});
|
||||
|
||||
test("createFrequencyDictionaryLookupService continues with no-op lookup when search path is missing", async () => {
|
||||
test("createFrequencyDictionaryLookup continues with no-op lookup when search path is missing", async () => {
|
||||
const logs: string[] = [];
|
||||
const missingPath = path.join(os.tmpdir(), "subminer-frequency-dict-missing-dir");
|
||||
const lookup = await createFrequencyDictionaryLookupService({
|
||||
const lookup = await createFrequencyDictionaryLookup({
|
||||
searchPaths: [missingPath],
|
||||
log: (message) => {
|
||||
logs.push(message);
|
||||
@@ -145,7 +145,7 @@ function collectDictionaryFromPath(
|
||||
return terms;
|
||||
}
|
||||
|
||||
export async function createFrequencyDictionaryLookupService(
|
||||
export async function createFrequencyDictionaryLookup(
|
||||
options: FrequencyDictionaryLookupOptions,
|
||||
): Promise<(term: string) => number | null> {
|
||||
const attemptedPaths: string[] = [];
|
||||
@@ -1,39 +1,39 @@
|
||||
export { TexthookerService } from "./texthooker-service";
|
||||
export { hasMpvWebsocketPlugin, SubtitleWebSocketService } from "./subtitle-ws-service";
|
||||
export { registerGlobalShortcutsService } from "./shortcut-service";
|
||||
export { createIpcDepsRuntimeService, registerIpcHandlersService } from "./ipc-service";
|
||||
export { shortcutMatchesInputForLocalFallback } from "./shortcut-fallback-service";
|
||||
export { Texthooker } from "./texthooker";
|
||||
export { hasMpvWebsocketPlugin, SubtitleWebSocket } from "./subtitle-ws";
|
||||
export { registerGlobalShortcuts } from "./shortcut";
|
||||
export { createIpcDepsRuntime, registerIpcHandlers } from "./ipc";
|
||||
export { shortcutMatchesInputForLocalFallback } from "./shortcut-fallback";
|
||||
export {
|
||||
refreshOverlayShortcutsRuntimeService,
|
||||
registerOverlayShortcutsService,
|
||||
syncOverlayShortcutsRuntimeService,
|
||||
unregisterOverlayShortcutsRuntimeService,
|
||||
} from "./overlay-shortcut-service";
|
||||
refreshOverlayShortcutsRuntime,
|
||||
registerOverlayShortcuts,
|
||||
syncOverlayShortcutsRuntime,
|
||||
unregisterOverlayShortcutsRuntime,
|
||||
} from "./overlay-shortcut";
|
||||
export { createOverlayShortcutRuntimeHandlers } from "./overlay-shortcut-handler";
|
||||
export { createCliCommandDepsRuntimeService, handleCliCommandService } from "./cli-command-service";
|
||||
export { createCliCommandDepsRuntime, handleCliCommand } from "./cli-command";
|
||||
export {
|
||||
copyCurrentSubtitleService,
|
||||
handleMineSentenceDigitService,
|
||||
handleMultiCopyDigitService,
|
||||
markLastCardAsAudioCardService,
|
||||
mineSentenceCardService,
|
||||
triggerFieldGroupingService,
|
||||
updateLastCardFromClipboardService,
|
||||
} from "./mining-service";
|
||||
export { createAppLifecycleDepsRuntimeService, startAppLifecycleService } from "./app-lifecycle-service";
|
||||
copyCurrentSubtitle,
|
||||
handleMineSentenceDigit,
|
||||
handleMultiCopyDigit,
|
||||
markLastCardAsAudioCard,
|
||||
mineSentenceCard,
|
||||
triggerFieldGrouping,
|
||||
updateLastCardFromClipboard,
|
||||
} from "./mining";
|
||||
export { createAppLifecycleDepsRuntime, startAppLifecycle } from "./app-lifecycle";
|
||||
export {
|
||||
cycleSecondarySubModeService,
|
||||
} from "./subtitle-position-service";
|
||||
cycleSecondarySubMode,
|
||||
} from "./subtitle-position";
|
||||
export {
|
||||
getInitialInvisibleOverlayVisibilityService,
|
||||
isAutoUpdateEnabledRuntimeService,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService,
|
||||
shouldBindVisibleOverlayToMpvSubVisibilityService,
|
||||
} from "./startup-service";
|
||||
export { openYomitanSettingsWindow } from "./yomitan-settings-service";
|
||||
export { createTokenizerDepsRuntimeService, tokenizeSubtitleService } from "./tokenizer-service";
|
||||
export { createFrequencyDictionaryLookupService } from "./frequency-dictionary-service";
|
||||
export { createJlptVocabularyLookupService } from "./jlpt-vocab-service";
|
||||
getInitialInvisibleOverlayVisibility,
|
||||
isAutoUpdateEnabledRuntime,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig,
|
||||
shouldBindVisibleOverlayToMpvSubVisibility,
|
||||
} from "./startup";
|
||||
export { openYomitanSettingsWindow } from "./yomitan-settings";
|
||||
export { createTokenizerDepsRuntime, tokenizeSubtitle } from "./tokenizer";
|
||||
export { createFrequencyDictionaryLookup } from "./frequency-dictionary";
|
||||
export { createJlptVocabularyLookup } from "./jlpt-vocab";
|
||||
export {
|
||||
getIgnoredPos1Entries,
|
||||
JlptIgnoredPos1Entry,
|
||||
@@ -44,59 +44,59 @@ export {
|
||||
shouldIgnoreJlptByTerm,
|
||||
shouldIgnoreJlptForMecabPos1,
|
||||
} from "./jlpt-token-filter";
|
||||
export { loadYomitanExtensionService } from "./yomitan-extension-loader-service";
|
||||
export { loadYomitanExtension } from "./yomitan-extension-loader";
|
||||
export {
|
||||
getJimakuLanguagePreferenceService,
|
||||
getJimakuMaxEntryResultsService,
|
||||
jimakuFetchJsonService,
|
||||
resolveJimakuApiKeyService,
|
||||
} from "./jimaku-service";
|
||||
getJimakuLanguagePreference,
|
||||
getJimakuMaxEntryResults,
|
||||
jimakuFetchJson,
|
||||
resolveJimakuApiKey,
|
||||
} from "./jimaku";
|
||||
export {
|
||||
loadSubtitlePositionService,
|
||||
saveSubtitlePositionService,
|
||||
updateCurrentMediaPathService,
|
||||
} from "./subtitle-position-service";
|
||||
loadSubtitlePosition,
|
||||
saveSubtitlePosition,
|
||||
updateCurrentMediaPath,
|
||||
} from "./subtitle-position";
|
||||
export {
|
||||
createOverlayWindowService,
|
||||
enforceOverlayLayerOrderService,
|
||||
ensureOverlayWindowLevelService,
|
||||
updateOverlayWindowBoundsService,
|
||||
} from "./overlay-window-service";
|
||||
export { initializeOverlayRuntimeService } from "./overlay-runtime-init-service";
|
||||
createOverlayWindow,
|
||||
enforceOverlayLayerOrder,
|
||||
ensureOverlayWindowLevel,
|
||||
updateOverlayWindowBounds,
|
||||
} from "./overlay-window";
|
||||
export { initializeOverlayRuntime } from "./overlay-runtime-init";
|
||||
export {
|
||||
setInvisibleOverlayVisibleService,
|
||||
setVisibleOverlayVisibleService,
|
||||
syncInvisibleOverlayMousePassthroughService,
|
||||
updateInvisibleOverlayVisibilityService,
|
||||
updateVisibleOverlayVisibilityService,
|
||||
} from "./overlay-visibility-service";
|
||||
setInvisibleOverlayVisible,
|
||||
setVisibleOverlayVisible,
|
||||
syncInvisibleOverlayMousePassthrough,
|
||||
updateInvisibleOverlayVisibility,
|
||||
updateVisibleOverlayVisibility,
|
||||
} from "./overlay-visibility";
|
||||
export {
|
||||
MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY,
|
||||
MpvIpcClient,
|
||||
MpvRuntimeClientLike,
|
||||
MpvTrackProperty,
|
||||
playNextSubtitleRuntimeService,
|
||||
replayCurrentSubtitleRuntimeService,
|
||||
playNextSubtitleRuntime,
|
||||
replayCurrentSubtitleRuntime,
|
||||
resolveCurrentAudioStreamIndex,
|
||||
sendMpvCommandRuntimeService,
|
||||
setMpvSubVisibilityRuntimeService,
|
||||
showMpvOsdRuntimeService,
|
||||
} from "./mpv-service";
|
||||
sendMpvCommandRuntime,
|
||||
setMpvSubVisibilityRuntime,
|
||||
showMpvOsdRuntime,
|
||||
} from "./mpv";
|
||||
export {
|
||||
applyMpvSubtitleRenderMetricsPatchService,
|
||||
applyMpvSubtitleRenderMetricsPatch,
|
||||
DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
sanitizeMpvSubtitleRenderMetrics,
|
||||
} from "./mpv-render-metrics-service";
|
||||
export { createOverlayContentMeasurementStoreService } from "./overlay-content-measurement-service";
|
||||
export { handleMpvCommandFromIpcService } from "./ipc-command-service";
|
||||
} from "./mpv-render-metrics";
|
||||
export { createOverlayContentMeasurementStore } from "./overlay-content-measurement";
|
||||
export { handleMpvCommandFromIpc } from "./ipc-command";
|
||||
export { createFieldGroupingOverlayRuntime } from "./field-grouping-overlay";
|
||||
export { createNumericShortcutRuntime } from "./numeric-shortcut";
|
||||
export { runStartupBootstrapRuntime } from "./startup";
|
||||
export { runSubsyncManualFromIpcRuntime, triggerSubsyncFromConfigRuntime } from "./subsync-runner";
|
||||
export { registerAnkiJimakuIpcRuntime } from "./anki-jimaku";
|
||||
export { ImmersionTrackerService } from "./immersion-tracker-service";
|
||||
export { createFieldGroupingOverlayRuntimeService } from "./field-grouping-overlay-service";
|
||||
export { createNumericShortcutRuntimeService } from "./numeric-shortcut-service";
|
||||
export { runStartupBootstrapRuntimeService } from "./startup-service";
|
||||
export { runSubsyncManualFromIpcRuntimeService, triggerSubsyncFromConfigRuntimeService } from "./subsync-runner-service";
|
||||
export { registerAnkiJimakuIpcRuntimeService } from "./anki-jimaku-service";
|
||||
export {
|
||||
broadcastRuntimeOptionsChangedRuntimeService,
|
||||
createOverlayManagerService,
|
||||
setOverlayDebugVisualizationEnabledRuntimeService,
|
||||
} from "./overlay-manager-service";
|
||||
broadcastRuntimeOptionsChangedRuntime,
|
||||
createOverlayManager,
|
||||
setOverlayDebugVisualizationEnabledRuntime,
|
||||
} from "./overlay-manager";
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface HandleMpvCommandFromIpcOptions {
|
||||
hasRuntimeOptionsManager: () => boolean;
|
||||
}
|
||||
|
||||
export function handleMpvCommandFromIpcService(
|
||||
export function handleMpvCommandFromIpc(
|
||||
command: (string | number)[],
|
||||
options: HandleMpvCommandFromIpcOptions,
|
||||
): void {
|
||||
@@ -66,7 +66,7 @@ export function handleMpvCommandFromIpcService(
|
||||
}
|
||||
}
|
||||
|
||||
export async function runSubsyncManualFromIpcService(
|
||||
export async function runSubsyncManualFromIpc(
|
||||
request: SubsyncManualRunRequest,
|
||||
options: {
|
||||
isSubsyncInProgress: () => boolean;
|
||||
@@ -84,7 +84,7 @@ export interface IpcDepsRuntimeOptions {
|
||||
reportOverlayContentBounds: (payload: unknown) => void;
|
||||
}
|
||||
|
||||
export function createIpcDepsRuntimeService(
|
||||
export function createIpcDepsRuntime(
|
||||
options: IpcDepsRuntimeOptions,
|
||||
): IpcServiceDeps {
|
||||
return {
|
||||
@@ -143,7 +143,7 @@ export function createIpcDepsRuntimeService(
|
||||
};
|
||||
}
|
||||
|
||||
export function registerIpcHandlersService(deps: IpcServiceDeps): void {
|
||||
export function registerIpcHandlers(deps: IpcServiceDeps): void {
|
||||
ipcMain.on(
|
||||
"set-ignore-mouse-events",
|
||||
(
|
||||
@@ -8,34 +8,34 @@ import {
|
||||
resolveJimakuApiKey as resolveJimakuApiKeyFromConfig,
|
||||
} from "../../jimaku/utils";
|
||||
|
||||
export function getJimakuConfigService(
|
||||
export function getJimakuConfig(
|
||||
getResolvedConfig: () => { jimaku?: JimakuConfig },
|
||||
): JimakuConfig {
|
||||
const config = getResolvedConfig();
|
||||
return config.jimaku ?? {};
|
||||
}
|
||||
|
||||
export function getJimakuBaseUrlService(
|
||||
export function getJimakuBaseUrl(
|
||||
getResolvedConfig: () => { jimaku?: JimakuConfig },
|
||||
defaultBaseUrl: string,
|
||||
): string {
|
||||
const config = getJimakuConfigService(getResolvedConfig);
|
||||
const config = getJimakuConfig(getResolvedConfig);
|
||||
return config.apiBaseUrl || defaultBaseUrl;
|
||||
}
|
||||
|
||||
export function getJimakuLanguagePreferenceService(
|
||||
export function getJimakuLanguagePreference(
|
||||
getResolvedConfig: () => { jimaku?: JimakuConfig },
|
||||
defaultPreference: JimakuLanguagePreference,
|
||||
): JimakuLanguagePreference {
|
||||
const config = getJimakuConfigService(getResolvedConfig);
|
||||
const config = getJimakuConfig(getResolvedConfig);
|
||||
return config.languagePreference || defaultPreference;
|
||||
}
|
||||
|
||||
export function getJimakuMaxEntryResultsService(
|
||||
export function getJimakuMaxEntryResults(
|
||||
getResolvedConfig: () => { jimaku?: JimakuConfig },
|
||||
defaultValue: number,
|
||||
): number {
|
||||
const config = getJimakuConfigService(getResolvedConfig);
|
||||
const config = getJimakuConfig(getResolvedConfig);
|
||||
const value = config.maxEntryResults;
|
||||
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
||||
return Math.floor(value);
|
||||
@@ -43,13 +43,13 @@ export function getJimakuMaxEntryResultsService(
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
export async function resolveJimakuApiKeyService(
|
||||
export async function resolveJimakuApiKey(
|
||||
getResolvedConfig: () => { jimaku?: JimakuConfig },
|
||||
): Promise<string | null> {
|
||||
return resolveJimakuApiKeyFromConfig(getJimakuConfigService(getResolvedConfig));
|
||||
return resolveJimakuApiKeyFromConfig(getJimakuConfig(getResolvedConfig));
|
||||
}
|
||||
|
||||
export async function jimakuFetchJsonService<T>(
|
||||
export async function jimakuFetchJson<T>(
|
||||
endpoint: string,
|
||||
query: Record<string, string | number | boolean | null | undefined> = {},
|
||||
options: {
|
||||
@@ -59,7 +59,7 @@ export async function jimakuFetchJsonService<T>(
|
||||
defaultLanguagePreference: JimakuLanguagePreference;
|
||||
},
|
||||
): Promise<JimakuApiResponse<T>> {
|
||||
const apiKey = await resolveJimakuApiKeyService(options.getResolvedConfig);
|
||||
const apiKey = await resolveJimakuApiKey(options.getResolvedConfig);
|
||||
if (!apiKey) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -72,7 +72,7 @@ export async function jimakuFetchJsonService<T>(
|
||||
}
|
||||
|
||||
return jimakuFetchJsonRequest<T>(endpoint, query, {
|
||||
baseUrl: getJimakuBaseUrlService(
|
||||
baseUrl: getJimakuBaseUrl(
|
||||
options.getResolvedConfig,
|
||||
options.defaultBaseUrl,
|
||||
),
|
||||
@@ -134,7 +134,7 @@ function collectDictionaryFromPath(
|
||||
return terms;
|
||||
}
|
||||
|
||||
export async function createJlptVocabularyLookupService(
|
||||
export async function createJlptVocabularyLookup(
|
||||
options: JlptVocabLookupOptions,
|
||||
): Promise<(term: string) => JlptLevel | null> {
|
||||
const attemptedPaths: string[] = [];
|
||||
@@ -1,24 +1,24 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
copyCurrentSubtitleService,
|
||||
handleMineSentenceDigitService,
|
||||
handleMultiCopyDigitService,
|
||||
mineSentenceCardService,
|
||||
} from "./mining-service";
|
||||
copyCurrentSubtitle,
|
||||
handleMineSentenceDigit,
|
||||
handleMultiCopyDigit,
|
||||
mineSentenceCard,
|
||||
} from "./mining";
|
||||
|
||||
test("copyCurrentSubtitleService reports tracker and subtitle guards", () => {
|
||||
test("copyCurrentSubtitle reports tracker and subtitle guards", () => {
|
||||
const osd: string[] = [];
|
||||
const copied: string[] = [];
|
||||
|
||||
copyCurrentSubtitleService({
|
||||
copyCurrentSubtitle({
|
||||
subtitleTimingTracker: null,
|
||||
writeClipboardText: (text) => copied.push(text),
|
||||
showMpvOsd: (text) => osd.push(text),
|
||||
});
|
||||
assert.equal(osd.at(-1), "Subtitle tracker not available");
|
||||
|
||||
copyCurrentSubtitleService({
|
||||
copyCurrentSubtitle({
|
||||
subtitleTimingTracker: {
|
||||
getRecentBlocks: () => [],
|
||||
getCurrentSubtitle: () => null,
|
||||
@@ -31,11 +31,11 @@ test("copyCurrentSubtitleService reports tracker and subtitle guards", () => {
|
||||
assert.deepEqual(copied, []);
|
||||
});
|
||||
|
||||
test("copyCurrentSubtitleService copies current subtitle text", () => {
|
||||
test("copyCurrentSubtitle copies current subtitle text", () => {
|
||||
const osd: string[] = [];
|
||||
const copied: string[] = [];
|
||||
|
||||
copyCurrentSubtitleService({
|
||||
copyCurrentSubtitle({
|
||||
subtitleTimingTracker: {
|
||||
getRecentBlocks: () => [],
|
||||
getCurrentSubtitle: () => "hello world",
|
||||
@@ -49,11 +49,11 @@ test("copyCurrentSubtitleService copies current subtitle text", () => {
|
||||
assert.equal(osd.at(-1), "Copied subtitle");
|
||||
});
|
||||
|
||||
test("mineSentenceCardService handles missing integration and disconnected mpv", async () => {
|
||||
test("mineSentenceCard handles missing integration and disconnected mpv", async () => {
|
||||
const osd: string[] = [];
|
||||
|
||||
assert.equal(
|
||||
await mineSentenceCardService({
|
||||
await mineSentenceCard({
|
||||
ankiIntegration: null,
|
||||
mpvClient: null,
|
||||
showMpvOsd: (text) => osd.push(text),
|
||||
@@ -63,7 +63,7 @@ test("mineSentenceCardService handles missing integration and disconnected mpv",
|
||||
assert.equal(osd.at(-1), "AnkiConnect integration not enabled");
|
||||
|
||||
assert.equal(
|
||||
await mineSentenceCardService({
|
||||
await mineSentenceCard({
|
||||
ankiIntegration: {
|
||||
updateLastAddedFromClipboard: async () => {},
|
||||
triggerFieldGroupingForLastAddedCard: async () => {},
|
||||
@@ -84,7 +84,7 @@ test("mineSentenceCardService handles missing integration and disconnected mpv",
|
||||
assert.equal(osd.at(-1), "MPV not connected");
|
||||
});
|
||||
|
||||
test("mineSentenceCardService creates sentence card from mpv subtitle state", async () => {
|
||||
test("mineSentenceCard creates sentence card from mpv subtitle state", async () => {
|
||||
const created: Array<{
|
||||
sentence: string;
|
||||
startTime: number;
|
||||
@@ -92,7 +92,7 @@ test("mineSentenceCardService creates sentence card from mpv subtitle state", as
|
||||
secondarySub?: string;
|
||||
}> = [];
|
||||
|
||||
const createdCard = await mineSentenceCardService({
|
||||
const createdCard = await mineSentenceCard({
|
||||
ankiIntegration: {
|
||||
updateLastAddedFromClipboard: async () => {},
|
||||
triggerFieldGroupingForLastAddedCard: async () => {},
|
||||
@@ -123,11 +123,11 @@ test("mineSentenceCardService creates sentence card from mpv subtitle state", as
|
||||
]);
|
||||
});
|
||||
|
||||
test("handleMultiCopyDigitService copies available history and reports truncation", () => {
|
||||
test("handleMultiCopyDigit copies available history and reports truncation", () => {
|
||||
const osd: string[] = [];
|
||||
const copied: string[] = [];
|
||||
|
||||
handleMultiCopyDigitService(5, {
|
||||
handleMultiCopyDigit(5, {
|
||||
subtitleTimingTracker: {
|
||||
getRecentBlocks: (count) => ["a", "b"].slice(0, count),
|
||||
getCurrentSubtitle: () => null,
|
||||
@@ -141,12 +141,12 @@ test("handleMultiCopyDigitService copies available history and reports truncatio
|
||||
assert.equal(osd.at(-1), "Only 2 lines available, copied 2");
|
||||
});
|
||||
|
||||
test("handleMineSentenceDigitService reports async create failures", async () => {
|
||||
test("handleMineSentenceDigit reports async create failures", async () => {
|
||||
const osd: string[] = [];
|
||||
const logs: Array<{ message: string; err: unknown }> = [];
|
||||
let cardsMined = 0;
|
||||
|
||||
handleMineSentenceDigitService(2, {
|
||||
handleMineSentenceDigit(2, {
|
||||
subtitleTimingTracker: {
|
||||
getRecentBlocks: () => ["one", "two"],
|
||||
getCurrentSubtitle: () => null,
|
||||
@@ -184,7 +184,7 @@ test("handleMineSentenceDigitService increments successful card count", async ()
|
||||
const osd: string[] = [];
|
||||
let cardsMined = 0;
|
||||
|
||||
handleMineSentenceDigitService(2, {
|
||||
handleMineSentenceDigit(2, {
|
||||
subtitleTimingTracker: {
|
||||
getRecentBlocks: () => ["one", "two"],
|
||||
getCurrentSubtitle: () => null,
|
||||
@@ -24,7 +24,7 @@ interface MpvClientLike {
|
||||
currentSecondarySubText?: string;
|
||||
}
|
||||
|
||||
export function handleMultiCopyDigitService(
|
||||
export function handleMultiCopyDigit(
|
||||
count: number,
|
||||
deps: {
|
||||
subtitleTimingTracker: SubtitleTimingTrackerLike | null;
|
||||
@@ -50,7 +50,7 @@ export function handleMultiCopyDigitService(
|
||||
}
|
||||
}
|
||||
|
||||
export function copyCurrentSubtitleService(deps: {
|
||||
export function copyCurrentSubtitle(deps: {
|
||||
subtitleTimingTracker: SubtitleTimingTrackerLike | null;
|
||||
writeClipboardText: (text: string) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
@@ -79,7 +79,7 @@ function requireAnkiIntegration(
|
||||
return ankiIntegration;
|
||||
}
|
||||
|
||||
export async function updateLastCardFromClipboardService(deps: {
|
||||
export async function updateLastCardFromClipboard(deps: {
|
||||
ankiIntegration: AnkiIntegrationLike | null;
|
||||
readClipboardText: () => string;
|
||||
showMpvOsd: (text: string) => void;
|
||||
@@ -89,7 +89,7 @@ export async function updateLastCardFromClipboardService(deps: {
|
||||
await anki.updateLastAddedFromClipboard(deps.readClipboardText());
|
||||
}
|
||||
|
||||
export async function triggerFieldGroupingService(deps: {
|
||||
export async function triggerFieldGrouping(deps: {
|
||||
ankiIntegration: AnkiIntegrationLike | null;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}): Promise<void> {
|
||||
@@ -98,7 +98,7 @@ export async function triggerFieldGroupingService(deps: {
|
||||
await anki.triggerFieldGroupingForLastAddedCard();
|
||||
}
|
||||
|
||||
export async function markLastCardAsAudioCardService(deps: {
|
||||
export async function markLastCardAsAudioCard(deps: {
|
||||
ankiIntegration: AnkiIntegrationLike | null;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}): Promise<void> {
|
||||
@@ -107,7 +107,7 @@ export async function markLastCardAsAudioCardService(deps: {
|
||||
await anki.markLastCardAsAudioCard();
|
||||
}
|
||||
|
||||
export async function mineSentenceCardService(deps: {
|
||||
export async function mineSentenceCard(deps: {
|
||||
ankiIntegration: AnkiIntegrationLike | null;
|
||||
mpvClient: MpvClientLike | null;
|
||||
showMpvOsd: (text: string) => void;
|
||||
@@ -133,7 +133,7 @@ export async function mineSentenceCardService(deps: {
|
||||
);
|
||||
}
|
||||
|
||||
export function handleMineSentenceDigitService(
|
||||
export function handleMineSentenceDigit(
|
||||
count: number,
|
||||
deps: {
|
||||
subtitleTimingTracker: SubtitleTimingTrackerLike | null;
|
||||
@@ -1,16 +1,16 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
playNextSubtitleRuntimeService,
|
||||
replayCurrentSubtitleRuntimeService,
|
||||
sendMpvCommandRuntimeService,
|
||||
setMpvSubVisibilityRuntimeService,
|
||||
showMpvOsdRuntimeService,
|
||||
} from "./mpv-service";
|
||||
playNextSubtitleRuntime,
|
||||
replayCurrentSubtitleRuntime,
|
||||
sendMpvCommandRuntime,
|
||||
setMpvSubVisibilityRuntime,
|
||||
showMpvOsdRuntime,
|
||||
} from "./mpv";
|
||||
|
||||
test("showMpvOsdRuntimeService sends show-text when connected", () => {
|
||||
test("showMpvOsdRuntime sends show-text when connected", () => {
|
||||
const commands: (string | number)[][] = [];
|
||||
showMpvOsdRuntimeService(
|
||||
showMpvOsdRuntime(
|
||||
{
|
||||
connected: true,
|
||||
send: ({ command }) => {
|
||||
@@ -22,9 +22,9 @@ test("showMpvOsdRuntimeService sends show-text when connected", () => {
|
||||
assert.deepEqual(commands, [["show-text", "hello", "3000"]]);
|
||||
});
|
||||
|
||||
test("showMpvOsdRuntimeService logs fallback when disconnected", () => {
|
||||
test("showMpvOsdRuntime logs fallback when disconnected", () => {
|
||||
const logs: string[] = [];
|
||||
showMpvOsdRuntimeService(
|
||||
showMpvOsdRuntime(
|
||||
{
|
||||
connected: false,
|
||||
send: () => {},
|
||||
@@ -55,10 +55,10 @@ test("mpv runtime command wrappers call expected client methods", () => {
|
||||
},
|
||||
};
|
||||
|
||||
replayCurrentSubtitleRuntimeService(client);
|
||||
playNextSubtitleRuntimeService(client);
|
||||
sendMpvCommandRuntimeService(client, ["script-message", "x"]);
|
||||
setMpvSubVisibilityRuntimeService(client, false);
|
||||
replayCurrentSubtitleRuntime(client);
|
||||
playNextSubtitleRuntime(client);
|
||||
sendMpvCommandRuntime(client, ["script-message", "x"]);
|
||||
setMpvSubVisibilityRuntime(client, false);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
"replay",
|
||||
@@ -1,25 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { MpvSubtitleRenderMetrics } from "../../types";
|
||||
import {
|
||||
applyMpvSubtitleRenderMetricsPatchService,
|
||||
DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
} from "./mpv-render-metrics-service";
|
||||
|
||||
const BASE: MpvSubtitleRenderMetrics = {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
};
|
||||
|
||||
test("applyMpvSubtitleRenderMetricsPatchService returns unchanged on empty patch", () => {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatchService(BASE, {});
|
||||
assert.equal(changed, false);
|
||||
assert.deepEqual(next, BASE);
|
||||
});
|
||||
|
||||
test("applyMpvSubtitleRenderMetricsPatchService reports changed when patch modifies value", () => {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatchService(BASE, {
|
||||
subPos: 95,
|
||||
});
|
||||
assert.equal(changed, true);
|
||||
assert.equal(next.subPos, 95);
|
||||
});
|
||||
25
src/core/services/mpv-render-metrics.test.ts
Normal file
25
src/core/services/mpv-render-metrics.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { MpvSubtitleRenderMetrics } from "../../types";
|
||||
import {
|
||||
applyMpvSubtitleRenderMetricsPatch,
|
||||
DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
} from "./mpv-render-metrics";
|
||||
|
||||
const BASE: MpvSubtitleRenderMetrics = {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
};
|
||||
|
||||
test("applyMpvSubtitleRenderMetricsPatch returns unchanged on empty patch", () => {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatch(BASE, {});
|
||||
assert.equal(changed, false);
|
||||
assert.deepEqual(next, BASE);
|
||||
});
|
||||
|
||||
test("applyMpvSubtitleRenderMetricsPatch reports changed when patch modifies value", () => {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatch(BASE, {
|
||||
subPos: 95,
|
||||
});
|
||||
assert.equal(changed, true);
|
||||
assert.equal(next.subPos, 95);
|
||||
});
|
||||
@@ -25,10 +25,10 @@ export function sanitizeMpvSubtitleRenderMetrics(
|
||||
patch: Partial<MpvSubtitleRenderMetrics> | null | undefined,
|
||||
): MpvSubtitleRenderMetrics {
|
||||
if (!patch) return current;
|
||||
return updateMpvSubtitleRenderMetricsService(current, patch);
|
||||
return updateMpvSubtitleRenderMetrics(current, patch);
|
||||
}
|
||||
|
||||
export function updateMpvSubtitleRenderMetricsService(
|
||||
export function updateMpvSubtitleRenderMetrics(
|
||||
current: MpvSubtitleRenderMetrics,
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
): MpvSubtitleRenderMetrics {
|
||||
@@ -83,11 +83,11 @@ export function updateMpvSubtitleRenderMetricsService(
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMpvSubtitleRenderMetricsPatchService(
|
||||
export function applyMpvSubtitleRenderMetricsPatch(
|
||||
current: MpvSubtitleRenderMetrics,
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
): { next: MpvSubtitleRenderMetrics; changed: boolean } {
|
||||
const next = updateMpvSubtitleRenderMetricsService(current, patch);
|
||||
const next = updateMpvSubtitleRenderMetrics(current, patch);
|
||||
const changed =
|
||||
next.subPos !== current.subPos ||
|
||||
next.subFontSize !== current.subFontSize ||
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveCurrentAudioStreamIndex } from "./mpv-service";
|
||||
import { resolveCurrentAudioStreamIndex } from "./mpv";
|
||||
|
||||
test("resolveCurrentAudioStreamIndex returns selected ff-index when no current track id", () => {
|
||||
assert.equal(
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MpvIpcClientDeps,
|
||||
MpvIpcClientProtocolDeps,
|
||||
MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY,
|
||||
} from "./mpv-service";
|
||||
} from "./mpv";
|
||||
import { MPV_REQUEST_ID_TRACK_LIST_AUDIO } from "./mpv-protocol";
|
||||
|
||||
function makeDeps(
|
||||
@@ -55,7 +55,7 @@ export interface MpvRuntimeClientLike {
|
||||
setSubVisibility?: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
export function showMpvOsdRuntimeService(
|
||||
export function showMpvOsdRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
text: string,
|
||||
fallbackLog: (text: string) => void = (line) => logger.info(line),
|
||||
@@ -67,21 +67,21 @@ export function showMpvOsdRuntimeService(
|
||||
fallbackLog(`OSD (MPV not connected): ${text}`);
|
||||
}
|
||||
|
||||
export function replayCurrentSubtitleRuntimeService(
|
||||
export function replayCurrentSubtitleRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
): void {
|
||||
if (!mpvClient?.replayCurrentSubtitle) return;
|
||||
mpvClient.replayCurrentSubtitle();
|
||||
}
|
||||
|
||||
export function playNextSubtitleRuntimeService(
|
||||
export function playNextSubtitleRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
): void {
|
||||
if (!mpvClient?.playNextSubtitle) return;
|
||||
mpvClient.playNextSubtitle();
|
||||
}
|
||||
|
||||
export function sendMpvCommandRuntimeService(
|
||||
export function sendMpvCommandRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
command: (string | number)[],
|
||||
): void {
|
||||
@@ -89,7 +89,7 @@ export function sendMpvCommandRuntimeService(
|
||||
mpvClient.send({ command });
|
||||
}
|
||||
|
||||
export function setMpvSubVisibilityRuntimeService(
|
||||
export function setMpvSubVisibilityRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
visible: boolean,
|
||||
): void {
|
||||
@@ -1,17 +1,17 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
createNumericShortcutRuntimeService,
|
||||
createNumericShortcutSessionService,
|
||||
} from "./numeric-shortcut-service";
|
||||
createNumericShortcutRuntime,
|
||||
createNumericShortcutSession,
|
||||
} from "./numeric-shortcut";
|
||||
|
||||
test("createNumericShortcutRuntimeService creates sessions wired to globalShortcut", () => {
|
||||
test("createNumericShortcutRuntime creates sessions wired to globalShortcut", () => {
|
||||
const registered: string[] = [];
|
||||
const unregistered: string[] = [];
|
||||
const osd: string[] = [];
|
||||
const handlers = new Map<string, () => void>();
|
||||
|
||||
const runtime = createNumericShortcutRuntimeService({
|
||||
const runtime = createNumericShortcutRuntime({
|
||||
globalShortcut: {
|
||||
register: (accelerator, callback) => {
|
||||
registered.push(accelerator);
|
||||
@@ -54,7 +54,7 @@ test("numeric shortcut session handles digit selection and unregisters shortcuts
|
||||
const handlers = new Map<string, () => void>();
|
||||
const unregistered: string[] = [];
|
||||
const osd: string[] = [];
|
||||
const session = createNumericShortcutSessionService({
|
||||
const session = createNumericShortcutSession({
|
||||
registerShortcut: (accelerator, handler) => {
|
||||
handlers.set(accelerator, handler);
|
||||
return true;
|
||||
@@ -96,7 +96,7 @@ test("numeric shortcut session handles digit selection and unregisters shortcuts
|
||||
|
||||
test("numeric shortcut session emits timeout message", () => {
|
||||
const osd: string[] = [];
|
||||
const session = createNumericShortcutSessionService({
|
||||
const session = createNumericShortcutSession({
|
||||
registerShortcut: () => true,
|
||||
unregisterShortcut: () => {},
|
||||
setTimer: (handler) => {
|
||||
@@ -126,7 +126,7 @@ test("numeric shortcut session emits timeout message", () => {
|
||||
test("numeric shortcut session handles escape cancellation", () => {
|
||||
const handlers = new Map<string, () => void>();
|
||||
const osd: string[] = [];
|
||||
const session = createNumericShortcutSessionService({
|
||||
const session = createNumericShortcutSession({
|
||||
registerShortcut: (accelerator, handler) => {
|
||||
handlers.set(accelerator, handler);
|
||||
return true;
|
||||
@@ -13,11 +13,11 @@ export interface NumericShortcutRuntimeOptions {
|
||||
clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
|
||||
}
|
||||
|
||||
export function createNumericShortcutRuntimeService(
|
||||
export function createNumericShortcutRuntime(
|
||||
options: NumericShortcutRuntimeOptions,
|
||||
) {
|
||||
const createSession = () =>
|
||||
createNumericShortcutSessionService({
|
||||
createNumericShortcutSession({
|
||||
registerShortcut: (accelerator, handler) =>
|
||||
options.globalShortcut.register(accelerator, handler),
|
||||
unregisterShortcut: (accelerator) =>
|
||||
@@ -52,7 +52,7 @@ export interface NumericShortcutSessionStartParams {
|
||||
messages: NumericShortcutSessionMessages;
|
||||
}
|
||||
|
||||
export function createNumericShortcutSessionService(
|
||||
export function createNumericShortcutSession(
|
||||
deps: NumericShortcutSessionDeps,
|
||||
) {
|
||||
let active = false;
|
||||
@@ -2,16 +2,16 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { KikuFieldGroupingChoice } from "../../types";
|
||||
import {
|
||||
createFieldGroupingCallbackRuntimeService,
|
||||
sendToVisibleOverlayRuntimeService,
|
||||
} from "./overlay-bridge-service";
|
||||
createFieldGroupingCallbackRuntime,
|
||||
sendToVisibleOverlayRuntime,
|
||||
} from "./overlay-bridge";
|
||||
|
||||
test("sendToVisibleOverlayRuntimeService restores visibility flag when opening hidden overlay modal", () => {
|
||||
test("sendToVisibleOverlayRuntime restores visibility flag when opening hidden overlay modal", () => {
|
||||
const sent: unknown[][] = [];
|
||||
const restoreSet = new Set<"runtime-options" | "subsync">();
|
||||
let visibleOverlayVisible = false;
|
||||
|
||||
const ok = sendToVisibleOverlayRuntimeService({
|
||||
const ok = sendToVisibleOverlayRuntime({
|
||||
mainWindow: {
|
||||
isDestroyed: () => false,
|
||||
webContents: {
|
||||
@@ -36,9 +36,9 @@ test("sendToVisibleOverlayRuntimeService restores visibility flag when opening h
|
||||
assert.deepEqual(sent, [["runtime-options:open"]]);
|
||||
});
|
||||
|
||||
test("createFieldGroupingCallbackRuntimeService cancels when overlay request cannot be sent", async () => {
|
||||
test("createFieldGroupingCallbackRuntime cancels when overlay request cannot be sent", async () => {
|
||||
let resolver: ((choice: KikuFieldGroupingChoice) => void) | null = null;
|
||||
const callback = createFieldGroupingCallbackRuntimeService<
|
||||
const callback = createFieldGroupingCallbackRuntime<
|
||||
"runtime-options" | "subsync"
|
||||
>({
|
||||
getVisibleOverlayVisible: () => false,
|
||||
@@ -2,10 +2,10 @@ import {
|
||||
KikuFieldGroupingChoice,
|
||||
KikuFieldGroupingRequestData,
|
||||
} from "../../types";
|
||||
import { createFieldGroupingCallbackService } from "./field-grouping-service";
|
||||
import { createFieldGroupingCallback } from "./field-grouping";
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
export function sendToVisibleOverlayRuntimeService<T extends string>(options: {
|
||||
export function sendToVisibleOverlayRuntime<T extends string>(options: {
|
||||
mainWindow: BrowserWindow | null;
|
||||
visibleOverlayVisible: boolean;
|
||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||
@@ -45,7 +45,7 @@ export function sendToVisibleOverlayRuntimeService<T extends string>(options: {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function createFieldGroupingCallbackRuntimeService<T extends string>(
|
||||
export function createFieldGroupingCallbackRuntime<T extends string>(
|
||||
options: {
|
||||
getVisibleOverlayVisible: () => boolean;
|
||||
getInvisibleOverlayVisible: () => boolean;
|
||||
@@ -62,7 +62,7 @@ export function createFieldGroupingCallbackRuntimeService<T extends string>(
|
||||
) => boolean;
|
||||
},
|
||||
): (data: KikuFieldGroupingRequestData) => Promise<KikuFieldGroupingChoice> {
|
||||
return createFieldGroupingCallbackService({
|
||||
return createFieldGroupingCallback({
|
||||
getVisibleOverlayVisible: options.getVisibleOverlayVisible,
|
||||
getInvisibleOverlayVisible: options.getInvisibleOverlayVisible,
|
||||
setVisibleOverlayVisible: options.setVisibleOverlayVisible,
|
||||
@@ -2,9 +2,9 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createOverlayContentMeasurementStoreService,
|
||||
createOverlayContentMeasurementStore,
|
||||
sanitizeOverlayContentMeasurement,
|
||||
} from "./overlay-content-measurement-service";
|
||||
} from "./overlay-content-measurement";
|
||||
|
||||
test("sanitizeOverlayContentMeasurement accepts valid payload with null rect", () => {
|
||||
const measurement = sanitizeOverlayContentMeasurement(
|
||||
@@ -40,7 +40,7 @@ test("sanitizeOverlayContentMeasurement rejects invalid ranges", () => {
|
||||
});
|
||||
|
||||
test("overlay measurement store keeps latest payload per layer", () => {
|
||||
const store = createOverlayContentMeasurementStoreService({
|
||||
const store = createOverlayContentMeasurementStore({
|
||||
now: () => 1000,
|
||||
warn: () => {
|
||||
// noop
|
||||
@@ -69,7 +69,7 @@ test("overlay measurement store keeps latest payload per layer", () => {
|
||||
test("overlay measurement store rate-limits invalid payload warnings", () => {
|
||||
let now = 1_000;
|
||||
const warnings: string[] = [];
|
||||
const store = createOverlayContentMeasurementStoreService({
|
||||
const store = createOverlayContentMeasurementStore({
|
||||
now: () => now,
|
||||
warn: (message) => {
|
||||
warnings.push(message);
|
||||
@@ -105,7 +105,7 @@ function readFiniteInRange(
|
||||
return value;
|
||||
}
|
||||
|
||||
export function createOverlayContentMeasurementStoreService(options?: {
|
||||
export function createOverlayContentMeasurementStore(options?: {
|
||||
now?: () => number;
|
||||
warn?: (message: string) => void;
|
||||
}) {
|
||||
@@ -1,13 +1,13 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
broadcastRuntimeOptionsChangedRuntimeService,
|
||||
createOverlayManagerService,
|
||||
setOverlayDebugVisualizationEnabledRuntimeService,
|
||||
} from "./overlay-manager-service";
|
||||
broadcastRuntimeOptionsChangedRuntime,
|
||||
createOverlayManager,
|
||||
setOverlayDebugVisualizationEnabledRuntime,
|
||||
} from "./overlay-manager";
|
||||
|
||||
test("overlay manager initializes with empty windows and hidden overlays", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
assert.equal(manager.getMainWindow(), null);
|
||||
assert.equal(manager.getInvisibleWindow(), null);
|
||||
assert.equal(manager.getVisibleOverlayVisible(), false);
|
||||
@@ -16,7 +16,7 @@ test("overlay manager initializes with empty windows and hidden overlays", () =>
|
||||
});
|
||||
|
||||
test("overlay manager stores window references and returns stable window order", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
const visibleWindow = { isDestroyed: () => false } as unknown as Electron.BrowserWindow;
|
||||
const invisibleWindow = { isDestroyed: () => false } as unknown as Electron.BrowserWindow;
|
||||
|
||||
@@ -31,7 +31,7 @@ test("overlay manager stores window references and returns stable window order",
|
||||
});
|
||||
|
||||
test("overlay manager excludes destroyed windows", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
manager.setMainWindow({ isDestroyed: () => true } as unknown as Electron.BrowserWindow);
|
||||
manager.setInvisibleWindow({ isDestroyed: () => false } as unknown as Electron.BrowserWindow);
|
||||
|
||||
@@ -39,7 +39,7 @@ test("overlay manager excludes destroyed windows", () => {
|
||||
});
|
||||
|
||||
test("overlay manager stores visibility state", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
|
||||
manager.setVisibleOverlayVisible(true);
|
||||
manager.setInvisibleOverlayVisible(true);
|
||||
@@ -48,7 +48,7 @@ test("overlay manager stores visibility state", () => {
|
||||
});
|
||||
|
||||
test("overlay manager broadcasts to non-destroyed windows", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
const calls: unknown[][] = [];
|
||||
const aliveWindow = {
|
||||
isDestroyed: () => false,
|
||||
@@ -73,7 +73,7 @@ test("overlay manager broadcasts to non-destroyed windows", () => {
|
||||
});
|
||||
|
||||
test("overlay manager applies bounds by layer", () => {
|
||||
const manager = createOverlayManagerService();
|
||||
const manager = createOverlayManager();
|
||||
const visibleCalls: Electron.Rectangle[] = [];
|
||||
const invisibleCalls: Electron.Rectangle[] = [];
|
||||
const visibleWindow = {
|
||||
@@ -110,14 +110,14 @@ test("overlay manager applies bounds by layer", () => {
|
||||
|
||||
test("runtime-option and debug broadcasts use expected channels", () => {
|
||||
const broadcasts: unknown[][] = [];
|
||||
broadcastRuntimeOptionsChangedRuntimeService(
|
||||
broadcastRuntimeOptionsChangedRuntime(
|
||||
() => [],
|
||||
(channel, ...args) => {
|
||||
broadcasts.push([channel, ...args]);
|
||||
},
|
||||
);
|
||||
let state = false;
|
||||
const changed = setOverlayDebugVisualizationEnabledRuntimeService(
|
||||
const changed = setOverlayDebugVisualizationEnabledRuntime(
|
||||
state,
|
||||
true,
|
||||
(enabled) => {
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { RuntimeOptionState, WindowGeometry } from "../../types";
|
||||
import { updateOverlayWindowBoundsService } from "./overlay-window-service";
|
||||
import { updateOverlayWindowBounds } from "./overlay-window";
|
||||
|
||||
type OverlayLayer = "visible" | "invisible";
|
||||
|
||||
export interface OverlayManagerService {
|
||||
export interface OverlayManager {
|
||||
getMainWindow: () => BrowserWindow | null;
|
||||
setMainWindow: (window: BrowserWindow | null) => void;
|
||||
getInvisibleWindow: () => BrowserWindow | null;
|
||||
@@ -19,7 +19,7 @@ export interface OverlayManagerService {
|
||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void;
|
||||
}
|
||||
|
||||
export function createOverlayManagerService(): OverlayManagerService {
|
||||
export function createOverlayManager(): OverlayManager {
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let invisibleWindow: BrowserWindow | null = null;
|
||||
let visibleOverlayVisible = false;
|
||||
@@ -37,7 +37,7 @@ export function createOverlayManagerService(): OverlayManagerService {
|
||||
getOverlayWindow: (layer) =>
|
||||
layer === "visible" ? mainWindow : invisibleWindow,
|
||||
setOverlayWindowBounds: (layer, geometry) => {
|
||||
updateOverlayWindowBoundsService(
|
||||
updateOverlayWindowBounds(
|
||||
geometry,
|
||||
layer === "visible" ? mainWindow : invisibleWindow,
|
||||
);
|
||||
@@ -75,14 +75,14 @@ export function createOverlayManagerService(): OverlayManagerService {
|
||||
};
|
||||
}
|
||||
|
||||
export function broadcastRuntimeOptionsChangedRuntimeService(
|
||||
export function broadcastRuntimeOptionsChangedRuntime(
|
||||
getRuntimeOptionsState: () => RuntimeOptionState[],
|
||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void,
|
||||
): void {
|
||||
broadcastToOverlayWindows("runtime-options:changed", getRuntimeOptionsState());
|
||||
}
|
||||
|
||||
export function setOverlayDebugVisualizationEnabledRuntimeService(
|
||||
export function setOverlayDebugVisualizationEnabledRuntime(
|
||||
currentEnabled: boolean,
|
||||
nextEnabled: boolean,
|
||||
setState: (enabled: boolean) => void,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
WindowGeometry,
|
||||
} from "../../types";
|
||||
|
||||
export function initializeOverlayRuntimeService(options: {
|
||||
export function initializeOverlayRuntime(options: {
|
||||
backendOverride: string | null;
|
||||
getInitialInvisibleOverlayVisibility: () => boolean;
|
||||
createMainWindow: () => void;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfiguredShortcuts } from "../utils/shortcut-config";
|
||||
import { OverlayShortcutHandlers } from "./overlay-shortcut-service";
|
||||
import { OverlayShortcutHandlers } from "./overlay-shortcut";
|
||||
import { createLogger } from "../../logger";
|
||||
|
||||
const logger = createLogger("main:overlay-shortcut-handler");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { globalShortcut } from "electron";
|
||||
import { ConfiguredShortcuts } from "../utils/shortcut-config";
|
||||
import { isGlobalShortcutRegisteredSafe } from "./shortcut-fallback-service";
|
||||
import { isGlobalShortcutRegisteredSafe } from "./shortcut-fallback";
|
||||
import { createLogger } from "../../logger";
|
||||
|
||||
const logger = createLogger("main:overlay-shortcut-service");
|
||||
@@ -26,7 +26,7 @@ export interface OverlayShortcutLifecycleDeps {
|
||||
cancelPendingMineSentenceMultiple: () => void;
|
||||
}
|
||||
|
||||
export function registerOverlayShortcutsService(
|
||||
export function registerOverlayShortcuts(
|
||||
shortcuts: ConfiguredShortcuts,
|
||||
handlers: OverlayShortcutHandlers,
|
||||
): boolean {
|
||||
@@ -140,7 +140,7 @@ export function registerOverlayShortcutsService(
|
||||
return registeredAny;
|
||||
}
|
||||
|
||||
export function unregisterOverlayShortcutsService(
|
||||
export function unregisterOverlayShortcuts(
|
||||
shortcuts: ConfiguredShortcuts,
|
||||
): void {
|
||||
if (shortcuts.copySubtitle) {
|
||||
@@ -178,45 +178,45 @@ export function unregisterOverlayShortcutsService(
|
||||
}
|
||||
}
|
||||
|
||||
export function registerOverlayShortcutsRuntimeService(
|
||||
export function registerOverlayShortcutsRuntime(
|
||||
deps: OverlayShortcutLifecycleDeps,
|
||||
): boolean {
|
||||
return registerOverlayShortcutsService(
|
||||
return registerOverlayShortcuts(
|
||||
deps.getConfiguredShortcuts(),
|
||||
deps.getOverlayHandlers(),
|
||||
);
|
||||
}
|
||||
|
||||
export function unregisterOverlayShortcutsRuntimeService(
|
||||
export function unregisterOverlayShortcutsRuntime(
|
||||
shortcutsRegistered: boolean,
|
||||
deps: OverlayShortcutLifecycleDeps,
|
||||
): boolean {
|
||||
if (!shortcutsRegistered) return shortcutsRegistered;
|
||||
deps.cancelPendingMultiCopy();
|
||||
deps.cancelPendingMineSentenceMultiple();
|
||||
unregisterOverlayShortcutsService(deps.getConfiguredShortcuts());
|
||||
unregisterOverlayShortcuts(deps.getConfiguredShortcuts());
|
||||
return false;
|
||||
}
|
||||
|
||||
export function syncOverlayShortcutsRuntimeService(
|
||||
export function syncOverlayShortcutsRuntime(
|
||||
shouldBeActive: boolean,
|
||||
shortcutsRegistered: boolean,
|
||||
deps: OverlayShortcutLifecycleDeps,
|
||||
): boolean {
|
||||
if (shouldBeActive) {
|
||||
return registerOverlayShortcutsRuntimeService(deps);
|
||||
return registerOverlayShortcutsRuntime(deps);
|
||||
}
|
||||
return unregisterOverlayShortcutsRuntimeService(shortcutsRegistered, deps);
|
||||
return unregisterOverlayShortcutsRuntime(shortcutsRegistered, deps);
|
||||
}
|
||||
|
||||
export function refreshOverlayShortcutsRuntimeService(
|
||||
export function refreshOverlayShortcutsRuntime(
|
||||
shouldBeActive: boolean,
|
||||
shortcutsRegistered: boolean,
|
||||
deps: OverlayShortcutLifecycleDeps,
|
||||
): boolean {
|
||||
const cleared = unregisterOverlayShortcutsRuntimeService(
|
||||
const cleared = unregisterOverlayShortcutsRuntime(
|
||||
shortcutsRegistered,
|
||||
deps,
|
||||
);
|
||||
return syncOverlayShortcutsRuntimeService(shouldBeActive, cleared, deps);
|
||||
return syncOverlayShortcutsRuntime(shouldBeActive, cleared, deps);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { BrowserWindow, screen } from "electron";
|
||||
import { BaseWindowTracker } from "../../window-trackers";
|
||||
import { WindowGeometry } from "../../types";
|
||||
|
||||
export function updateVisibleOverlayVisibilityService(args: {
|
||||
export function updateVisibleOverlayVisibility(args: {
|
||||
visibleOverlayVisible: boolean;
|
||||
mainWindow: BrowserWindow | null;
|
||||
windowTracker: BaseWindowTracker | null;
|
||||
@@ -66,7 +66,7 @@ export function updateVisibleOverlayVisibilityService(args: {
|
||||
args.syncOverlayShortcuts();
|
||||
}
|
||||
|
||||
export function updateInvisibleOverlayVisibilityService(args: {
|
||||
export function updateInvisibleOverlayVisibility(args: {
|
||||
invisibleWindow: BrowserWindow | null;
|
||||
visibleOverlayVisible: boolean;
|
||||
invisibleOverlayVisible: boolean;
|
||||
@@ -131,7 +131,7 @@ export function updateInvisibleOverlayVisibilityService(args: {
|
||||
args.syncOverlayShortcuts();
|
||||
}
|
||||
|
||||
export function syncInvisibleOverlayMousePassthroughService(options: {
|
||||
export function syncInvisibleOverlayMousePassthrough(options: {
|
||||
hasInvisibleWindow: () => boolean;
|
||||
setIgnoreMouseEvents: (ignore: boolean, extra?: { forward: boolean }) => void;
|
||||
visibleOverlayVisible: boolean;
|
||||
@@ -145,7 +145,7 @@ export function syncInvisibleOverlayMousePassthroughService(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function setVisibleOverlayVisibleService(options: {
|
||||
export function setVisibleOverlayVisible(options: {
|
||||
visible: boolean;
|
||||
setVisibleOverlayVisibleState: (visible: boolean) => void;
|
||||
updateVisibleOverlayVisibility: () => void;
|
||||
@@ -167,7 +167,7 @@ export function setVisibleOverlayVisibleService(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function setInvisibleOverlayVisibleService(options: {
|
||||
export function setInvisibleOverlayVisible(options: {
|
||||
visible: boolean;
|
||||
setInvisibleOverlayVisibleState: (visible: boolean) => void;
|
||||
updateInvisibleOverlayVisibility: () => void;
|
||||
@@ -7,7 +7,7 @@ const logger = createLogger("main:overlay-window");
|
||||
|
||||
export type OverlayWindowKind = "visible" | "invisible";
|
||||
|
||||
export function updateOverlayWindowBoundsService(
|
||||
export function updateOverlayWindowBounds(
|
||||
geometry: WindowGeometry,
|
||||
window: BrowserWindow | null,
|
||||
): void {
|
||||
@@ -20,7 +20,7 @@ export function updateOverlayWindowBoundsService(
|
||||
});
|
||||
}
|
||||
|
||||
export function ensureOverlayWindowLevelService(window: BrowserWindow): void {
|
||||
export function ensureOverlayWindowLevel(window: BrowserWindow): void {
|
||||
if (process.platform === "darwin") {
|
||||
window.setAlwaysOnTop(true, "screen-saver", 1);
|
||||
window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||
@@ -30,7 +30,7 @@ export function ensureOverlayWindowLevelService(window: BrowserWindow): void {
|
||||
window.setAlwaysOnTop(true);
|
||||
}
|
||||
|
||||
export function enforceOverlayLayerOrderService(options: {
|
||||
export function enforceOverlayLayerOrder(options: {
|
||||
visibleOverlayVisible: boolean;
|
||||
invisibleOverlayVisible: boolean;
|
||||
mainWindow: BrowserWindow | null;
|
||||
@@ -45,7 +45,7 @@ export function enforceOverlayLayerOrderService(options: {
|
||||
options.mainWindow.moveTop();
|
||||
}
|
||||
|
||||
export function createOverlayWindowService(
|
||||
export function createOverlayWindow(
|
||||
kind: OverlayWindowKind,
|
||||
options: {
|
||||
isDev: boolean;
|
||||
@@ -1,98 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
getInitialInvisibleOverlayVisibilityService,
|
||||
isAutoUpdateEnabledRuntimeService,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService,
|
||||
shouldBindVisibleOverlayToMpvSubVisibilityService,
|
||||
} from "./startup-service";
|
||||
|
||||
const BASE_CONFIG = {
|
||||
auto_start_overlay: false,
|
||||
bind_visible_overlay_to_mpv_sub_visibility: true,
|
||||
invisibleOverlay: {
|
||||
startupVisibility: "platform-default" as const,
|
||||
},
|
||||
ankiConnect: {
|
||||
behavior: {
|
||||
autoUpdateNewCards: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test("getInitialInvisibleOverlayVisibilityService handles visibility + platform", () => {
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibilityService(
|
||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: "visible" } },
|
||||
"linux",
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibilityService(
|
||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: "hidden" } },
|
||||
"darwin",
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibilityService(BASE_CONFIG, "linux"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibilityService(BASE_CONFIG, "darwin"),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldAutoInitializeOverlayRuntimeFromConfigService respects auto start and visible startup", () => {
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService(BASE_CONFIG),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService({
|
||||
...BASE_CONFIG,
|
||||
auto_start_overlay: true,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService({
|
||||
...BASE_CONFIG,
|
||||
invisibleOverlay: { startupVisibility: "visible" },
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldBindVisibleOverlayToMpvSubVisibilityService returns config value", () => {
|
||||
assert.equal(shouldBindVisibleOverlayToMpvSubVisibilityService(BASE_CONFIG), true);
|
||||
assert.equal(
|
||||
shouldBindVisibleOverlayToMpvSubVisibilityService({
|
||||
...BASE_CONFIG,
|
||||
bind_visible_overlay_to_mpv_sub_visibility: false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("isAutoUpdateEnabledRuntimeService prefers runtime option and falls back to config", () => {
|
||||
assert.equal(
|
||||
isAutoUpdateEnabledRuntimeService(BASE_CONFIG, {
|
||||
getOptionValue: () => false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isAutoUpdateEnabledRuntimeService(
|
||||
{
|
||||
...BASE_CONFIG,
|
||||
ankiConnect: { behavior: { autoUpdateNewCards: false } },
|
||||
},
|
||||
null,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(isAutoUpdateEnabledRuntimeService(BASE_CONFIG, null), true);
|
||||
});
|
||||
98
src/core/services/runtime-config.test.ts
Normal file
98
src/core/services/runtime-config.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
getInitialInvisibleOverlayVisibility,
|
||||
isAutoUpdateEnabledRuntime,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig,
|
||||
shouldBindVisibleOverlayToMpvSubVisibility,
|
||||
} from "./startup";
|
||||
|
||||
const BASE_CONFIG = {
|
||||
auto_start_overlay: false,
|
||||
bind_visible_overlay_to_mpv_sub_visibility: true,
|
||||
invisibleOverlay: {
|
||||
startupVisibility: "platform-default" as const,
|
||||
},
|
||||
ankiConnect: {
|
||||
behavior: {
|
||||
autoUpdateNewCards: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test("getInitialInvisibleOverlayVisibility handles visibility + platform", () => {
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibility(
|
||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: "visible" } },
|
||||
"linux",
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibility(
|
||||
{ ...BASE_CONFIG, invisibleOverlay: { startupVisibility: "hidden" } },
|
||||
"darwin",
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibility(BASE_CONFIG, "linux"),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
getInitialInvisibleOverlayVisibility(BASE_CONFIG, "darwin"),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldAutoInitializeOverlayRuntimeFromConfig respects auto start and visible startup", () => {
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig(BASE_CONFIG),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig({
|
||||
...BASE_CONFIG,
|
||||
auto_start_overlay: true,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig({
|
||||
...BASE_CONFIG,
|
||||
invisibleOverlay: { startupVisibility: "visible" },
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldBindVisibleOverlayToMpvSubVisibility returns config value", () => {
|
||||
assert.equal(shouldBindVisibleOverlayToMpvSubVisibility(BASE_CONFIG), true);
|
||||
assert.equal(
|
||||
shouldBindVisibleOverlayToMpvSubVisibility({
|
||||
...BASE_CONFIG,
|
||||
bind_visible_overlay_to_mpv_sub_visibility: false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("isAutoUpdateEnabledRuntime prefers runtime option and falls back to config", () => {
|
||||
assert.equal(
|
||||
isAutoUpdateEnabledRuntime(BASE_CONFIG, {
|
||||
getOptionValue: () => false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isAutoUpdateEnabledRuntime(
|
||||
{
|
||||
...BASE_CONFIG,
|
||||
ankiConnect: { behavior: { autoUpdateNewCards: false } },
|
||||
},
|
||||
null,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(isAutoUpdateEnabledRuntime(BASE_CONFIG, null), true);
|
||||
});
|
||||
@@ -1,14 +1,14 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
applyRuntimeOptionResultRuntimeService,
|
||||
cycleRuntimeOptionFromIpcRuntimeService,
|
||||
setRuntimeOptionFromIpcRuntimeService,
|
||||
} from "./runtime-options-ipc-service";
|
||||
applyRuntimeOptionResultRuntime,
|
||||
cycleRuntimeOptionFromIpcRuntime,
|
||||
setRuntimeOptionFromIpcRuntime,
|
||||
} from "./runtime-options-ipc";
|
||||
|
||||
test("applyRuntimeOptionResultRuntimeService emits success OSD message", () => {
|
||||
test("applyRuntimeOptionResultRuntime emits success OSD message", () => {
|
||||
const osd: string[] = [];
|
||||
const result = applyRuntimeOptionResultRuntimeService(
|
||||
const result = applyRuntimeOptionResultRuntime(
|
||||
{ ok: true, osdMessage: "Updated" },
|
||||
(text) => {
|
||||
osd.push(text);
|
||||
@@ -19,9 +19,9 @@ test("applyRuntimeOptionResultRuntimeService emits success OSD message", () => {
|
||||
assert.deepEqual(osd, ["Updated"]);
|
||||
});
|
||||
|
||||
test("setRuntimeOptionFromIpcRuntimeService returns unavailable when manager missing", () => {
|
||||
test("setRuntimeOptionFromIpcRuntime returns unavailable when manager missing", () => {
|
||||
const osd: string[] = [];
|
||||
const result = setRuntimeOptionFromIpcRuntimeService(
|
||||
const result = setRuntimeOptionFromIpcRuntime(
|
||||
null,
|
||||
"anki.autoUpdateNewCards",
|
||||
true,
|
||||
@@ -34,9 +34,9 @@ test("setRuntimeOptionFromIpcRuntimeService returns unavailable when manager mis
|
||||
assert.deepEqual(osd, []);
|
||||
});
|
||||
|
||||
test("cycleRuntimeOptionFromIpcRuntimeService reports errors once", () => {
|
||||
test("cycleRuntimeOptionFromIpcRuntime reports errors once", () => {
|
||||
const osd: string[] = [];
|
||||
const result = cycleRuntimeOptionFromIpcRuntimeService(
|
||||
const result = cycleRuntimeOptionFromIpcRuntime(
|
||||
{
|
||||
setOptionValue: () => ({ ok: true }),
|
||||
cycleOption: () => ({ ok: false, error: "bad option" }),
|
||||
@@ -15,7 +15,7 @@ export interface RuntimeOptionsManagerLike {
|
||||
) => RuntimeOptionApplyResult;
|
||||
}
|
||||
|
||||
export function applyRuntimeOptionResultRuntimeService(
|
||||
export function applyRuntimeOptionResultRuntime(
|
||||
result: RuntimeOptionApplyResult,
|
||||
showMpvOsd: (text: string) => void,
|
||||
): RuntimeOptionApplyResult {
|
||||
@@ -25,7 +25,7 @@ export function applyRuntimeOptionResultRuntimeService(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function setRuntimeOptionFromIpcRuntimeService(
|
||||
export function setRuntimeOptionFromIpcRuntime(
|
||||
manager: RuntimeOptionsManagerLike | null,
|
||||
id: RuntimeOptionId,
|
||||
value: RuntimeOptionValue,
|
||||
@@ -34,7 +34,7 @@ export function setRuntimeOptionFromIpcRuntimeService(
|
||||
if (!manager) {
|
||||
return { ok: false, error: "Runtime options manager unavailable" };
|
||||
}
|
||||
const result = applyRuntimeOptionResultRuntimeService(
|
||||
const result = applyRuntimeOptionResultRuntime(
|
||||
manager.setOptionValue(id, value),
|
||||
showMpvOsd,
|
||||
);
|
||||
@@ -44,7 +44,7 @@ export function setRuntimeOptionFromIpcRuntimeService(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function cycleRuntimeOptionFromIpcRuntimeService(
|
||||
export function cycleRuntimeOptionFromIpcRuntime(
|
||||
manager: RuntimeOptionsManagerLike | null,
|
||||
id: RuntimeOptionId,
|
||||
direction: 1 | -1,
|
||||
@@ -53,7 +53,7 @@ export function cycleRuntimeOptionFromIpcRuntimeService(
|
||||
if (!manager) {
|
||||
return { ok: false, error: "Runtime options manager unavailable" };
|
||||
}
|
||||
const result = applyRuntimeOptionResultRuntimeService(
|
||||
const result = applyRuntimeOptionResultRuntime(
|
||||
manager.cycleOption(id, direction),
|
||||
showMpvOsd,
|
||||
);
|
||||
@@ -1,15 +1,15 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { SecondarySubMode } from "../../types";
|
||||
import { cycleSecondarySubModeService } from "./subtitle-position-service";
|
||||
import { cycleSecondarySubMode } from "./subtitle-position";
|
||||
|
||||
test("cycleSecondarySubModeService cycles and emits broadcast + OSD", () => {
|
||||
test("cycleSecondarySubMode cycles and emits broadcast + OSD", () => {
|
||||
let mode: SecondarySubMode = "hover";
|
||||
let lastToggleAt = 0;
|
||||
const broadcasts: SecondarySubMode[] = [];
|
||||
const osd: string[] = [];
|
||||
|
||||
cycleSecondarySubModeService({
|
||||
cycleSecondarySubMode({
|
||||
getSecondarySubMode: () => mode,
|
||||
setSecondarySubMode: (next) => {
|
||||
mode = next;
|
||||
@@ -33,13 +33,13 @@ test("cycleSecondarySubModeService cycles and emits broadcast + OSD", () => {
|
||||
assert.equal(lastToggleAt, 1000);
|
||||
});
|
||||
|
||||
test("cycleSecondarySubModeService obeys debounce window", () => {
|
||||
test("cycleSecondarySubMode obeys debounce window", () => {
|
||||
let mode: SecondarySubMode = "visible";
|
||||
let lastToggleAt = 950;
|
||||
let broadcasted = false;
|
||||
let osdShown = false;
|
||||
|
||||
cycleSecondarySubModeService({
|
||||
cycleSecondarySubMode({
|
||||
getSecondarySubMode: () => mode,
|
||||
setSecondarySubMode: (next) => {
|
||||
mode = next;
|
||||
@@ -19,7 +19,7 @@ export interface RegisterGlobalShortcutsServiceOptions {
|
||||
getMainWindow: () => BrowserWindow | null;
|
||||
}
|
||||
|
||||
export function registerGlobalShortcutsService(
|
||||
export function registerGlobalShortcuts(
|
||||
options: RegisterGlobalShortcutsServiceOptions,
|
||||
): void {
|
||||
const visibleShortcut = options.shortcuts.toggleVisibleOverlayGlobal;
|
||||
@@ -1,8 +1,8 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
runStartupBootstrapRuntimeService,
|
||||
} from "./startup-service";
|
||||
runStartupBootstrapRuntime,
|
||||
} from "./startup";
|
||||
import { CliArgs } from "../../cli/args";
|
||||
|
||||
function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
@@ -40,7 +40,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
};
|
||||
}
|
||||
|
||||
test("runStartupBootstrapRuntimeService configures startup state and starts lifecycle", () => {
|
||||
test("runStartupBootstrapRuntime configures startup state and starts lifecycle", () => {
|
||||
const calls: string[] = [];
|
||||
const args = makeArgs({
|
||||
logLevel: "debug",
|
||||
@@ -51,7 +51,7 @@ test("runStartupBootstrapRuntimeService configures startup state and starts life
|
||||
texthooker: true,
|
||||
});
|
||||
|
||||
const result = runStartupBootstrapRuntimeService({
|
||||
const result = runStartupBootstrapRuntime({
|
||||
argv: ["node", "main.ts", "--log-level", "debug"],
|
||||
parseArgs: () => args,
|
||||
setLogLevel: (level, source) => calls.push(`setLog:${level}:${source}`),
|
||||
@@ -77,13 +77,13 @@ test("runStartupBootstrapRuntimeService configures startup state and starts life
|
||||
]);
|
||||
});
|
||||
|
||||
test("runStartupBootstrapRuntimeService keeps log-level precedence for repeated calls", () => {
|
||||
test("runStartupBootstrapRuntime keeps log-level precedence for repeated calls", () => {
|
||||
const calls: string[] = [];
|
||||
const args = makeArgs({
|
||||
logLevel: "warn",
|
||||
});
|
||||
|
||||
runStartupBootstrapRuntimeService({
|
||||
runStartupBootstrapRuntime({
|
||||
argv: ["node", "main.ts", "--log-level", "warn"],
|
||||
parseArgs: () => args,
|
||||
setLogLevel: (level, source) => calls.push(`setLog:${level}:${source}`),
|
||||
@@ -102,13 +102,13 @@ test("runStartupBootstrapRuntimeService keeps log-level precedence for repeated
|
||||
]);
|
||||
});
|
||||
|
||||
test("runStartupBootstrapRuntimeService keeps --debug separate from log verbosity", () => {
|
||||
test("runStartupBootstrapRuntime keeps --debug separate from log verbosity", () => {
|
||||
const calls: string[] = [];
|
||||
const args = makeArgs({
|
||||
debug: true,
|
||||
});
|
||||
|
||||
runStartupBootstrapRuntimeService({
|
||||
runStartupBootstrapRuntime({
|
||||
argv: ["node", "main.ts", "--debug"],
|
||||
parseArgs: () => args,
|
||||
setLogLevel: (level, source) => calls.push(`setLog:${level}:${source}`),
|
||||
@@ -123,11 +123,11 @@ test("runStartupBootstrapRuntimeService keeps --debug separate from log verbosit
|
||||
assert.deepEqual(calls, ["forceX11", "enforceWayland", "startLifecycle"]);
|
||||
});
|
||||
|
||||
test("runStartupBootstrapRuntimeService skips lifecycle when generate-config flow handled", () => {
|
||||
test("runStartupBootstrapRuntime skips lifecycle when generate-config flow handled", () => {
|
||||
const calls: string[] = [];
|
||||
const args = makeArgs({ generateConfig: true, logLevel: "warn" });
|
||||
|
||||
const result = runStartupBootstrapRuntimeService({
|
||||
const result = runStartupBootstrapRuntime({
|
||||
argv: ["node", "main.ts", "--generate-config"],
|
||||
parseArgs: () => args,
|
||||
setLogLevel: (level, source) => calls.push(`setLog:${level}:${source}`),
|
||||
@@ -40,7 +40,7 @@ export interface StartupBootstrapRuntimeDeps {
|
||||
startAppLifecycle: (args: CliArgs) => void;
|
||||
}
|
||||
|
||||
export function runStartupBootstrapRuntimeService(
|
||||
export function runStartupBootstrapRuntime(
|
||||
deps: StartupBootstrapRuntimeDeps,
|
||||
): StartupBootstrapRuntimeState {
|
||||
const initialArgs = deps.parseArgs(deps.argv);
|
||||
@@ -107,7 +107,7 @@ export interface AppReadyRuntimeDeps {
|
||||
handleInitialArgs: () => void;
|
||||
}
|
||||
|
||||
export function getInitialInvisibleOverlayVisibilityService(
|
||||
export function getInitialInvisibleOverlayVisibility(
|
||||
config: RuntimeConfigLike,
|
||||
platform: NodeJS.Platform,
|
||||
): boolean {
|
||||
@@ -118,7 +118,7 @@ export function getInitialInvisibleOverlayVisibilityService(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function shouldAutoInitializeOverlayRuntimeFromConfigService(
|
||||
export function shouldAutoInitializeOverlayRuntimeFromConfig(
|
||||
config: RuntimeConfigLike,
|
||||
): boolean {
|
||||
if (config.auto_start_overlay === true) return true;
|
||||
@@ -126,13 +126,13 @@ export function shouldAutoInitializeOverlayRuntimeFromConfigService(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldBindVisibleOverlayToMpvSubVisibilityService(
|
||||
export function shouldBindVisibleOverlayToMpvSubVisibility(
|
||||
config: RuntimeConfigLike,
|
||||
): boolean {
|
||||
return config.bind_visible_overlay_to_mpv_sub_visibility;
|
||||
}
|
||||
|
||||
export function isAutoUpdateEnabledRuntimeService(
|
||||
export function isAutoUpdateEnabledRuntime(
|
||||
config: ResolvedConfig | RuntimeConfigLike,
|
||||
runtimeOptionsManager: RuntimeAutoUpdateOptionManagerLike | null,
|
||||
): boolean {
|
||||
@@ -141,7 +141,7 @@ export function isAutoUpdateEnabledRuntimeService(
|
||||
return (config as ResolvedConfig).ankiConnect?.behavior?.autoUpdateNewCards !== false;
|
||||
}
|
||||
|
||||
export async function runAppReadyRuntimeService(
|
||||
export async function runAppReadyRuntime(
|
||||
deps: AppReadyRuntimeDeps,
|
||||
): Promise<void> {
|
||||
deps.loadSubtitlePosition();
|
||||
@@ -4,12 +4,12 @@ import {
|
||||
SubsyncResult,
|
||||
} from "../../types";
|
||||
import { SubsyncResolvedConfig } from "../../subsync/utils";
|
||||
import { runSubsyncManualFromIpcService } from "./ipc-command-service";
|
||||
import { runSubsyncManualFromIpc } from "./ipc-command";
|
||||
import {
|
||||
TriggerSubsyncFromConfigDeps,
|
||||
runSubsyncManualService,
|
||||
triggerSubsyncFromConfigService,
|
||||
} from "./subsync-service";
|
||||
runSubsyncManual,
|
||||
triggerSubsyncFromConfig,
|
||||
} from "./subsync";
|
||||
|
||||
const AUTOSUBSYNC_SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
||||
|
||||
@@ -62,24 +62,24 @@ function buildTriggerSubsyncDeps(
|
||||
};
|
||||
}
|
||||
|
||||
export async function triggerSubsyncFromConfigRuntimeService(
|
||||
export async function triggerSubsyncFromConfigRuntime(
|
||||
deps: SubsyncRuntimeDeps,
|
||||
): Promise<void> {
|
||||
await triggerSubsyncFromConfigService(buildTriggerSubsyncDeps(deps));
|
||||
await triggerSubsyncFromConfig(buildTriggerSubsyncDeps(deps));
|
||||
}
|
||||
|
||||
export async function runSubsyncManualFromIpcRuntimeService(
|
||||
export async function runSubsyncManualFromIpcRuntime(
|
||||
request: SubsyncManualRunRequest,
|
||||
deps: SubsyncRuntimeDeps,
|
||||
): Promise<SubsyncResult> {
|
||||
const triggerDeps = buildTriggerSubsyncDeps(deps);
|
||||
return runSubsyncManualFromIpcService(request, {
|
||||
return runSubsyncManualFromIpc(request, {
|
||||
isSubsyncInProgress: triggerDeps.isSubsyncInProgress,
|
||||
setSubsyncInProgress: triggerDeps.setSubsyncInProgress,
|
||||
showMpvOsd: triggerDeps.showMpvOsd,
|
||||
runWithSpinner: (task) =>
|
||||
triggerDeps.runWithSubsyncSpinner(() => task()),
|
||||
runSubsyncManual: (subsyncRequest) =>
|
||||
runSubsyncManualService(subsyncRequest, triggerDeps),
|
||||
runSubsyncManual(subsyncRequest, triggerDeps),
|
||||
});
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import * as os from "os";
|
||||
import * as path from "path";
|
||||
import {
|
||||
TriggerSubsyncFromConfigDeps,
|
||||
runSubsyncManualService,
|
||||
triggerSubsyncFromConfigService,
|
||||
} from "./subsync-service";
|
||||
runSubsyncManual,
|
||||
triggerSubsyncFromConfig,
|
||||
} from "./subsync";
|
||||
|
||||
function makeDeps(
|
||||
overrides: Partial<TriggerSubsyncFromConfigDeps> = {},
|
||||
@@ -55,9 +55,9 @@ function makeDeps(
|
||||
};
|
||||
}
|
||||
|
||||
test("triggerSubsyncFromConfigService returns early when already in progress", async () => {
|
||||
test("triggerSubsyncFromConfig returns early when already in progress", async () => {
|
||||
const osd: string[] = [];
|
||||
await triggerSubsyncFromConfigService(
|
||||
await triggerSubsyncFromConfig(
|
||||
makeDeps({
|
||||
isSubsyncInProgress: () => true,
|
||||
showMpvOsd: (text) => {
|
||||
@@ -68,12 +68,12 @@ test("triggerSubsyncFromConfigService returns early when already in progress", a
|
||||
assert.deepEqual(osd, ["Subsync already running"]);
|
||||
});
|
||||
|
||||
test("triggerSubsyncFromConfigService opens manual picker in manual mode", async () => {
|
||||
test("triggerSubsyncFromConfig opens manual picker in manual mode", async () => {
|
||||
const osd: string[] = [];
|
||||
let payloadTrackCount = 0;
|
||||
let inProgressState: boolean | null = null;
|
||||
|
||||
await triggerSubsyncFromConfigService(
|
||||
await triggerSubsyncFromConfig(
|
||||
makeDeps({
|
||||
openManualPicker: (payload) => {
|
||||
payloadTrackCount = payload.sourceTracks.length;
|
||||
@@ -92,9 +92,9 @@ test("triggerSubsyncFromConfigService opens manual picker in manual mode", async
|
||||
assert.equal(inProgressState, false);
|
||||
});
|
||||
|
||||
test("triggerSubsyncFromConfigService reports failures to OSD", async () => {
|
||||
test("triggerSubsyncFromConfig reports failures to OSD", async () => {
|
||||
const osd: string[] = [];
|
||||
await triggerSubsyncFromConfigService(
|
||||
await triggerSubsyncFromConfig(
|
||||
makeDeps({
|
||||
getMpvClient: () => null,
|
||||
showMpvOsd: (text) => {
|
||||
@@ -106,8 +106,8 @@ test("triggerSubsyncFromConfigService reports failures to OSD", async () => {
|
||||
assert.ok(osd.some((line) => line.startsWith("Subsync failed: MPV not connected")));
|
||||
});
|
||||
|
||||
test("runSubsyncManualService requires a source track for alass", async () => {
|
||||
const result = await runSubsyncManualService(
|
||||
test("runSubsyncManual requires a source track for alass", async () => {
|
||||
const result = await runSubsyncManual(
|
||||
{ engine: "alass", sourceTrackId: null },
|
||||
makeDeps(),
|
||||
);
|
||||
@@ -118,11 +118,11 @@ test("runSubsyncManualService requires a source track for alass", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("triggerSubsyncFromConfigService reports path validation failures", async () => {
|
||||
test("triggerSubsyncFromConfig reports path validation failures", async () => {
|
||||
const osd: string[] = [];
|
||||
const inProgress: boolean[] = [];
|
||||
|
||||
await triggerSubsyncFromConfigService(
|
||||
await triggerSubsyncFromConfig(
|
||||
makeDeps({
|
||||
getResolvedConfig: () => ({
|
||||
defaultMode: "auto",
|
||||
@@ -152,7 +152,7 @@ function writeExecutableScript(filePath: string, content: string): void {
|
||||
fs.chmodSync(filePath, 0o755);
|
||||
}
|
||||
|
||||
test("runSubsyncManualService constructs ffsubsync command and returns success", async () => {
|
||||
test("runSubsyncManual constructs ffsubsync command and returns success", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subsync-ffsubsync-"));
|
||||
const ffsubsyncLogPath = path.join(tmpDir, "ffsubsync-args.log");
|
||||
const ffsubsyncPath = path.join(tmpDir, "ffsubsync.sh");
|
||||
@@ -210,7 +210,7 @@ test("runSubsyncManualService constructs ffsubsync command and returns success",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runSubsyncManualService(
|
||||
const result = await runSubsyncManual(
|
||||
{ engine: "ffsubsync", sourceTrackId: null },
|
||||
deps,
|
||||
);
|
||||
@@ -227,7 +227,7 @@ test("runSubsyncManualService constructs ffsubsync command and returns success",
|
||||
assert.deepEqual(sentCommands[1], ["set_property", "sub-delay", 0]);
|
||||
});
|
||||
|
||||
test("runSubsyncManualService constructs alass command and returns failure on non-zero exit", async () => {
|
||||
test("runSubsyncManual constructs alass command and returns failure on non-zero exit", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subsync-alass-"));
|
||||
const alassLogPath = path.join(tmpDir, "alass-args.log");
|
||||
const alassPath = path.join(tmpDir, "alass.sh");
|
||||
@@ -285,7 +285,7 @@ test("runSubsyncManualService constructs alass command and returns failure on no
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runSubsyncManualService(
|
||||
const result = await runSubsyncManual(
|
||||
{ engine: "alass", sourceTrackId: 2 },
|
||||
deps,
|
||||
);
|
||||
@@ -298,7 +298,7 @@ test("runSubsyncManualService constructs alass command and returns failure on no
|
||||
assert.equal(alassArgs[1], primaryPath);
|
||||
});
|
||||
|
||||
test("runSubsyncManualService resolves string sid values from mpv stream properties", async () => {
|
||||
test("runSubsyncManual resolves string sid values from mpv stream properties", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subsync-stream-sid-"));
|
||||
const ffsubsyncPath = path.join(tmpDir, "ffsubsync.sh");
|
||||
const ffsubsyncLogPath = path.join(tmpDir, "ffsubsync-args.log");
|
||||
@@ -347,7 +347,7 @@ test("runSubsyncManualService resolves string sid values from mpv stream propert
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runSubsyncManualService(
|
||||
const result = await runSubsyncManual(
|
||||
{ engine: "ffsubsync", sourceTrackId: null },
|
||||
deps,
|
||||
);
|
||||
@@ -399,7 +399,7 @@ async function runSubsyncAutoInternal(
|
||||
);
|
||||
}
|
||||
|
||||
export async function runSubsyncManualService(
|
||||
export async function runSubsyncManual(
|
||||
request: SubsyncManualRunRequest,
|
||||
deps: SubsyncCoreDeps,
|
||||
): Promise<SubsyncResult> {
|
||||
@@ -452,7 +452,7 @@ export async function runSubsyncManualService(
|
||||
}
|
||||
}
|
||||
|
||||
export async function openSubsyncManualPickerService(
|
||||
export async function openSubsyncManualPicker(
|
||||
deps: TriggerSubsyncFromConfigDeps,
|
||||
): Promise<void> {
|
||||
const client = getMpvClientForSubsync(deps);
|
||||
@@ -468,7 +468,7 @@ export async function openSubsyncManualPickerService(
|
||||
deps.openManualPicker(payload);
|
||||
}
|
||||
|
||||
export async function triggerSubsyncFromConfigService(
|
||||
export async function triggerSubsyncFromConfig(
|
||||
deps: TriggerSubsyncFromConfigDeps,
|
||||
): Promise<void> {
|
||||
if (deps.isSubsyncInProgress()) {
|
||||
@@ -479,7 +479,7 @@ export async function triggerSubsyncFromConfigService(
|
||||
const resolved = deps.getResolvedConfig();
|
||||
try {
|
||||
if (resolved.defaultMode === "manual") {
|
||||
await openSubsyncManualPickerService(deps);
|
||||
await openSubsyncManualPicker(deps);
|
||||
deps.showMpvOsd("Subsync: choose engine and source");
|
||||
return;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export interface CycleSecondarySubModeDeps {
|
||||
const SECONDARY_SUB_CYCLE: SecondarySubMode[] = ["hidden", "visible", "hover"];
|
||||
const SECONDARY_SUB_TOGGLE_DEBOUNCE_MS = 120;
|
||||
|
||||
export function cycleSecondarySubModeService(
|
||||
export function cycleSecondarySubMode(
|
||||
deps: CycleSecondarySubModeDeps,
|
||||
): void {
|
||||
const now = deps.now ? deps.now() : Date.now();
|
||||
@@ -89,7 +89,7 @@ function persistSubtitlePosition(
|
||||
fs.writeFileSync(positionPath, JSON.stringify(position, null, 2));
|
||||
}
|
||||
|
||||
export function loadSubtitlePositionService(options: {
|
||||
export function loadSubtitlePosition(options: {
|
||||
currentMediaPath: string | null;
|
||||
fallbackPosition: SubtitlePosition;
|
||||
} & { subtitlePositionsDir: string }): SubtitlePosition | null {
|
||||
@@ -135,7 +135,7 @@ export function loadSubtitlePositionService(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSubtitlePositionService(options: {
|
||||
export function saveSubtitlePosition(options: {
|
||||
position: SubtitlePosition;
|
||||
currentMediaPath: string | null;
|
||||
subtitlePositionsDir: string;
|
||||
@@ -160,7 +160,7 @@ export function saveSubtitlePositionService(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCurrentMediaPathService(options: {
|
||||
export function updateCurrentMediaPath(options: {
|
||||
mediaPath: unknown;
|
||||
currentMediaPath: string | null;
|
||||
pendingSubtitlePosition: SubtitlePosition | null;
|
||||
@@ -16,7 +16,7 @@ export function hasMpvWebsocketPlugin(): boolean {
|
||||
return fs.existsSync(mpvWebsocketPath);
|
||||
}
|
||||
|
||||
export class SubtitleWebSocketService {
|
||||
export class SubtitleWebSocket {
|
||||
private server: WebSocket.Server | null = null;
|
||||
|
||||
public isRunning(): boolean {
|
||||
@@ -5,7 +5,7 @@ import { createLogger } from "../../logger";
|
||||
|
||||
const logger = createLogger("main:texthooker");
|
||||
|
||||
export class TexthookerService {
|
||||
export class Texthooker {
|
||||
private server: http.Server | null = null;
|
||||
|
||||
public isRunning(): boolean {
|
||||
@@ -2,11 +2,11 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { PartOfSpeech } from "../../types";
|
||||
import {
|
||||
createTokenizerDepsRuntimeService,
|
||||
createTokenizerDepsRuntime,
|
||||
TokenizerServiceDeps,
|
||||
TokenizerDepsRuntimeOptions,
|
||||
tokenizeSubtitleService,
|
||||
} from "./tokenizer-service";
|
||||
tokenizeSubtitle,
|
||||
} from "./tokenizer";
|
||||
|
||||
function makeDeps(
|
||||
overrides: Partial<TokenizerServiceDeps> = {},
|
||||
@@ -31,7 +31,7 @@ function makeDepsFromMecabTokenizer(
|
||||
tokenize: (text: string) => Promise<import("../../types").Token[] | null>,
|
||||
overrides: Partial<TokenizerDepsRuntimeOptions> = {},
|
||||
): TokenizerServiceDeps {
|
||||
return createTokenizerDepsRuntimeService({
|
||||
return createTokenizerDepsRuntime({
|
||||
getYomitanExt: () => null,
|
||||
getYomitanParserWindow: () => null,
|
||||
setYomitanParserWindow: () => {},
|
||||
@@ -49,8 +49,8 @@ function makeDepsFromMecabTokenizer(
|
||||
});
|
||||
}
|
||||
|
||||
test("tokenizeSubtitleService assigns JLPT level to parsed Yomitan tokens", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle assigns JLPT level to parsed Yomitan tokens", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -88,9 +88,9 @@ test("tokenizeSubtitleService assigns JLPT level to parsed Yomitan tokens", asyn
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, "N5");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService caches JLPT lookups across repeated tokens", async () => {
|
||||
test("tokenizeSubtitle caches JLPT lookups across repeated tokens", async () => {
|
||||
let lookupCalls = 0;
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫猫",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -133,8 +133,8 @@ test("tokenizeSubtitleService caches JLPT lookups across repeated tokens", async
|
||||
assert.equal(result.tokens?.[1]?.jlptLevel, "N5");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService leaves JLPT unset for non-matching tokens", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle leaves JLPT unset for non-matching tokens", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -159,9 +159,9 @@ test("tokenizeSubtitleService leaves JLPT unset for non-matching tokens", async
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips JLPT lookups when disabled", async () => {
|
||||
test("tokenizeSubtitle skips JLPT lookups when disabled", async () => {
|
||||
let lookupCalls = 0;
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -190,8 +190,8 @@ test("tokenizeSubtitleService skips JLPT lookups when disabled", async () => {
|
||||
assert.equal(lookupCalls, 0);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService applies frequency dictionary ranks", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle applies frequency dictionary ranks", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -228,8 +228,8 @@ test("tokenizeSubtitleService applies frequency dictionary ranks", async () => {
|
||||
assert.equal(result.tokens?.[1]?.frequencyRank, 1200);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService uses only selected Yomitan headword for frequency lookup", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle uses only selected Yomitan headword for frequency lookup", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -265,8 +265,8 @@ test("tokenizeSubtitleService uses only selected Yomitan headword for frequency
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, 1200);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService keeps furigana-split Yomitan segments as one token", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle keeps furigana-split Yomitan segments as one token", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"友達と話した",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -324,8 +324,8 @@ test("tokenizeSubtitleService keeps furigana-split Yomitan segments as one token
|
||||
assert.equal(result.tokens?.[2]?.frequencyRank, 90);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService prefers exact headword frequency over surface/reading when available", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle prefers exact headword frequency over surface/reading when available", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -358,8 +358,8 @@ test("tokenizeSubtitleService prefers exact headword frequency over surface/read
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, 8);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService keeps no frequency when only reading matches and headword misses", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle keeps no frequency when only reading matches and headword misses", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -392,8 +392,8 @@ test("tokenizeSubtitleService keeps no frequency when only reading matches and h
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService ignores invalid frequency rank on selected headword", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle ignores invalid frequency rank on selected headword", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -429,8 +429,8 @@ test("tokenizeSubtitleService ignores invalid frequency rank on selected headwor
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService handles real-word frequency candidates and prefers most frequent term", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle handles real-word frequency candidates and prefers most frequent term", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"昨日",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -466,8 +466,8 @@ test("tokenizeSubtitleService handles real-word frequency candidates and prefers
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, 40);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService ignores candidates with no dictionary rank when higher-frequency candidate exists", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle ignores candidates with no dictionary rank when higher-frequency candidate exists", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -504,8 +504,8 @@ test("tokenizeSubtitleService ignores candidates with no dictionary rank when hi
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, 88);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService ignores frequency lookup failures", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle ignores frequency lookup failures", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -531,8 +531,8 @@ test("tokenizeSubtitleService ignores frequency lookup failures", async () => {
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips frequency rank when Yomitan token is enriched as particle by mecab pos1", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle skips frequency rank when Yomitan token is enriched as particle by mecab pos1", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"は",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -580,8 +580,8 @@ test("tokenizeSubtitleService skips frequency rank when Yomitan token is enriche
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService ignores invalid frequency ranks", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle ignores invalid frequency ranks", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
@@ -622,9 +622,9 @@ test("tokenizeSubtitleService ignores invalid frequency ranks", async () => {
|
||||
assert.equal(result.tokens?.[1]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips frequency lookups when disabled", async () => {
|
||||
test("tokenizeSubtitle skips frequency lookups when disabled", async () => {
|
||||
let frequencyCalls = 0;
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫",
|
||||
makeDeps({
|
||||
getFrequencyDictionaryEnabled: () => false,
|
||||
@@ -653,8 +653,8 @@ test("tokenizeSubtitleService skips frequency lookups when disabled", async () =
|
||||
assert.equal(frequencyCalls, 0);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips JLPT level for excluded demonstratives", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle skips JLPT level for excluded demonstratives", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"この",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -687,8 +687,8 @@ test("tokenizeSubtitleService skips JLPT level for excluded demonstratives", asy
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips JLPT level for repeated kana SFX", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle skips JLPT level for repeated kana SFX", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"ああ",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -721,8 +721,8 @@ test("tokenizeSubtitleService skips JLPT level for repeated kana SFX", async ()
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService assigns JLPT level to mecab tokens", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle assigns JLPT level to mecab tokens", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -747,8 +747,8 @@ test("tokenizeSubtitleService assigns JLPT level to mecab tokens", async () => {
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, "N4");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService skips JLPT level for mecab tokens marked as ineligible", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle skips JLPT level for mecab tokens marked as ineligible", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"は",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -774,14 +774,14 @@ test("tokenizeSubtitleService skips JLPT level for mecab tokens marked as inelig
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService returns null tokens for empty normalized text", async () => {
|
||||
const result = await tokenizeSubtitleService(" \\n ", makeDeps());
|
||||
test("tokenizeSubtitle returns null tokens for empty normalized text", async () => {
|
||||
const result = await tokenizeSubtitle(" \\n ", makeDeps());
|
||||
assert.deepEqual(result, { text: " \\n ", tokens: null });
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService normalizes newlines before mecab fallback", async () => {
|
||||
test("tokenizeSubtitle normalizes newlines before mecab fallback", async () => {
|
||||
let tokenizeInput = "";
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫\\Nです\nね",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async (text) => {
|
||||
@@ -808,8 +808,8 @@ test("tokenizeSubtitleService normalizes newlines before mecab fallback", async
|
||||
assert.equal(result.tokens?.[0]?.surface, "猫ですね");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService falls back to mecab tokens when available", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle falls back to mecab tokens when available", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -833,8 +833,8 @@ test("tokenizeSubtitleService falls back to mecab tokens when available", async
|
||||
assert.equal(result.tokens?.[0]?.surface, "猫");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService returns null tokens when mecab throws", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle returns null tokens when mecab throws", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => {
|
||||
@@ -846,7 +846,7 @@ test("tokenizeSubtitleService returns null tokens when mecab throws", async () =
|
||||
assert.deepEqual(result, { text: "猫です", tokens: null });
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService uses Yomitan parser result when available", async () => {
|
||||
test("tokenizeSubtitle uses Yomitan parser result when available", async () => {
|
||||
const parserWindow = {
|
||||
isDestroyed: () => false,
|
||||
webContents: {
|
||||
@@ -874,7 +874,7 @@ test("tokenizeSubtitleService uses Yomitan parser result when available", async
|
||||
},
|
||||
} as unknown as Electron.BrowserWindow;
|
||||
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -893,7 +893,7 @@ test("tokenizeSubtitleService uses Yomitan parser result when available", async
|
||||
assert.equal(result.tokens?.[1]?.isKnown, false);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService logs selected Yomitan groups when debug toggle is enabled", async () => {
|
||||
test("tokenizeSubtitle logs selected Yomitan groups when debug toggle is enabled", async () => {
|
||||
const infoLogs: string[] = [];
|
||||
const originalInfo = console.info;
|
||||
console.info = (...args: unknown[]) => {
|
||||
@@ -901,7 +901,7 @@ test("tokenizeSubtitleService logs selected Yomitan groups when debug toggle is
|
||||
};
|
||||
|
||||
try {
|
||||
await tokenizeSubtitleService(
|
||||
await tokenizeSubtitle(
|
||||
"友達と話した",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -949,7 +949,7 @@ test("tokenizeSubtitleService logs selected Yomitan groups when debug toggle is
|
||||
);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService does not log Yomitan groups when debug toggle is disabled", async () => {
|
||||
test("tokenizeSubtitle does not log Yomitan groups when debug toggle is disabled", async () => {
|
||||
const infoLogs: string[] = [];
|
||||
const originalInfo = console.info;
|
||||
console.info = (...args: unknown[]) => {
|
||||
@@ -957,7 +957,7 @@ test("tokenizeSubtitleService does not log Yomitan groups when debug toggle is d
|
||||
};
|
||||
|
||||
try {
|
||||
await tokenizeSubtitleService(
|
||||
await tokenizeSubtitle(
|
||||
"友達と話した",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -999,7 +999,7 @@ test("tokenizeSubtitleService does not log Yomitan groups when debug toggle is d
|
||||
);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService preserves segmented Yomitan line as one token", async () => {
|
||||
test("tokenizeSubtitle preserves segmented Yomitan line as one token", async () => {
|
||||
const parserWindow = {
|
||||
isDestroyed: () => false,
|
||||
webContents: {
|
||||
@@ -1025,7 +1025,7 @@ test("tokenizeSubtitleService preserves segmented Yomitan line as one token", as
|
||||
},
|
||||
} as unknown as Electron.BrowserWindow;
|
||||
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1042,8 +1042,8 @@ test("tokenizeSubtitleService preserves segmented Yomitan line as one token", as
|
||||
assert.equal(result.tokens?.[0]?.isKnown, false);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService prefers mecab parser tokens when scanning parser returns one token", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle prefers mecab parser tokens when scanning parser returns one token", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"俺は小園にいきたい",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1091,8 +1091,8 @@ test("tokenizeSubtitleService prefers mecab parser tokens when scanning parser r
|
||||
assert.equal(result.tokens?.[2]?.frequencyRank, 25);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService keeps scanning parser tokens when they are already split", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle keeps scanning parser tokens when they are already split", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"小園に行きたい",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1139,8 +1139,8 @@ test("tokenizeSubtitleService keeps scanning parser tokens when they are already
|
||||
assert.equal(result.tokens?.[2]?.frequencyRank, undefined);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService prefers parse candidates with fewer fragment-only kana tokens when source priority is equal", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle prefers parse candidates with fewer fragment-only kana tokens when source priority is equal", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"俺は公園にいきたい",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1192,8 +1192,8 @@ test("tokenizeSubtitleService prefers parse candidates with fewer fragment-only
|
||||
assert.equal(result.tokens?.[4]?.frequencyRank, 1500);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService still assigns frequency to non-known Yomitan tokens", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle still assigns frequency to non-known Yomitan tokens", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"小園に",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1229,8 +1229,8 @@ test("tokenizeSubtitleService still assigns frequency to non-known Yomitan token
|
||||
assert.equal(result.tokens?.[1]?.frequencyRank, 3000);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService marks tokens as known using callback", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle marks tokens as known using callback", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -1255,8 +1255,8 @@ test("tokenizeSubtitleService marks tokens as known using callback", async () =>
|
||||
assert.equal(result.tokens?.[0]?.isKnown, true);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService still assigns frequency rank to non-known tokens", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle still assigns frequency rank to non-known tokens", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"既知未知",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -1312,8 +1312,8 @@ test("tokenizeSubtitleService still assigns frequency rank to non-known tokens",
|
||||
assert.equal(result.tokens?.[1]?.frequencyRank, 30);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService selects one N+1 target token", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle selects one N+1 target token", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -1349,8 +1349,8 @@ test("tokenizeSubtitleService selects one N+1 target token", async () => {
|
||||
assert.equal(targets[0]?.surface, "犬");
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService does not mark target when sentence has multiple candidates", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle does not mark target when sentence has multiple candidates", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫犬",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -1386,7 +1386,7 @@ test("tokenizeSubtitleService does not mark target when sentence has multiple ca
|
||||
);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService applies N+1 target marking to Yomitan results", async () => {
|
||||
test("tokenizeSubtitle applies N+1 target marking to Yomitan results", async () => {
|
||||
const parserWindow = {
|
||||
isDestroyed: () => false,
|
||||
webContents: {
|
||||
@@ -1415,7 +1415,7 @@ test("tokenizeSubtitleService applies N+1 target marking to Yomitan results", as
|
||||
},
|
||||
} as unknown as Electron.BrowserWindow;
|
||||
|
||||
const result = await tokenizeSubtitleService(
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
getYomitanExt: () => ({ id: "dummy-ext" } as any),
|
||||
@@ -1433,8 +1433,8 @@ test("tokenizeSubtitleService applies N+1 target marking to Yomitan results", as
|
||||
assert.equal(result.tokens?.[1]?.isNPlusOneTarget, false);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService does not color 1-2 word sentences by default", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle does not color 1-2 word sentences by default", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDeps({
|
||||
tokenizeWithMecab: async () => [
|
||||
@@ -1470,8 +1470,8 @@ test("tokenizeSubtitleService does not color 1-2 word sentences by default", asy
|
||||
);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService checks known words by headword, not surface", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle checks known words by headword, not surface", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -1496,8 +1496,8 @@ test("tokenizeSubtitleService checks known words by headword, not surface", asyn
|
||||
assert.equal(result.tokens?.[0]?.isKnown, true);
|
||||
});
|
||||
|
||||
test("tokenizeSubtitleService checks known words by surface when configured", async () => {
|
||||
const result = await tokenizeSubtitleService(
|
||||
test("tokenizeSubtitle checks known words by surface when configured", async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
"猫です",
|
||||
makeDepsFromMecabTokenizer(async () => [
|
||||
{
|
||||
@@ -182,7 +182,7 @@ function getCachedFrequencyRank(
|
||||
return rank;
|
||||
}
|
||||
|
||||
export function createTokenizerDepsRuntimeService(
|
||||
export function createTokenizerDepsRuntime(
|
||||
options: TokenizerDepsRuntimeOptions,
|
||||
): TokenizerServiceDeps {
|
||||
return {
|
||||
@@ -983,7 +983,7 @@ async function parseWithYomitanInternalParser(
|
||||
}
|
||||
}
|
||||
|
||||
export async function tokenizeSubtitleService(
|
||||
export async function tokenizeSubtitle(
|
||||
text: string,
|
||||
deps: TokenizerServiceDeps,
|
||||
): Promise<SubtitleData> {
|
||||
@@ -58,7 +58,7 @@ function ensureExtensionCopy(sourceDir: string, userDataPath: string): string {
|
||||
return targetDir;
|
||||
}
|
||||
|
||||
export async function loadYomitanExtensionService(
|
||||
export async function loadYomitanExtension(
|
||||
deps: YomitanExtensionLoaderDeps,
|
||||
): Promise<Extension | null> {
|
||||
const searchPaths = [
|
||||
176
src/main.ts
176
src/main.ts
@@ -85,53 +85,53 @@ import {
|
||||
} from "./core/utils";
|
||||
import {
|
||||
MpvIpcClient,
|
||||
SubtitleWebSocketService,
|
||||
TexthookerService,
|
||||
applyMpvSubtitleRenderMetricsPatchService,
|
||||
broadcastRuntimeOptionsChangedRuntimeService,
|
||||
copyCurrentSubtitleService,
|
||||
createOverlayManagerService,
|
||||
createFieldGroupingOverlayRuntimeService,
|
||||
createNumericShortcutRuntimeService,
|
||||
createOverlayContentMeasurementStoreService,
|
||||
createOverlayWindowService,
|
||||
createTokenizerDepsRuntimeService,
|
||||
cycleSecondarySubModeService,
|
||||
enforceOverlayLayerOrderService,
|
||||
ensureOverlayWindowLevelService,
|
||||
getInitialInvisibleOverlayVisibilityService,
|
||||
getJimakuLanguagePreferenceService,
|
||||
getJimakuMaxEntryResultsService,
|
||||
handleMineSentenceDigitService,
|
||||
handleMultiCopyDigitService,
|
||||
SubtitleWebSocket,
|
||||
Texthooker,
|
||||
applyMpvSubtitleRenderMetricsPatch,
|
||||
broadcastRuntimeOptionsChangedRuntime,
|
||||
copyCurrentSubtitle as copyCurrentSubtitleCore,
|
||||
createOverlayManager,
|
||||
createFieldGroupingOverlayRuntime,
|
||||
createNumericShortcutRuntime,
|
||||
createOverlayContentMeasurementStore,
|
||||
createOverlayWindow as createOverlayWindowCore,
|
||||
createTokenizerDepsRuntime,
|
||||
cycleSecondarySubMode as cycleSecondarySubModeCore,
|
||||
enforceOverlayLayerOrder as enforceOverlayLayerOrderCore,
|
||||
ensureOverlayWindowLevel as ensureOverlayWindowLevelCore,
|
||||
getInitialInvisibleOverlayVisibility as getInitialInvisibleOverlayVisibilityCore,
|
||||
getJimakuLanguagePreference as getJimakuLanguagePreferenceCore,
|
||||
getJimakuMaxEntryResults as getJimakuMaxEntryResultsCore,
|
||||
handleMineSentenceDigit as handleMineSentenceDigitCore,
|
||||
handleMultiCopyDigit as handleMultiCopyDigitCore,
|
||||
hasMpvWebsocketPlugin,
|
||||
initializeOverlayRuntimeService,
|
||||
isAutoUpdateEnabledRuntimeService,
|
||||
jimakuFetchJsonService,
|
||||
loadSubtitlePositionService,
|
||||
loadYomitanExtensionService,
|
||||
markLastCardAsAudioCardService,
|
||||
initializeOverlayRuntime as initializeOverlayRuntimeCore,
|
||||
isAutoUpdateEnabledRuntime as isAutoUpdateEnabledRuntimeCore,
|
||||
jimakuFetchJson as jimakuFetchJsonCore,
|
||||
loadSubtitlePosition as loadSubtitlePositionCore,
|
||||
loadYomitanExtension as loadYomitanExtensionCore,
|
||||
markLastCardAsAudioCard as markLastCardAsAudioCardCore,
|
||||
DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
mineSentenceCardService,
|
||||
mineSentenceCard as mineSentenceCardCore,
|
||||
ImmersionTrackerService,
|
||||
openYomitanSettingsWindow,
|
||||
playNextSubtitleRuntimeService,
|
||||
registerGlobalShortcutsService,
|
||||
replayCurrentSubtitleRuntimeService,
|
||||
resolveJimakuApiKeyService,
|
||||
runStartupBootstrapRuntimeService,
|
||||
saveSubtitlePositionService,
|
||||
sendMpvCommandRuntimeService,
|
||||
setInvisibleOverlayVisibleService,
|
||||
setMpvSubVisibilityRuntimeService,
|
||||
setOverlayDebugVisualizationEnabledRuntimeService,
|
||||
setVisibleOverlayVisibleService,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfigService,
|
||||
shouldBindVisibleOverlayToMpvSubVisibilityService,
|
||||
showMpvOsdRuntimeService,
|
||||
tokenizeSubtitleService,
|
||||
triggerFieldGroupingService,
|
||||
updateLastCardFromClipboardService,
|
||||
playNextSubtitleRuntime,
|
||||
registerGlobalShortcuts as registerGlobalShortcutsCore,
|
||||
replayCurrentSubtitleRuntime,
|
||||
resolveJimakuApiKey as resolveJimakuApiKeyCore,
|
||||
runStartupBootstrapRuntime,
|
||||
saveSubtitlePosition as saveSubtitlePositionCore,
|
||||
sendMpvCommandRuntime,
|
||||
setInvisibleOverlayVisible as setInvisibleOverlayVisibleCore,
|
||||
setMpvSubVisibilityRuntime,
|
||||
setOverlayDebugVisualizationEnabledRuntime,
|
||||
setVisibleOverlayVisible as setVisibleOverlayVisibleCore,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig as shouldAutoInitializeOverlayRuntimeFromConfigCore,
|
||||
shouldBindVisibleOverlayToMpvSubVisibility as shouldBindVisibleOverlayToMpvSubVisibilityCore,
|
||||
showMpvOsdRuntime,
|
||||
tokenizeSubtitle as tokenizeSubtitleCore,
|
||||
triggerFieldGrouping as triggerFieldGroupingCore,
|
||||
updateLastCardFromClipboard as updateLastCardFromClipboardCore,
|
||||
} from "./core/services";
|
||||
import {
|
||||
guessAnilistMediaInfo,
|
||||
@@ -140,7 +140,7 @@ import {
|
||||
} from "./core/services/anilist/anilist-updater";
|
||||
import { createAnilistTokenStore } from "./core/services/anilist/anilist-token-store";
|
||||
import { createAnilistUpdateQueue } from "./core/services/anilist/anilist-update-queue";
|
||||
import { applyRuntimeOptionResultRuntimeService } from "./core/services/runtime-options-ipc-service";
|
||||
import { applyRuntimeOptionResultRuntime } from "./core/services/runtime-options-ipc";
|
||||
import {
|
||||
createAppReadyRuntimeRunner,
|
||||
} from "./main/app-lifecycle";
|
||||
@@ -283,8 +283,8 @@ const anilistUpdateQueue = createAnilistUpdateQueue(
|
||||
);
|
||||
const isDev =
|
||||
process.argv.includes("--dev") || process.argv.includes("--debug");
|
||||
const texthookerService = new TexthookerService();
|
||||
const subtitleWsService = new SubtitleWebSocketService();
|
||||
const texthookerService = new Texthooker();
|
||||
const subtitleWsService = new SubtitleWebSocket();
|
||||
const logger = createLogger("main");
|
||||
const appLogger = {
|
||||
logInfo: (message: string) => {
|
||||
@@ -330,8 +330,8 @@ process.on("SIGTERM", () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
const overlayManager = createOverlayManagerService();
|
||||
const overlayContentMeasurementStore = createOverlayContentMeasurementStoreService({
|
||||
const overlayManager = createOverlayManager();
|
||||
const overlayContentMeasurementStore = createOverlayContentMeasurementStore({
|
||||
now: () => Date.now(),
|
||||
warn: (message: string) => logger.warn(message),
|
||||
});
|
||||
@@ -460,7 +460,7 @@ function setFieldGroupingResolver(
|
||||
appState.fieldGroupingResolver = wrappedResolver;
|
||||
}
|
||||
|
||||
const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntimeService<OverlayHostedModal>({
|
||||
const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntime<OverlayHostedModal>({
|
||||
getMainWindow: () => overlayManager.getMainWindow(),
|
||||
getVisibleOverlayVisible: () => overlayManager.getVisibleOverlayVisible(),
|
||||
getInvisibleOverlayVisible: () => overlayManager.getInvisibleOverlayVisible(),
|
||||
@@ -548,7 +548,7 @@ function broadcastToOverlayWindows(channel: string, ...args: unknown[]): void {
|
||||
}
|
||||
|
||||
function broadcastRuntimeOptionsChanged(): void {
|
||||
broadcastRuntimeOptionsChangedRuntimeService(
|
||||
broadcastRuntimeOptionsChangedRuntime(
|
||||
() => getRuntimeOptionsState(),
|
||||
(channel, ...args) => broadcastToOverlayWindows(channel, ...args),
|
||||
);
|
||||
@@ -567,7 +567,7 @@ function sendToActiveOverlayWindow(
|
||||
}
|
||||
|
||||
function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
|
||||
setOverlayDebugVisualizationEnabledRuntimeService(
|
||||
setOverlayDebugVisualizationEnabledRuntime(
|
||||
appState.overlayDebugVisualizationEnabled,
|
||||
enabled,
|
||||
(next) => {
|
||||
@@ -646,32 +646,32 @@ async function getCurrentMpvMediaStateForTracker(): Promise<ImmersionMediaState>
|
||||
}
|
||||
|
||||
function getInitialInvisibleOverlayVisibility(): boolean {
|
||||
return getInitialInvisibleOverlayVisibilityService(
|
||||
return getInitialInvisibleOverlayVisibilityCore(
|
||||
getResolvedConfig(),
|
||||
process.platform,
|
||||
);
|
||||
}
|
||||
|
||||
function shouldAutoInitializeOverlayRuntimeFromConfig(): boolean {
|
||||
return shouldAutoInitializeOverlayRuntimeFromConfigService(getResolvedConfig());
|
||||
return shouldAutoInitializeOverlayRuntimeFromConfigCore(getResolvedConfig());
|
||||
}
|
||||
|
||||
function shouldBindVisibleOverlayToMpvSubVisibility(): boolean {
|
||||
return shouldBindVisibleOverlayToMpvSubVisibilityService(getResolvedConfig());
|
||||
return shouldBindVisibleOverlayToMpvSubVisibilityCore(getResolvedConfig());
|
||||
}
|
||||
|
||||
function isAutoUpdateEnabledRuntime(): boolean {
|
||||
return isAutoUpdateEnabledRuntimeService(
|
||||
return isAutoUpdateEnabledRuntimeCore(
|
||||
getResolvedConfig(),
|
||||
appState.runtimeOptionsManager,
|
||||
);
|
||||
}
|
||||
|
||||
function getJimakuLanguagePreference(): JimakuLanguagePreference { return getJimakuLanguagePreferenceService(() => getResolvedConfig(), DEFAULT_CONFIG.jimaku.languagePreference); }
|
||||
function getJimakuLanguagePreference(): JimakuLanguagePreference { return getJimakuLanguagePreferenceCore(() => getResolvedConfig(), DEFAULT_CONFIG.jimaku.languagePreference); }
|
||||
|
||||
function getJimakuMaxEntryResults(): number { return getJimakuMaxEntryResultsService(() => getResolvedConfig(), DEFAULT_CONFIG.jimaku.maxEntryResults); }
|
||||
function getJimakuMaxEntryResults(): number { return getJimakuMaxEntryResultsCore(() => getResolvedConfig(), DEFAULT_CONFIG.jimaku.maxEntryResults); }
|
||||
|
||||
async function resolveJimakuApiKey(): Promise<string | null> { return resolveJimakuApiKeyService(() => getResolvedConfig()); }
|
||||
async function resolveJimakuApiKey(): Promise<string | null> { return resolveJimakuApiKeyCore(() => getResolvedConfig()); }
|
||||
|
||||
function seedImmersionTrackerFromCurrentMedia(): void {
|
||||
const tracker = appState.immersionTracker;
|
||||
@@ -754,7 +754,7 @@ async function jimakuFetchJson<T>(
|
||||
endpoint: string,
|
||||
query: Record<string, string | number | boolean | null | undefined> = {},
|
||||
): Promise<JimakuApiResponse<T>> {
|
||||
return jimakuFetchJsonService<T>(endpoint, query, {
|
||||
return jimakuFetchJsonCore<T>(endpoint, query, {
|
||||
getResolvedConfig: () => getResolvedConfig(),
|
||||
defaultBaseUrl: DEFAULT_CONFIG.jimaku.apiBaseUrl,
|
||||
defaultMaxEntryResults: DEFAULT_CONFIG.jimaku.maxEntryResults,
|
||||
@@ -1193,7 +1193,7 @@ async function maybeRunAnilistPostWatchUpdate(): Promise<void> {
|
||||
}
|
||||
|
||||
function loadSubtitlePosition(): SubtitlePosition | null {
|
||||
appState.subtitlePosition = loadSubtitlePositionService({
|
||||
appState.subtitlePosition = loadSubtitlePositionCore({
|
||||
currentMediaPath: appState.currentMediaPath,
|
||||
fallbackPosition: getResolvedConfig().subtitlePosition,
|
||||
subtitlePositionsDir: SUBTITLE_POSITIONS_DIR,
|
||||
@@ -1203,7 +1203,7 @@ function loadSubtitlePosition(): SubtitlePosition | null {
|
||||
|
||||
function saveSubtitlePosition(position: SubtitlePosition): void {
|
||||
appState.subtitlePosition = position;
|
||||
saveSubtitlePositionService({
|
||||
saveSubtitlePositionCore({
|
||||
position,
|
||||
currentMediaPath: appState.currentMediaPath,
|
||||
subtitlePositionsDir: SUBTITLE_POSITIONS_DIR,
|
||||
@@ -1216,7 +1216,7 @@ function saveSubtitlePosition(position: SubtitlePosition): void {
|
||||
});
|
||||
}
|
||||
|
||||
const startupState = runStartupBootstrapRuntimeService(
|
||||
const startupState = runStartupBootstrapRuntime(
|
||||
createStartupBootstrapRuntimeDeps({
|
||||
argv: process.argv,
|
||||
parseArgs: (argv: string[]) => parseArgs(argv),
|
||||
@@ -1558,7 +1558,7 @@ function createMpvClientRuntimeService(): MpvIpcClient {
|
||||
function updateMpvSubtitleRenderMetrics(
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
): void {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatchService(
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatch(
|
||||
appState.mpvSubtitleRenderMetrics,
|
||||
patch,
|
||||
);
|
||||
@@ -1573,9 +1573,9 @@ function updateMpvSubtitleRenderMetrics(
|
||||
async function tokenizeSubtitle(text: string): Promise<SubtitleData> {
|
||||
await jlptDictionaryRuntime.ensureJlptDictionaryLookup();
|
||||
await frequencyDictionaryRuntime.ensureFrequencyDictionaryLookup();
|
||||
return tokenizeSubtitleService(
|
||||
return tokenizeSubtitleCore(
|
||||
text,
|
||||
createTokenizerDepsRuntimeService({
|
||||
createTokenizerDepsRuntime({
|
||||
getYomitanExt: () => appState.yomitanExt,
|
||||
getYomitanParserWindow: () => appState.yomitanParserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
@@ -1621,11 +1621,11 @@ function updateInvisibleOverlayBounds(geometry: WindowGeometry): void {
|
||||
}
|
||||
|
||||
function ensureOverlayWindowLevel(window: BrowserWindow): void {
|
||||
ensureOverlayWindowLevelService(window);
|
||||
ensureOverlayWindowLevelCore(window);
|
||||
}
|
||||
|
||||
function enforceOverlayLayerOrder(): void {
|
||||
enforceOverlayLayerOrderService({
|
||||
enforceOverlayLayerOrderCore({
|
||||
visibleOverlayVisible: overlayManager.getVisibleOverlayVisible(),
|
||||
invisibleOverlayVisible: overlayManager.getInvisibleOverlayVisible(),
|
||||
mainWindow: overlayManager.getMainWindow(),
|
||||
@@ -1635,7 +1635,7 @@ function enforceOverlayLayerOrder(): void {
|
||||
}
|
||||
|
||||
async function loadYomitanExtension(): Promise<Extension | null> {
|
||||
return loadYomitanExtensionService({
|
||||
return loadYomitanExtensionCore({
|
||||
userDataPath: USER_DATA_PATH,
|
||||
getYomitanParserWindow: () => appState.yomitanParserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
@@ -1654,7 +1654,7 @@ async function loadYomitanExtension(): Promise<Extension | null> {
|
||||
}
|
||||
|
||||
function createOverlayWindow(kind: "visible" | "invisible"): BrowserWindow {
|
||||
return createOverlayWindowService(
|
||||
return createOverlayWindowCore(
|
||||
kind,
|
||||
{
|
||||
isDev,
|
||||
@@ -1695,7 +1695,7 @@ function initializeOverlayRuntime(): void {
|
||||
if (appState.overlayRuntimeInitialized) {
|
||||
return;
|
||||
}
|
||||
const result = initializeOverlayRuntimeService(
|
||||
const result = initializeOverlayRuntimeCore(
|
||||
{
|
||||
backendOverride: appState.backendOverride,
|
||||
getInitialInvisibleOverlayVisibility: () =>
|
||||
@@ -1759,7 +1759,7 @@ function openYomitanSettings(): void {
|
||||
);
|
||||
}
|
||||
function registerGlobalShortcuts(): void {
|
||||
registerGlobalShortcutsService(
|
||||
registerGlobalShortcutsCore(
|
||||
{
|
||||
shortcuts: getConfiguredShortcuts(),
|
||||
onToggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||
@@ -1774,7 +1774,7 @@ function registerGlobalShortcuts(): void {
|
||||
function getConfiguredShortcuts() { return resolveConfiguredShortcuts(getResolvedConfig(), DEFAULT_CONFIG); }
|
||||
|
||||
function cycleSecondarySubMode(): void {
|
||||
cycleSecondarySubModeService(
|
||||
cycleSecondarySubModeCore(
|
||||
{
|
||||
getSecondarySubMode: () => appState.secondarySubMode,
|
||||
setSecondarySubMode: (mode: SecondarySubMode) => {
|
||||
@@ -1794,7 +1794,7 @@ function cycleSecondarySubMode(): void {
|
||||
|
||||
function showMpvOsd(text: string): void {
|
||||
appendToMpvLog(`[OSD] ${text}`);
|
||||
showMpvOsdRuntimeService(
|
||||
showMpvOsdRuntime(
|
||||
appState.mpvClient,
|
||||
text,
|
||||
(line) => {
|
||||
@@ -1816,7 +1816,7 @@ function appendToMpvLog(message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
const numericShortcutRuntime = createNumericShortcutRuntimeService({
|
||||
const numericShortcutRuntime = createNumericShortcutRuntime({
|
||||
globalShortcut,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
setTimer: (handler, timeoutMs) => setTimeout(handler, timeoutMs),
|
||||
@@ -1863,7 +1863,7 @@ function startPendingMultiCopy(timeoutMs: number): void {
|
||||
}
|
||||
|
||||
function handleMultiCopyDigit(count: number): void {
|
||||
handleMultiCopyDigitService(
|
||||
handleMultiCopyDigitCore(
|
||||
count,
|
||||
{
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
@@ -1874,7 +1874,7 @@ function handleMultiCopyDigit(count: number): void {
|
||||
}
|
||||
|
||||
function copyCurrentSubtitle(): void {
|
||||
copyCurrentSubtitleService(
|
||||
copyCurrentSubtitleCore(
|
||||
{
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
writeClipboardText: (text) => clipboard.writeText(text),
|
||||
@@ -1884,7 +1884,7 @@ function copyCurrentSubtitle(): void {
|
||||
}
|
||||
|
||||
async function updateLastCardFromClipboard(): Promise<void> {
|
||||
await updateLastCardFromClipboardService(
|
||||
await updateLastCardFromClipboardCore(
|
||||
{
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
readClipboardText: () => clipboard.readText(),
|
||||
@@ -1902,7 +1902,7 @@ async function refreshKnownWordCache(): Promise<void> {
|
||||
}
|
||||
|
||||
async function triggerFieldGrouping(): Promise<void> {
|
||||
await triggerFieldGroupingService(
|
||||
await triggerFieldGroupingCore(
|
||||
{
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
@@ -1911,7 +1911,7 @@ async function triggerFieldGrouping(): Promise<void> {
|
||||
}
|
||||
|
||||
async function markLastCardAsAudioCard(): Promise<void> {
|
||||
await markLastCardAsAudioCardService(
|
||||
await markLastCardAsAudioCardCore(
|
||||
{
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
@@ -1920,7 +1920,7 @@ async function markLastCardAsAudioCard(): Promise<void> {
|
||||
}
|
||||
|
||||
async function mineSentenceCard(): Promise<void> {
|
||||
const created = await mineSentenceCardService(
|
||||
const created = await mineSentenceCardCore(
|
||||
{
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
mpvClient: appState.mpvClient,
|
||||
@@ -1949,7 +1949,7 @@ function startPendingMineSentenceMultiple(timeoutMs: number): void {
|
||||
}
|
||||
|
||||
function handleMineSentenceDigit(count: number): void {
|
||||
handleMineSentenceDigitService(
|
||||
handleMineSentenceDigitCore(
|
||||
count,
|
||||
{
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
@@ -1983,7 +1983,7 @@ function refreshOverlayShortcuts(): void {
|
||||
}
|
||||
|
||||
function setVisibleOverlayVisible(visible: boolean): void {
|
||||
setVisibleOverlayVisibleService({
|
||||
setVisibleOverlayVisibleCore({
|
||||
visible,
|
||||
setVisibleOverlayVisibleState: (nextVisible) => {
|
||||
overlayManager.setVisibleOverlayVisible(nextVisible);
|
||||
@@ -1998,13 +1998,13 @@ function setVisibleOverlayVisible(visible: boolean): void {
|
||||
shouldBindVisibleOverlayToMpvSubVisibility(),
|
||||
isMpvConnected: () => Boolean(appState.mpvClient && appState.mpvClient.connected),
|
||||
setMpvSubVisibility: (mpvSubVisible) => {
|
||||
setMpvSubVisibilityRuntimeService(appState.mpvClient, mpvSubVisible);
|
||||
setMpvSubVisibilityRuntime(appState.mpvClient, mpvSubVisible);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setInvisibleOverlayVisible(visible: boolean): void {
|
||||
setInvisibleOverlayVisibleService({
|
||||
setInvisibleOverlayVisibleCore({
|
||||
visible,
|
||||
setInvisibleOverlayVisibleState: (nextVisible) => {
|
||||
overlayManager.setInvisibleOverlayVisible(nextVisible);
|
||||
@@ -2036,16 +2036,16 @@ function handleMpvCommandFromIpc(command: (string | number)[]): void {
|
||||
if (!appState.runtimeOptionsManager) {
|
||||
return { ok: false, error: "Runtime options manager unavailable" };
|
||||
}
|
||||
return applyRuntimeOptionResultRuntimeService(
|
||||
return applyRuntimeOptionResultRuntime(
|
||||
appState.runtimeOptionsManager.cycleOption(id, direction),
|
||||
(text) => showMpvOsd(text),
|
||||
);
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
replayCurrentSubtitle: () => replayCurrentSubtitleRuntimeService(appState.mpvClient),
|
||||
playNextSubtitle: () => playNextSubtitleRuntimeService(appState.mpvClient),
|
||||
replayCurrentSubtitle: () => replayCurrentSubtitleRuntime(appState.mpvClient),
|
||||
playNextSubtitle: () => playNextSubtitleRuntime(appState.mpvClient),
|
||||
sendMpvCommand: (rawCommand: (string | number)[]) =>
|
||||
sendMpvCommandRuntimeService(appState.mpvClient, rawCommand),
|
||||
sendMpvCommandRuntime(appState.mpvClient, rawCommand),
|
||||
isMpvConnected: () => Boolean(appState.mpvClient && appState.mpvClient.connected),
|
||||
hasRuntimeOptionsManager: () => appState.runtimeOptionsManager !== null,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CliArgs, CliCommandSource } from "../cli/args";
|
||||
import { runAppReadyRuntimeService } from "../core/services/startup-service";
|
||||
import type { AppReadyRuntimeDeps } from "../core/services/startup-service";
|
||||
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle-service";
|
||||
import { runAppReadyRuntime } from "../core/services/startup";
|
||||
import type { AppReadyRuntimeDeps } from "../core/services/startup";
|
||||
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle";
|
||||
|
||||
export interface AppLifecycleRuntimeDepsFactoryInput {
|
||||
app: AppLifecycleDepsRuntimeOptions["app"];
|
||||
@@ -96,6 +96,6 @@ export function createAppReadyRuntimeRunner(
|
||||
params: AppReadyRuntimeDepsFactoryInput,
|
||||
): () => Promise<void> {
|
||||
return async () => {
|
||||
await runAppReadyRuntimeService(createAppReadyRuntimeDeps(params));
|
||||
await runAppReadyRuntime(createAppReadyRuntimeDeps(params));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { handleCliCommandService, createCliCommandDepsRuntimeService } from "../core/services";
|
||||
import { handleCliCommand, createCliCommandDepsRuntime } from "../core/services";
|
||||
import type { CliArgs, CliCommandSource } from "../cli/args";
|
||||
import { createCliCommandRuntimeServiceDeps, CliCommandRuntimeServiceDepsParams } from "./dependencies";
|
||||
|
||||
@@ -102,10 +102,10 @@ export function handleCliCommandRuntimeService(
|
||||
source: CliCommandSource,
|
||||
params: CliCommandRuntimeServiceDepsParams,
|
||||
): void {
|
||||
const deps = createCliCommandDepsRuntimeService(
|
||||
const deps = createCliCommandDepsRuntime(
|
||||
createCliCommandRuntimeServiceDeps(params),
|
||||
);
|
||||
handleCliCommandService(args, source, deps);
|
||||
handleCliCommand(args, source, deps);
|
||||
}
|
||||
|
||||
export function handleCliCommandRuntimeServiceWithContext(
|
||||
|
||||
@@ -4,15 +4,15 @@ import {
|
||||
SubsyncManualPayload,
|
||||
} from "../types";
|
||||
import { SubsyncResolvedConfig } from "../subsync/utils";
|
||||
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner-service";
|
||||
import type { IpcDepsRuntimeOptions } from "../core/services/ipc-service";
|
||||
import type { AnkiJimakuIpcRuntimeOptions } from "../core/services/anki-jimaku-service";
|
||||
import type { CliCommandDepsRuntimeOptions } from "../core/services/cli-command-service";
|
||||
import type { HandleMpvCommandFromIpcOptions } from "../core/services/ipc-command-service";
|
||||
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner";
|
||||
import type { IpcDepsRuntimeOptions } from "../core/services/ipc";
|
||||
import type { AnkiJimakuIpcRuntimeOptions } from "../core/services/anki-jimaku";
|
||||
import type { CliCommandDepsRuntimeOptions } from "../core/services/cli-command";
|
||||
import type { HandleMpvCommandFromIpcOptions } from "../core/services/ipc-command";
|
||||
import {
|
||||
cycleRuntimeOptionFromIpcRuntimeService,
|
||||
setRuntimeOptionFromIpcRuntimeService,
|
||||
} from "../core/services/runtime-options-ipc-service";
|
||||
cycleRuntimeOptionFromIpcRuntime,
|
||||
setRuntimeOptionFromIpcRuntime,
|
||||
} from "../core/services/runtime-options-ipc";
|
||||
import { RuntimeOptionsManager } from "../runtime-options";
|
||||
|
||||
export interface RuntimeOptionsIpcDepsParams {
|
||||
@@ -35,14 +35,14 @@ export function createRuntimeOptionsIpcDeps(params: RuntimeOptionsIpcDepsParams)
|
||||
} {
|
||||
return {
|
||||
setRuntimeOption: (id, value) =>
|
||||
setRuntimeOptionFromIpcRuntimeService(
|
||||
setRuntimeOptionFromIpcRuntime(
|
||||
params.getRuntimeOptionsManager(),
|
||||
id as RuntimeOptionId,
|
||||
value as RuntimeOptionValue,
|
||||
(text) => params.showMpvOsd(text),
|
||||
),
|
||||
cycleRuntimeOption: (id, direction) =>
|
||||
cycleRuntimeOptionFromIpcRuntimeService(
|
||||
cycleRuntimeOptionFromIpcRuntime(
|
||||
params.getRuntimeOptionsManager(),
|
||||
id as RuntimeOptionId,
|
||||
direction,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "path";
|
||||
import type { FrequencyDictionaryLookup } from "../types";
|
||||
import { createFrequencyDictionaryLookupService } from "../core/services";
|
||||
import { createFrequencyDictionaryLookup } from "../core/services";
|
||||
|
||||
export interface FrequencyDictionarySearchPathDeps {
|
||||
getDictionaryRoots: () => string[];
|
||||
@@ -47,7 +47,7 @@ export function getFrequencyDictionarySearchPaths(
|
||||
export async function initializeFrequencyDictionaryLookup(
|
||||
deps: FrequencyDictionaryRuntimeDeps,
|
||||
): Promise<void> {
|
||||
const lookup = await createFrequencyDictionaryLookupService({
|
||||
const lookup = await createFrequencyDictionaryLookup({
|
||||
searchPaths: deps.getSearchPaths(),
|
||||
log: deps.log,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RuntimeOptionApplyResult, RuntimeOptionId } from "../types";
|
||||
import { handleMpvCommandFromIpcService } from "../core/services";
|
||||
import { handleMpvCommandFromIpc } from "../core/services";
|
||||
import { createMpvCommandRuntimeServiceDeps } from "./dependencies";
|
||||
import { SPECIAL_COMMANDS } from "../config";
|
||||
|
||||
@@ -22,7 +22,7 @@ export function handleMpvCommandFromIpcRuntime(
|
||||
command: (string | number)[],
|
||||
deps: MpvCommandFromIpcRuntimeDeps,
|
||||
): void {
|
||||
handleMpvCommandFromIpcService(
|
||||
handleMpvCommandFromIpc(
|
||||
command,
|
||||
createMpvCommandRuntimeServiceDeps({
|
||||
specialCommands: SPECIAL_COMMANDS,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
createIpcDepsRuntimeService,
|
||||
registerAnkiJimakuIpcRuntimeService,
|
||||
registerIpcHandlersService,
|
||||
createIpcDepsRuntime,
|
||||
registerAnkiJimakuIpcRuntime,
|
||||
registerIpcHandlers,
|
||||
} from "../core/services";
|
||||
import { registerAnkiJimakuIpcHandlers } from "../core/services/anki-jimaku-ipc-service";
|
||||
import { registerAnkiJimakuIpcHandlers } from "../core/services/anki-jimaku-ipc";
|
||||
import {
|
||||
createAnkiJimakuIpcRuntimeServiceDeps,
|
||||
AnkiJimakuIpcRuntimeServiceDepsParams,
|
||||
@@ -25,15 +25,15 @@ export interface RegisterIpcRuntimeServicesParams {
|
||||
export function registerMainIpcRuntimeServices(
|
||||
params: MainIpcRuntimeServiceDepsParams,
|
||||
): void {
|
||||
registerIpcHandlersService(
|
||||
createIpcDepsRuntimeService(createMainIpcRuntimeServiceDeps(params)),
|
||||
registerIpcHandlers(
|
||||
createIpcDepsRuntime(createMainIpcRuntimeServiceDeps(params)),
|
||||
);
|
||||
}
|
||||
|
||||
export function registerAnkiJimakuIpcRuntimeServices(
|
||||
params: AnkiJimakuIpcRuntimeServiceDepsParams,
|
||||
): void {
|
||||
registerAnkiJimakuIpcRuntimeService(
|
||||
registerAnkiJimakuIpcRuntime(
|
||||
createAnkiJimakuIpcRuntimeServiceDeps(params),
|
||||
registerAnkiJimakuIpcHandlers,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as path from "path";
|
||||
import type { JlptLevel } from "../types";
|
||||
|
||||
import { createJlptVocabularyLookupService } from "../core/services";
|
||||
import { createJlptVocabularyLookup } from "../core/services";
|
||||
|
||||
export interface JlptDictionarySearchPathDeps {
|
||||
getDictionaryRoots: () => string[];
|
||||
@@ -39,7 +39,7 @@ export async function initializeJlptDictionaryLookup(
|
||||
deps: JlptDictionaryRuntimeDeps,
|
||||
): Promise<void> {
|
||||
deps.setJlptLevelLookup(
|
||||
await createJlptVocabularyLookupService({
|
||||
await createJlptVocabularyLookup({
|
||||
searchPaths: deps.getSearchPaths(),
|
||||
log: deps.log,
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { updateCurrentMediaPathService } from "../core/services";
|
||||
import { updateCurrentMediaPath } from "../core/services";
|
||||
|
||||
import type { SubtitlePosition } from "../types";
|
||||
|
||||
@@ -31,7 +31,7 @@ export function createMediaRuntimeService(
|
||||
deps.setCurrentMediaTitle(null);
|
||||
}
|
||||
|
||||
updateCurrentMediaPathService({
|
||||
updateCurrentMediaPath({
|
||||
mediaPath,
|
||||
currentMediaPath: deps.getCurrentMediaPath(),
|
||||
pendingSubtitlePosition: deps.getPendingSubtitlePosition(),
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
shortcutMatchesInputForLocalFallback,
|
||||
} from "../core/services";
|
||||
import {
|
||||
refreshOverlayShortcutsRuntimeService,
|
||||
registerOverlayShortcutsService,
|
||||
syncOverlayShortcutsRuntimeService,
|
||||
unregisterOverlayShortcutsRuntimeService,
|
||||
refreshOverlayShortcutsRuntime,
|
||||
registerOverlayShortcuts,
|
||||
syncOverlayShortcutsRuntime,
|
||||
unregisterOverlayShortcutsRuntime,
|
||||
} from "../core/services";
|
||||
import { runOverlayShortcutLocalFallback } from "../core/services/overlay-shortcut-handler";
|
||||
|
||||
@@ -102,7 +102,7 @@ export function createOverlayShortcutsRuntimeService(
|
||||
),
|
||||
registerOverlayShortcuts: () => {
|
||||
input.setShortcutsRegistered(
|
||||
registerOverlayShortcutsService(
|
||||
registerOverlayShortcuts(
|
||||
input.getConfiguredShortcuts(),
|
||||
handlers.overlayHandlers,
|
||||
),
|
||||
@@ -110,7 +110,7 @@ export function createOverlayShortcutsRuntimeService(
|
||||
},
|
||||
unregisterOverlayShortcuts: () => {
|
||||
input.setShortcutsRegistered(
|
||||
unregisterOverlayShortcutsRuntimeService(
|
||||
unregisterOverlayShortcutsRuntime(
|
||||
input.getShortcutsRegistered(),
|
||||
getShortcutLifecycleDeps(),
|
||||
),
|
||||
@@ -118,7 +118,7 @@ export function createOverlayShortcutsRuntimeService(
|
||||
},
|
||||
syncOverlayShortcuts: () => {
|
||||
input.setShortcutsRegistered(
|
||||
syncOverlayShortcutsRuntimeService(
|
||||
syncOverlayShortcutsRuntime(
|
||||
shouldOverlayShortcutsBeActive(),
|
||||
input.getShortcutsRegistered(),
|
||||
getShortcutLifecycleDeps(),
|
||||
@@ -127,7 +127,7 @@ export function createOverlayShortcutsRuntimeService(
|
||||
},
|
||||
refreshOverlayShortcuts: () => {
|
||||
input.setShortcutsRegistered(
|
||||
refreshOverlayShortcutsRuntimeService(
|
||||
refreshOverlayShortcutsRuntime(
|
||||
shouldOverlayShortcutsBeActive(),
|
||||
input.getShortcutsRegistered(),
|
||||
getShortcutLifecycleDeps(),
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { BrowserWindow } from "electron";
|
||||
import type { BaseWindowTracker } from "../window-trackers";
|
||||
import type { WindowGeometry } from "../types";
|
||||
import {
|
||||
syncInvisibleOverlayMousePassthroughService,
|
||||
updateInvisibleOverlayVisibilityService,
|
||||
updateVisibleOverlayVisibilityService,
|
||||
syncInvisibleOverlayMousePassthrough,
|
||||
updateInvisibleOverlayVisibility,
|
||||
updateVisibleOverlayVisibility,
|
||||
} from "../core/services";
|
||||
|
||||
export interface OverlayVisibilityRuntimeDeps {
|
||||
@@ -48,7 +48,7 @@ export function createOverlayVisibilityRuntimeService(
|
||||
|
||||
return {
|
||||
updateVisibleOverlayVisibility(): void {
|
||||
updateVisibleOverlayVisibilityService({
|
||||
updateVisibleOverlayVisibility({
|
||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
||||
mainWindow: deps.getMainWindow(),
|
||||
windowTracker: deps.getWindowTracker(),
|
||||
@@ -66,7 +66,7 @@ export function createOverlayVisibilityRuntimeService(
|
||||
},
|
||||
|
||||
updateInvisibleOverlayVisibility(): void {
|
||||
updateInvisibleOverlayVisibilityService({
|
||||
updateInvisibleOverlayVisibility({
|
||||
invisibleWindow: deps.getInvisibleWindow(),
|
||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
||||
invisibleOverlayVisible: deps.getInvisibleOverlayVisible(),
|
||||
@@ -81,7 +81,7 @@ export function createOverlayVisibilityRuntimeService(
|
||||
},
|
||||
|
||||
syncInvisibleOverlayMousePassthrough(): void {
|
||||
syncInvisibleOverlayMousePassthroughService({
|
||||
syncInvisibleOverlayMousePassthrough({
|
||||
hasInvisibleWindow,
|
||||
setIgnoreMouseEvents,
|
||||
visibleOverlayVisible: deps.getVisibleOverlayVisible(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CliArgs, CliCommandSource } from "../cli/args";
|
||||
import { createAppLifecycleDepsRuntimeService } from "../core/services";
|
||||
import { startAppLifecycleService } from "../core/services/app-lifecycle-service";
|
||||
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle-service";
|
||||
import { createAppLifecycleDepsRuntime } from "../core/services";
|
||||
import { startAppLifecycle } from "../core/services/app-lifecycle";
|
||||
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle";
|
||||
import { createAppLifecycleRuntimeDeps } from "./app-lifecycle";
|
||||
|
||||
export interface AppLifecycleRuntimeRunnerParams {
|
||||
@@ -22,9 +22,9 @@ export function createAppLifecycleRuntimeRunner(
|
||||
params: AppLifecycleRuntimeRunnerParams,
|
||||
): (args: CliArgs) => void {
|
||||
return (args: CliArgs): void => {
|
||||
startAppLifecycleService(
|
||||
startAppLifecycle(
|
||||
args,
|
||||
createAppLifecycleDepsRuntimeService(
|
||||
createAppLifecycleDepsRuntime(
|
||||
createAppLifecycleRuntimeDeps({
|
||||
app: params.app,
|
||||
platform: params.platform,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CliArgs } from "../cli/args";
|
||||
import type { ResolvedConfig } from "../types";
|
||||
import type { StartupBootstrapRuntimeDeps } from "../core/services/startup-service";
|
||||
import type { StartupBootstrapRuntimeDeps } from "../core/services/startup";
|
||||
import type { LogLevelSource } from "../logger";
|
||||
|
||||
export interface StartupBootstrapRuntimeFactoryDeps {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { SubsyncResolvedConfig } from "../subsync/utils";
|
||||
import type { SubsyncManualPayload, SubsyncManualRunRequest, SubsyncResult } from "../types";
|
||||
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner-service";
|
||||
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner";
|
||||
import { createSubsyncRuntimeDeps } from "./dependencies";
|
||||
import { runSubsyncManualFromIpcRuntimeService, triggerSubsyncFromConfigRuntimeService } from "../core/services";
|
||||
import {
|
||||
runSubsyncManualFromIpcRuntime as runSubsyncManualFromIpcRuntimeCore,
|
||||
triggerSubsyncFromConfigRuntime as triggerSubsyncFromConfigRuntimeCore,
|
||||
} from "../core/services";
|
||||
|
||||
export interface SubsyncRuntimeServiceInput {
|
||||
getMpvClient: SubsyncRuntimeDeps["getMpvClient"];
|
||||
@@ -51,14 +54,14 @@ export function createSubsyncRuntimeServiceDeps(
|
||||
export function triggerSubsyncFromConfigRuntime(
|
||||
params: SubsyncRuntimeServiceInput,
|
||||
): Promise<void> {
|
||||
return triggerSubsyncFromConfigRuntimeService(createSubsyncRuntimeServiceDeps(params));
|
||||
return triggerSubsyncFromConfigRuntimeCore(createSubsyncRuntimeServiceDeps(params));
|
||||
}
|
||||
|
||||
export async function runSubsyncManualFromIpcRuntime(
|
||||
request: SubsyncManualRunRequest,
|
||||
params: SubsyncRuntimeServiceInput,
|
||||
): Promise<SubsyncResult> {
|
||||
return runSubsyncManualFromIpcRuntimeService(
|
||||
return runSubsyncManualFromIpcRuntimeCore(
|
||||
request,
|
||||
createSubsyncRuntimeServiceDeps(params),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user