mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 12:55:16 -07:00
feat(config): add configuration window (#70)
This commit is contained in:
@@ -29,27 +29,85 @@ function isEqual(a: unknown, b: unknown): boolean {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function pathStartsWith(path: string, prefix: string): boolean {
|
||||
return path === prefix || path.startsWith(`${prefix}.`);
|
||||
}
|
||||
|
||||
function collectChangedPaths(prev: unknown, next: unknown, prefix = ''): string[] {
|
||||
if (isEqual(prev, next)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isRecord(prev) || !isRecord(next)) {
|
||||
return prefix ? [prefix] : [];
|
||||
}
|
||||
|
||||
const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);
|
||||
return [...keys].flatMap((key) =>
|
||||
collectChangedPaths(prev[key], next[key], prefix ? `${prefix}.${key}` : key),
|
||||
);
|
||||
}
|
||||
|
||||
const HOT_RELOAD_ROOTS = ['subtitleStyle', 'keybindings', 'shortcuts', 'subtitleSidebar'] as const;
|
||||
|
||||
const HOT_RELOAD_EXACT_OR_PREFIX_PATHS = [
|
||||
'secondarySub.defaultMode',
|
||||
'mpv.aniskipButtonKey',
|
||||
'ankiConnect.ai.enabled',
|
||||
'stats.toggleKey',
|
||||
'stats.markWatchedKey',
|
||||
'logging.level',
|
||||
'youtube.primarySubLanguages',
|
||||
'jimaku',
|
||||
'subsync',
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
'ankiConnect.knownWords.addMinedWordsImmediately',
|
||||
'ankiConnect.knownWords.matchMode',
|
||||
'ankiConnect.knownWords.decks',
|
||||
'ankiConnect.nPlusOne.enabled',
|
||||
'ankiConnect.nPlusOne.minSentenceWords',
|
||||
'ankiConnect.fields.word',
|
||||
'ankiConnect.fields.audio',
|
||||
'ankiConnect.fields.image',
|
||||
'ankiConnect.fields.sentence',
|
||||
'ankiConnect.fields.miscInfo',
|
||||
'ankiConnect.isLapis.sentenceCardModel',
|
||||
'ankiConnect.isKiku.fieldGrouping',
|
||||
] as const;
|
||||
|
||||
function hotReloadFieldForChangedPath(path: string): string | null {
|
||||
for (const root of HOT_RELOAD_ROOTS) {
|
||||
if (pathStartsWith(path, root)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
for (const hotPath of HOT_RELOAD_EXACT_OR_PREFIX_PATHS) {
|
||||
if (pathStartsWith(path, hotPath)) {
|
||||
return hotPath === 'jimaku' || hotPath === 'subsync' ? path : hotPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function classifyDiff(prev: ResolvedConfig, next: ResolvedConfig): ConfigHotReloadDiff {
|
||||
const hotReloadFields: string[] = [];
|
||||
const restartRequiredFields: string[] = [];
|
||||
const hotReloadFieldSet = new Set<string>();
|
||||
const changedPaths = collectChangedPaths(prev, next);
|
||||
|
||||
if (!isEqual(prev.subtitleStyle, next.subtitleStyle)) {
|
||||
hotReloadFields.push('subtitleStyle');
|
||||
}
|
||||
if (!isEqual(prev.keybindings, next.keybindings)) {
|
||||
hotReloadFields.push('keybindings');
|
||||
}
|
||||
if (!isEqual(prev.shortcuts, next.shortcuts)) {
|
||||
hotReloadFields.push('shortcuts');
|
||||
}
|
||||
if (!isEqual(prev.subtitleSidebar, next.subtitleSidebar)) {
|
||||
hotReloadFields.push('subtitleSidebar');
|
||||
}
|
||||
if (prev.secondarySub.defaultMode !== next.secondarySub.defaultMode) {
|
||||
hotReloadFields.push('secondarySub.defaultMode');
|
||||
}
|
||||
if (!isEqual(prev.ankiConnect.ai, next.ankiConnect.ai)) {
|
||||
hotReloadFields.push('ankiConnect.ai');
|
||||
for (const path of changedPaths) {
|
||||
const hotReloadField = hotReloadFieldForChangedPath(path);
|
||||
if (hotReloadField) {
|
||||
hotReloadFieldSet.add(hotReloadField);
|
||||
}
|
||||
}
|
||||
|
||||
const keys = new Set([
|
||||
@@ -67,37 +125,16 @@ function classifyDiff(prev: ResolvedConfig, next: ResolvedConfig): ConfigHotRelo
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'secondarySub') {
|
||||
const normalizedPrev = {
|
||||
...prev.secondarySub,
|
||||
defaultMode: next.secondarySub.defaultMode,
|
||||
};
|
||||
if (!isEqual(normalizedPrev, next.secondarySub)) {
|
||||
restartRequiredFields.push('secondarySub');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'ankiConnect') {
|
||||
const normalizedPrev = {
|
||||
...prev.ankiConnect,
|
||||
ai: {
|
||||
enabled: next.ankiConnect.ai.enabled,
|
||||
model: prev.ankiConnect.ai.model,
|
||||
systemPrompt: prev.ankiConnect.ai.systemPrompt,
|
||||
},
|
||||
};
|
||||
if (!isEqual(normalizedPrev, next.ankiConnect)) {
|
||||
restartRequiredFields.push('ankiConnect');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isEqual(prev[key], next[key])) {
|
||||
const changedPathsForKey = changedPaths.filter((path) => pathStartsWith(path, String(key)));
|
||||
const hasRestartRequiredChange = changedPathsForKey.some(
|
||||
(path) => !hotReloadFieldForChangedPath(path),
|
||||
);
|
||||
if (hasRestartRequiredChange) {
|
||||
restartRequiredFields.push(String(key));
|
||||
}
|
||||
}
|
||||
|
||||
hotReloadFields.push(...hotReloadFieldSet);
|
||||
return { hotReloadFields, restartRequiredFields };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user