Files
cc-switch/src/hooks/useSettings.ts
Jason 5ebe23abc8 refactor: remove config file location display feature
Remove the config file location display from settings dialog to simplify
the user interface. Users who need to access the config file can still do
so through the advanced settings section.

Changes:
- Removed ConfigPathDisplay component and its usage
- Removed configPath and openConfigFolder from useSettings hook
- Removed configPath and openConfigFolder from useSettingsMetadata hook
- Removed related i18n keys: configFileLocation, openFolder
- Updated settings dialog to remove the config path display section

This simplifies the settings UI while maintaining access to config
directory management through the advanced settings tab.
2025-10-17 23:46:44 +08:00

209 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { settingsApi, type AppType } from "@/lib/api";
import { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query";
import type { Settings } from "@/types";
import {
useSettingsForm,
type SettingsFormState,
} from "./useSettingsForm";
import {
useDirectorySettings,
type ResolvedDirectories,
} from "./useDirectorySettings";
import { useSettingsMetadata } from "./useSettingsMetadata";
type Language = "zh" | "en";
interface SaveResult {
requiresRestart: boolean;
}
export interface UseSettingsResult {
settings: SettingsFormState | null;
isLoading: boolean;
isSaving: boolean;
isPortable: boolean;
appConfigDir?: string;
resolvedDirs: ResolvedDirectories;
requiresRestart: boolean;
updateSettings: (updates: Partial<SettingsFormState>) => void;
updateDirectory: (app: AppType, value?: string) => void;
updateAppConfigDir: (value?: string) => void;
browseDirectory: (app: AppType) => Promise<void>;
browseAppConfigDir: () => Promise<void>;
resetDirectory: (app: AppType) => Promise<void>;
resetAppConfigDir: () => Promise<void>;
saveSettings: () => Promise<SaveResult | null>;
resetSettings: () => void;
acknowledgeRestart: () => void;
}
export type { SettingsFormState, ResolvedDirectories };
const sanitizeDir = (value?: string | null): string | undefined => {
if (!value) return undefined;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
};
/**
* useSettings - 组合层
* 负责:
* - 组合 useSettingsForm、useDirectorySettings、useSettingsMetadata
* - 保存设置逻辑
* - 重置设置逻辑
*/
export function useSettings(): UseSettingsResult {
const { t } = useTranslation();
const { data } = useSettingsQuery();
const saveMutation = useSaveSettingsMutation();
// 1⃣ 表单状态管理
const {
settings,
isLoading: isFormLoading,
initialLanguage,
updateSettings,
resetSettings: resetForm,
syncLanguage,
} = useSettingsForm();
// 2⃣ 目录管理
const {
appConfigDir,
resolvedDirs,
isLoading: isDirectoryLoading,
initialAppConfigDir,
updateDirectory,
updateAppConfigDir,
browseDirectory,
browseAppConfigDir,
resetDirectory,
resetAppConfigDir,
resetAllDirectories,
} = useDirectorySettings({
settings,
onUpdateSettings: updateSettings,
});
// 3⃣ 元数据管理
const {
isPortable,
requiresRestart,
isLoading: isMetadataLoading,
acknowledgeRestart,
setRequiresRestart,
} = useSettingsMetadata();
// 重置设置
const resetSettings = useCallback(() => {
resetForm(data ?? null);
syncLanguage(initialLanguage);
resetAllDirectories(
sanitizeDir(data?.claudeConfigDir),
sanitizeDir(data?.codexConfigDir),
);
setRequiresRestart(false);
}, [
data,
initialLanguage,
resetForm,
syncLanguage,
resetAllDirectories,
setRequiresRestart,
]);
// 保存设置
const saveSettings = useCallback(async (): Promise<SaveResult | null> => {
if (!settings) return null;
try {
const sanitizedAppDir = sanitizeDir(appConfigDir);
const sanitizedClaudeDir = sanitizeDir(settings.claudeConfigDir);
const sanitizedCodexDir = sanitizeDir(settings.codexConfigDir);
const previousAppDir = initialAppConfigDir;
const payload: Settings = {
...settings,
claudeConfigDir: sanitizedClaudeDir,
codexConfigDir: sanitizedCodexDir,
language: settings.language,
};
await saveMutation.mutateAsync(payload);
await settingsApi.setAppConfigDirOverride(sanitizedAppDir ?? null);
try {
if (payload.enableClaudePluginIntegration) {
await settingsApi.applyClaudePluginConfig({ official: false });
} else {
await settingsApi.applyClaudePluginConfig({ official: true });
}
} catch (error) {
console.warn(
"[useSettings] Failed to sync Claude plugin config",
error,
);
toast.error(
t("notifications.syncClaudePluginFailed", {
defaultValue: "同步 Claude 插件失败",
}),
);
}
try {
if (typeof window !== "undefined") {
window.localStorage.setItem("language", payload.language as Language);
}
} catch (error) {
console.warn(
"[useSettings] Failed to persist language preference",
error,
);
}
const appDirChanged = sanitizedAppDir !== (previousAppDir ?? undefined);
setRequiresRestart(appDirChanged);
return { requiresRestart: appDirChanged };
} catch (error) {
console.error("[useSettings] Failed to save settings", error);
throw error;
}
}, [
appConfigDir,
initialAppConfigDir,
saveMutation,
settings,
setRequiresRestart,
t,
]);
const isLoading = useMemo(
() => isFormLoading || isDirectoryLoading || isMetadataLoading,
[isFormLoading, isDirectoryLoading, isMetadataLoading],
);
return {
settings,
isLoading,
isSaving: saveMutation.isPending,
isPortable,
appConfigDir,
resolvedDirs,
requiresRestart,
updateSettings,
updateDirectory,
updateAppConfigDir,
browseDirectory,
browseAppConfigDir,
resetDirectory,
resetAppConfigDir,
saveSettings,
resetSettings,
acknowledgeRestart,
};
}