Files
SubMiner/src/main/runtime/config-settings-save.ts
T
sudacode 6ba91780c1 feat(config): unify mpv plugin options under main config and add CSS/Ani
- Replace subminer.conf plugin config with mpv.* fields in config.jsonc
- Add socketPath, backend, autoStartSubMiner, pauseUntilOverlayReady, aniskipEnabled/buttonKey, subminerBinaryPath to mpv config
- Add subtitleSidebar.css field; migrate legacy sidebar appearance fields
- Add paintOrder and WebkitTextStroke to subtitle style options
- Update default subtitle/sidebar fontFamily to CJK-first stack
- Fix overlay visible state surviving mpv y-r restart
- Fix live config saves applying subtitle CSS immediately to open overlays
- Migrate legacy primary/secondary subtitle appearance into subtitleStyle.css on load
- Switch AniSkip button key setting to click-to-learn key capture
2026-05-18 03:07:39 -07:00

104 lines
3.3 KiB
TypeScript

import type { ReloadConfigStrictResult } from '../../config';
import { applyConfigSettingsPatchToContent } from '../../config/settings/jsonc-edit';
import type { ConfigValidationWarning, ResolvedConfig } from '../../types/config';
import type {
ConfigSettingsPatch,
ConfigSettingsSaveResult,
ConfigSettingsSnapshot,
} from '../../types/settings';
export interface ConfigSettingsHotReloadDiff {
hotReloadFields: string[];
restartRequiredFields: string[];
}
export interface ConfigSettingsSaveDeps {
getConfigPath(): string;
getCurrentConfig(): ResolvedConfig;
getWarnings(): ConfigValidationWarning[];
getSnapshot(): ConfigSettingsSnapshot;
fileExists(path: string): boolean;
readText(path: string): string;
writeTextAtomically(path: string, content: string): void;
deleteFile?(path: string): void;
reloadConfigStrict(): ReloadConfigStrictResult;
classifyDiff(prev: ResolvedConfig, next: ResolvedConfig): ConfigSettingsHotReloadDiff;
getRestartRequiredSections(restartRequiredFields: string[]): string[];
onHotReloadApplied?: (diff: ConfigSettingsHotReloadDiff, config: ResolvedConfig) => void;
}
export function createSaveConfigSettingsPatchHandler(deps: ConfigSettingsSaveDeps) {
return (patch: ConfigSettingsPatch): ConfigSettingsSaveResult => {
if (patch.operations.length === 0) {
return {
ok: true,
snapshot: deps.getSnapshot(),
hotReloadFields: [],
restartRequiredFields: [],
restartRequiredSections: [],
};
}
const configPath = deps.getConfigPath();
const previousConfig = deps.getCurrentConfig();
const previousWarnings = deps.getWarnings();
const hadExistingConfig = deps.fileExists(configPath);
const content = hadExistingConfig ? deps.readText(configPath) : '{}\n';
const candidate = applyConfigSettingsPatchToContent({
content,
operations: patch.operations,
previousWarnings,
});
if (!candidate.ok) {
return {
ok: false,
warnings: candidate.warnings,
error: candidate.error,
hotReloadFields: [],
restartRequiredFields: [],
restartRequiredSections: [],
};
}
deps.writeTextAtomically(configPath, candidate.content);
const reloadResult = deps.reloadConfigStrict();
if (!reloadResult.ok) {
try {
if (hadExistingConfig) {
deps.writeTextAtomically(configPath, content);
} else if (deps.deleteFile) {
deps.deleteFile(configPath);
} else {
deps.writeTextAtomically(configPath, content);
}
deps.reloadConfigStrict();
} catch {
// Best-effort rollback; preserve original reload error for caller.
}
return {
ok: false,
warnings: [],
error: reloadResult.error,
hotReloadFields: [],
restartRequiredFields: [],
restartRequiredSections: [],
};
}
const diff = deps.classifyDiff(previousConfig, reloadResult.config);
if (diff.hotReloadFields.length > 0) {
deps.onHotReloadApplied?.(diff, reloadResult.config);
}
return {
ok: true,
snapshot: deps.getSnapshot(),
warnings: reloadResult.warnings,
hotReloadFields: diff.hotReloadFields,
restartRequiredFields: diff.restartRequiredFields,
restartRequiredSections: deps.getRestartRequiredSections(diff.restartRequiredFields),
};
};
}