refactor: extract runtime option ipc helpers

This commit is contained in:
2026-02-09 23:19:36 -08:00
parent 688eedbfc0
commit 35c2dd0e32
4 changed files with 135 additions and 30 deletions

View File

@@ -16,7 +16,7 @@
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
"test:config": "pnpm run build && node --test dist/config/config.test.js",
"test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-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/mpv-runtime-service.test.js",
"test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-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/mpv-runtime-service.test.js dist/core/services/runtime-options-runtime-service.test.js",
"test:subtitle": "pnpm run build && node --test dist/subtitle/stages.test.js dist/subtitle/pipeline.test.js",
"generate:config-example": "pnpm run build && node dist/generate-config-example.js",
"start": "pnpm run build && electron . --start",

View File

@@ -0,0 +1,53 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
applyRuntimeOptionResultRuntimeService,
cycleRuntimeOptionFromIpcRuntimeService,
setRuntimeOptionFromIpcRuntimeService,
} from "./runtime-options-runtime-service";
test("applyRuntimeOptionResultRuntimeService emits success OSD message", () => {
const osd: string[] = [];
const result = applyRuntimeOptionResultRuntimeService(
{ ok: true, osdMessage: "Updated" },
(text) => {
osd.push(text);
},
);
assert.equal(result.ok, true);
assert.deepEqual(osd, ["Updated"]);
});
test("setRuntimeOptionFromIpcRuntimeService returns unavailable when manager missing", () => {
const osd: string[] = [];
const result = setRuntimeOptionFromIpcRuntimeService(
null,
"anki.autoUpdateNewCards",
true,
(text) => {
osd.push(text);
},
);
assert.equal(result.ok, false);
assert.equal(result.error, "Runtime options manager unavailable");
assert.deepEqual(osd, []);
});
test("cycleRuntimeOptionFromIpcRuntimeService reports errors once", () => {
const osd: string[] = [];
const result = cycleRuntimeOptionFromIpcRuntimeService(
{
setOptionValue: () => ({ ok: true }),
cycleOption: () => ({ ok: false, error: "bad option" }),
},
"anki.kikuFieldGrouping",
1,
(text) => {
osd.push(text);
},
);
assert.equal(result.ok, false);
assert.equal(result.error, "bad option");
assert.deepEqual(osd, ["bad option"]);
});

View File

@@ -0,0 +1,64 @@
import {
RuntimeOptionApplyResult,
RuntimeOptionId,
RuntimeOptionValue,
} from "../../types";
export interface RuntimeOptionsManagerLike {
setOptionValue: (
id: RuntimeOptionId,
value: RuntimeOptionValue,
) => RuntimeOptionApplyResult;
cycleOption: (
id: RuntimeOptionId,
direction: 1 | -1,
) => RuntimeOptionApplyResult;
}
export function applyRuntimeOptionResultRuntimeService(
result: RuntimeOptionApplyResult,
showMpvOsd: (text: string) => void,
): RuntimeOptionApplyResult {
if (result.ok && result.osdMessage) {
showMpvOsd(result.osdMessage);
}
return result;
}
export function setRuntimeOptionFromIpcRuntimeService(
manager: RuntimeOptionsManagerLike | null,
id: RuntimeOptionId,
value: RuntimeOptionValue,
showMpvOsd: (text: string) => void,
): RuntimeOptionApplyResult {
if (!manager) {
return { ok: false, error: "Runtime options manager unavailable" };
}
const result = applyRuntimeOptionResultRuntimeService(
manager.setOptionValue(id, value),
showMpvOsd,
);
if (!result.ok && result.error) {
showMpvOsd(result.error);
}
return result;
}
export function cycleRuntimeOptionFromIpcRuntimeService(
manager: RuntimeOptionsManagerLike | null,
id: RuntimeOptionId,
direction: 1 | -1,
showMpvOsd: (text: string) => void,
): RuntimeOptionApplyResult {
if (!manager) {
return { ok: false, error: "Runtime options manager unavailable" };
}
const result = applyRuntimeOptionResultRuntimeService(
manager.cycleOption(id, direction),
showMpvOsd,
);
if (!result.ok && result.error) {
showMpvOsd(result.error);
}
return result;
}

