mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
feat: expand hot-reload to logging, jimaku, subsync, and Anki sub-fields
- Mark logging.level, stats keys, jimaku.*, subsync.*, and granular ankiConnect fields (knownWords, nPlusOne, fields, isLapis, isKiku, behavior) as hot-reloadable - Refactor classifyConfigHotReloadDiff to path-walk diffing instead of per-key branches - Wire setLogLevel, invalidateTokenizationCache, refreshSubtitlePrefetch, refreshCurrentSubtitle into hot-reload applied handler - Exclude ai.* and ankiConnect.ai.* prefixes from config window; hide fields.translation - Update docs and config.example.jsonc hot-reload annotations
This commit is contained in:
@@ -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 = {
|
||||
@@ -59,6 +62,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 +141,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) {
|
||||
|
||||
Reference in New Issue
Block a user