mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-02 00:58:07 +08:00
* feat(window): add app-level window controls with settings toggle Add a persistent settings toggle to enable app-level minimize/maximize/close controls and hide system decorations when enabled, providing a Wayland-friendly fallback for broken native titlebar interactions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(window): restrict app-level window controls to Linux only and fix startup flicker - Guard useAppWindowControls with isLinux() in App.tsx so it's always false on macOS/Windows even if persisted as true - Wrap set_decorations call in lib.rs with #[cfg(target_os = "linux")] - Only show the toggle in WindowSettings on Linux - Skip setDecorations effect while settingsData is still loading to prevent the Rust-side decoration state from being overridden by the undefined->false fallback, which caused a brief title bar flicker --------- Co-authored-by: wzk <wx13571681304@outlook.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Jason <farion1231@gmail.com>
172 lines
5.2 KiB
TypeScript
172 lines
5.2 KiB
TypeScript
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" | "ja";
|
|
|
|
export type SettingsFormState = Omit<Settings, "language"> & {
|
|
language: Language;
|
|
};
|
|
|
|
const normalizeLanguage = (lang?: string | null): Language => {
|
|
if (!lang) return "zh";
|
|
const normalized = lang.toLowerCase();
|
|
return normalized === "en" || normalized === "ja" ? normalized : "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" || stored === "ja") {
|
|
return stored as Language;
|
|
}
|
|
}
|
|
return normalizeLanguage(i18n.language);
|
|
}, [i18n]);
|
|
|
|
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,
|
|
useAppWindowControls: data.useAppWindowControls ?? false,
|
|
enableClaudePluginIntegration:
|
|
data.enableClaudePluginIntegration ?? false,
|
|
silentStartup: data.silentStartup ?? false,
|
|
skipClaudeOnboarding: data.skipClaudeOnboarding ?? false,
|
|
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
|
|
codexConfigDir: sanitizeDir(data.codexConfigDir),
|
|
geminiConfigDir: sanitizeDir(data.geminiConfigDir),
|
|
opencodeConfigDir: sanitizeDir(data.opencodeConfigDir),
|
|
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,
|
|
useAppWindowControls: false,
|
|
enableClaudePluginIntegration: false,
|
|
skipClaudeOnboarding: 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,
|
|
useAppWindowControls: serverData.useAppWindowControls ?? false,
|
|
enableClaudePluginIntegration:
|
|
serverData.enableClaudePluginIntegration ?? false,
|
|
silentStartup: serverData.silentStartup ?? false,
|
|
skipClaudeOnboarding: serverData.skipClaudeOnboarding ?? false,
|
|
claudeConfigDir: sanitizeDir(serverData.claudeConfigDir),
|
|
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
|
|
geminiConfigDir: sanitizeDir(serverData.geminiConfigDir),
|
|
opencodeConfigDir: sanitizeDir(serverData.opencodeConfigDir),
|
|
language: normalizedLanguage,
|
|
};
|
|
|
|
setSettingsState(normalized);
|
|
syncLanguage(initialLanguageRef.current);
|
|
},
|
|
[readPersistedLanguage, syncLanguage],
|
|
);
|
|
|
|
return {
|
|
settings: settingsState,
|
|
isLoading,
|
|
initialLanguage: initialLanguageRef.current,
|
|
updateSettings,
|
|
resetSettings,
|
|
readPersistedLanguage,
|
|
syncLanguage,
|
|
};
|
|
}
|