View File

@@ -65,7 +65,6 @@ import {
KikuFieldGroupingChoice,
KikuMergePreviewRequest,
KikuMergePreviewResponse,
RuntimeOptionApplyResult,
RuntimeOptionId,
RuntimeOptionState,
RuntimeOptionValue,
@@ -139,6 +138,11 @@ import {
setMpvSubVisibilityRuntimeService,
showMpvOsdRuntimeService,
} from "./core/services/mpv-runtime-service";
import {
applyRuntimeOptionResultRuntimeService,
cycleRuntimeOptionFromIpcRuntimeService,
setRuntimeOptionFromIpcRuntimeService,
} from "./core/services/runtime-options-runtime-service";
import { showDesktopNotification } from "./core/utils/notification";
import { openYomitanSettingsWindow } from "./core/services/yomitan-settings-service";
import { tokenizeSubtitleService } from "./core/services/tokenizer-service";
@@ -306,15 +310,6 @@ function broadcastRuntimeOptionsChanged(): void { broadcastToOverlayWindows("run
function setOverlayDebugVisualizationEnabled(enabled: boolean): void { if (overlayDebugVisualizationEnabled === enabled) return; overlayDebugVisualizationEnabled = enabled; broadcastToOverlayWindows("overlay-debug-visualization:set", overlayDebugVisualizationEnabled); }
function applyRuntimeOptionResult(
result: RuntimeOptionApplyResult,
): RuntimeOptionApplyResult {
if (result.ok && result.osdMessage) {
showMpvOsd(result.osdMessage);
}
return result;
}
function openRuntimeOptionsPalette(): void { sendToVisibleOverlay("runtime-options:open", undefined, { restoreOnModalClose: "runtime-options" }); }
function getResolvedConfig() { return configService.getConfig(); }
@@ -1134,8 +1129,9 @@ function handleMpvCommandFromIpc(command: (string | number)[]): void {
if (!runtimeOptionsManager) {
return { ok: false, error: "Runtime options manager unavailable" };
}
return applyRuntimeOptionResult(
return applyRuntimeOptionResultRuntimeService(
runtimeOptionsManager.cycleOption(id, direction),
(text) => showMpvOsd(text),
);
},
showMpvOsd: (text) => showMpvOsd(text),
@@ -1195,28 +1191,20 @@ registerIpcHandlersService({
getAnkiConnectStatus: () => ankiIntegration !== null,
getRuntimeOptions: () => getRuntimeOptionsState(),
setRuntimeOption: (id, value) => {
if (!runtimeOptionsManager) {
return { ok: false, error: "Runtime options manager unavailable" };
}
const result = applyRuntimeOptionResult(
runtimeOptionsManager.setOptionValue(id as RuntimeOptionId, value as RuntimeOptionValue),
return setRuntimeOptionFromIpcRuntimeService(
runtimeOptionsManager,
id as RuntimeOptionId,
value as RuntimeOptionValue,
(text) => showMpvOsd(text),
);
if (!result.ok && result.error) {
showMpvOsd(result.error);
}
return result;
},
cycleRuntimeOption: (id, direction) => {
if (!runtimeOptionsManager) {
return { ok: false, error: "Runtime options manager unavailable" };
}
const result = applyRuntimeOptionResult(
runtimeOptionsManager.cycleOption(id as RuntimeOptionId, direction),
return cycleRuntimeOptionFromIpcRuntimeService(
runtimeOptionsManager,
id as RuntimeOptionId,
direction,
(text) => showMpvOsd(text),
);
if (!result.ok && result.error) {
showMpvOsd(result.error);
}
return result;
},
});