Files
cc-switch/src/hooks/useSettings.ts
Jason 57552b3159 fix: unify dialog layout and fix content padding issues
- Fix negative margin overflow in all dialog content areas
- Standardize dialog structure with flex-col layout
- Add consistent py-4 spacing to all content areas
- Ensure proper spacing between header, content, and footer

Affected components:
- AddProviderDialog, EditProviderDialog
- McpFormModal, McpPanel
- UsageScriptModal
- SettingsDialog

All dialogs now follow unified layout pattern:
- DialogContent: flex flex-col max-h-[90vh]
- Content area: flex-1 overflow-y-auto px-6 py-4
- No negative margins that cause content overflow
2025-10-18 16:52:02 +08:00

206 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,
};
}