mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 08:44:41 +08:00
refactor: split useSettings hook into specialized hooks
Before optimization: - useSettings.ts: 516 lines (single monolithic hook) After optimization: - useSettingsForm.ts: 158 lines (form state management) - useDirectorySettings.ts: 297 lines (directory management) - useSettingsMetadata.ts: 95 lines (metadata management) - useSettings.ts: 215 lines (composition layer) - Total: 765 lines (+249 lines, but with clear separation of concerns) Benefits: ✅ Single Responsibility Principle: each hook focuses on one domain ✅ Testability: independent hooks are easier to unit test ✅ Reusability: specialized hooks can be reused in other components ✅ Maintainability: reduced cognitive load per file ✅ Zero breaking changes: SettingsDialog auto-adapted to new interface Technical details: - useSettingsForm: pure form state + language sync - useDirectorySettings: directory selection/reset + default value computation - useSettingsMetadata: config path + portable mode + restart flag - useSettings: composition layer + save logic + reset logic
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettingsQuery } from "@/lib/query";
|
||||
import type { Settings } from "@/types";
|
||||
|
||||
type Language = "zh" | "en";
|
||||
|
||||
export type SettingsFormState = Omit<Settings, "language"> & {
|
||||
language: Language;
|
||||
};
|
||||
|
||||
const normalizeLanguage = (lang?: string | null): Language => {
|
||||
if (!lang) return "zh";
|
||||
return lang === "en" ? "en" : "zh";
|
||||
};
|
||||
|
||||
const sanitizeDir = (value?: string | null): string | undefined => {
|
||||
if (!value) return undefined;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
};
|
||||
|
||||
export interface UseSettingsFormResult {
|
||||
settings: SettingsFormState | null;
|
||||
isLoading: boolean;
|
||||
initialLanguage: Language;
|
||||
updateSettings: (updates: Partial<SettingsFormState>) => void;
|
||||
resetSettings: (serverData: Settings | null) => void;
|
||||
readPersistedLanguage: () => Language;
|
||||
syncLanguage: (lang: Language) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* useSettingsForm - 表单状态管理
|
||||
* 负责:
|
||||
* - 表单数据状态
|
||||
* - 表单字段更新
|
||||
* - 语言同步
|
||||
* - 表单重置
|
||||
*/
|
||||
export function useSettingsForm(): UseSettingsFormResult {
|
||||
const { i18n } = useTranslation();
|
||||
const { data, isLoading } = useSettingsQuery();
|
||||
|
||||
const [settingsState, setSettingsState] = useState<SettingsFormState | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const initialLanguageRef = useRef<Language>("zh");
|
||||
|
||||
const readPersistedLanguage = useCallback((): Language => {
|
||||
if (typeof window !== "undefined") {
|
||||
const stored = window.localStorage.getItem("language");
|
||||
if (stored === "en" || stored === "zh") {
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
return normalizeLanguage(i18n.language);
|
||||
}, [i18n.language]);
|
||||
|
||||
const syncLanguage = useCallback(
|
||||
(lang: Language) => {
|
||||
const current = normalizeLanguage(i18n.language);
|
||||
if (current !== lang) {
|
||||
void i18n.changeLanguage(lang);
|
||||
}
|
||||
},
|
||||
[i18n],
|
||||
);
|
||||
|
||||
// 初始化设置数据
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
|
||||
const normalizedLanguage = normalizeLanguage(
|
||||
data.language ?? readPersistedLanguage(),
|
||||
);
|
||||
|
||||
const normalized: SettingsFormState = {
|
||||
...data,
|
||||
showInTray: data.showInTray ?? true,
|
||||
minimizeToTrayOnClose: data.minimizeToTrayOnClose ?? true,
|
||||
enableClaudePluginIntegration:
|
||||
data.enableClaudePluginIntegration ?? false,
|
||||
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
|
||||
codexConfigDir: sanitizeDir(data.codexConfigDir),
|
||||
language: normalizedLanguage,
|
||||
};
|
||||
|
||||
setSettingsState(normalized);
|
||||
initialLanguageRef.current = normalizedLanguage;
|
||||
syncLanguage(normalizedLanguage);
|
||||
}, [data, readPersistedLanguage, syncLanguage]);
|
||||
|
||||
const updateSettings = useCallback(
|
||||
(updates: Partial<SettingsFormState>) => {
|
||||
setSettingsState((prev) => {
|
||||
const base =
|
||||
prev ??
|
||||
({
|
||||
showInTray: true,
|
||||
minimizeToTrayOnClose: true,
|
||||
enableClaudePluginIntegration: false,
|
||||
language: readPersistedLanguage(),
|
||||
} as SettingsFormState);
|
||||
|
||||
const next: SettingsFormState = {
|
||||
...base,
|
||||
...updates,
|
||||
};
|
||||
|
||||
if (updates.language) {
|
||||
const normalized = normalizeLanguage(updates.language);
|
||||
next.language = normalized;
|
||||
syncLanguage(normalized);
|
||||
}
|
||||
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[readPersistedLanguage, syncLanguage],
|
||||
);
|
||||
|
||||
const resetSettings = useCallback(
|
||||
(serverData: Settings | null) => {
|
||||
if (!serverData) return;
|
||||
|
||||
const normalizedLanguage = normalizeLanguage(
|
||||
serverData.language ?? readPersistedLanguage(),
|
||||
);
|
||||
|
||||
const normalized: SettingsFormState = {
|
||||
...serverData,
|
||||
showInTray: serverData.showInTray ?? true,
|
||||
minimizeToTrayOnClose: serverData.minimizeToTrayOnClose ?? true,
|
||||
enableClaudePluginIntegration:
|
||||
serverData.enableClaudePluginIntegration ?? false,
|
||||
claudeConfigDir: sanitizeDir(serverData.claudeConfigDir),
|
||||
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
|
||||
language: normalizedLanguage,
|
||||
};
|
||||
|
||||
setSettingsState(normalized);
|
||||
syncLanguage(initialLanguageRef.current);
|
||||
},
|
||||
[readPersistedLanguage, syncLanguage],
|
||||
);
|
||||
|
||||
return {
|
||||
settings: settingsState,
|
||||
isLoading,
|
||||
initialLanguage: initialLanguageRef.current,
|
||||
updateSettings,
|
||||
resetSettings,
|
||||
readPersistedLanguage,
|
||||
syncLanguage,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user