feat(config): add configuration window (#70)

This commit is contained in:
2026-05-21 04:16:21 -07:00
committed by GitHub
parent a54f03f0cd
commit dc52bc2fba
287 changed files with 14507 additions and 8134 deletions
+86 -7
View File
@@ -3,6 +3,7 @@ import { compileSessionBindings } from '../../core/services/session-bindings';
import { resolveKeybindings } from '../../core/utils/keybindings';
import { resolveConfiguredShortcuts } from '../../core/utils/shortcut-config';
import { DEFAULT_CONFIG, DEFAULT_KEYBINDINGS } from '../../config';
import type { AnkiConnectConfig } from '../../types/anki';
import type { ConfigHotReloadPayload, ResolvedConfig, SecondarySubMode } from '../../types';
type ConfigHotReloadAppliedDeps = {
@@ -14,9 +15,11 @@ type ConfigHotReloadAppliedDeps = {
refreshGlobalAndOverlayShortcuts: () => void;
setSecondarySubMode: (mode: SecondarySubMode) => void;
broadcastToOverlayWindows: (channel: string, payload: unknown) => void;
applyAnkiRuntimeConfigPatch: (patch: {
ai: ResolvedConfig['ankiConnect']['ai']['enabled'];
}) => void;
applyAnkiRuntimeConfigPatch: (patch: Partial<AnkiConnectConfig>) => void;
invalidateTokenizationCache?: () => void;
refreshSubtitlePrefetch?: () => void;
refreshCurrentSubtitle?: () => void;
setLogLevel?: (level: ResolvedConfig['logging']['level']) => void;
};
type ConfigHotReloadMessageDeps = {
@@ -30,8 +33,8 @@ export function resolveSubtitleStyleForRenderer(config: ResolvedConfig) {
}
return {
...config.subtitleStyle,
nPlusOneColor: config.ankiConnect.nPlusOne.nPlusOne,
knownWordColor: config.ankiConnect.knownWords.color,
nPlusOneColor: config.subtitleStyle.nPlusOneColor,
knownWordColor: config.subtitleStyle.knownWordColor,
nameMatchColor: config.subtitleStyle.nameMatchColor,
enableJlpt: config.subtitleStyle.enableJlpt,
frequencyDictionary: config.subtitleStyle.frequencyDictionary,
@@ -44,6 +47,7 @@ export function buildConfigHotReloadPayload(config: ResolvedConfig): ConfigHotRe
keybindings,
shortcuts: resolveConfiguredShortcuts(config, DEFAULT_CONFIG),
statsToggleKey: config.stats.toggleKey,
statsMarkWatchedKey: config.stats.markWatchedKey,
platform:
process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'linux',
rawConfig: config,
@@ -59,6 +63,70 @@ export function buildConfigHotReloadPayload(config: ResolvedConfig): ConfigHotRe
};
}
function hasAnyHotReloadField(diff: ConfigHotReloadDiff, prefixes: string[]): boolean {
return diff.hotReloadFields.some((field) =>
prefixes.some((prefix) => field === prefix || field.startsWith(`${prefix}.`)),
);
}
function buildAnkiRuntimeConfigPatch(
diff: ConfigHotReloadDiff,
config: ResolvedConfig,
): Partial<AnkiConnectConfig> | null {
const patch: Partial<AnkiConnectConfig> = {};
if (diff.hotReloadFields.includes('ankiConnect.ai')) {
patch.ai = config.ankiConnect.ai.enabled;
}
if (diff.hotReloadFields.includes('ankiConnect.ai.enabled')) {
patch.ai = config.ankiConnect.ai.enabled;
}
if (diff.hotReloadFields.includes('ankiConnect.behavior.autoUpdateNewCards')) {
patch.behavior = { autoUpdateNewCards: config.ankiConnect.behavior.autoUpdateNewCards };
}
if (hasAnyHotReloadField(diff, ['ankiConnect.knownWords'])) {
patch.knownWords = config.ankiConnect.knownWords;
}
if (hasAnyHotReloadField(diff, ['ankiConnect.nPlusOne'])) {
patch.nPlusOne = config.ankiConnect.nPlusOne;
}
const fieldPatch: NonNullable<AnkiConnectConfig['fields']> = {};
if (diff.hotReloadFields.includes('ankiConnect.fields.word')) {
fieldPatch.word = config.ankiConnect.fields.word;
}
if (diff.hotReloadFields.includes('ankiConnect.fields.audio')) {
fieldPatch.audio = config.ankiConnect.fields.audio;
}
if (diff.hotReloadFields.includes('ankiConnect.fields.image')) {
fieldPatch.image = config.ankiConnect.fields.image;
}
if (diff.hotReloadFields.includes('ankiConnect.fields.sentence')) {
fieldPatch.sentence = config.ankiConnect.fields.sentence;
}
if (diff.hotReloadFields.includes('ankiConnect.fields.miscInfo')) {
fieldPatch.miscInfo = config.ankiConnect.fields.miscInfo;
}
if (Object.keys(fieldPatch).length > 0) {
patch.fields = fieldPatch;
}
if (diff.hotReloadFields.includes('ankiConnect.isLapis.sentenceCardModel')) {
patch.isLapis = { sentenceCardModel: config.ankiConnect.isLapis.sentenceCardModel };
}
if (diff.hotReloadFields.includes('ankiConnect.isKiku.fieldGrouping')) {
patch.isKiku = { fieldGrouping: config.ankiConnect.isKiku.fieldGrouping };
}
return Object.keys(patch).length > 0 ? patch : null;
}
function hasAnnotationRuntimeHotReload(diff: ConfigHotReloadDiff): boolean {
return hasAnyHotReloadField(diff, [
'ankiConnect.knownWords',
'ankiConnect.nPlusOne',
'ankiConnect.fields.word',
]);
}
export function createConfigHotReloadAppliedHandler(deps: ConfigHotReloadAppliedDeps) {
return (diff: ConfigHotReloadDiff, config: ResolvedConfig): void => {
const payload = buildConfigHotReloadPayload(config);
@@ -74,8 +142,19 @@ export function createConfigHotReloadAppliedHandler(deps: ConfigHotReloadApplied
deps.broadcastToOverlayWindows('secondary-subtitle:mode', payload.secondarySubMode);
}
if (diff.hotReloadFields.includes('ankiConnect.ai')) {
deps.applyAnkiRuntimeConfigPatch({ ai: config.ankiConnect.ai.enabled });
const ankiPatch = buildAnkiRuntimeConfigPatch(diff, config);
if (ankiPatch) {
deps.applyAnkiRuntimeConfigPatch(ankiPatch);
}
if (hasAnnotationRuntimeHotReload(diff)) {
deps.invalidateTokenizationCache?.();
deps.refreshSubtitlePrefetch?.();
deps.refreshCurrentSubtitle?.();
}
if (diff.hotReloadFields.includes('logging.level')) {
deps.setLogLevel?.(config.logging.level);
}
if (diff.hotReloadFields.length > 0) {