Files
cc-switch/src/components/SettingsModal.tsx
Jason b8d2daccde - fix(linux): disable modal backdrop blur on Linux (WebKitGTK/Wayland) to prevent freeze when opening Add Provider panel
- refactor(ui): add runtime platform detector and conditionally apply blur only on non-Linux platforms
- chore: verify typecheck and renderer build succeed; no functional regression expected
2025-09-19 16:17:14 +08:00

327 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from "react";
import {
X,
RefreshCw,
FolderOpen,
Download,
ExternalLink,
Check,
} from "lucide-react";
import { getVersion } from "@tauri-apps/api/app";
import "../lib/tauri-api";
import { relaunchApp } from "../lib/updater";
import { useUpdate } from "../contexts/UpdateContext";
import type { Settings } from "../types";
import { isLinux } from "../lib/platform";
interface SettingsModalProps {
onClose: () => void;
}
export default function SettingsModal({ onClose }: SettingsModalProps) {
const [settings, setSettings] = useState<Settings>({
showInTray: true,
});
const [configPath, setConfigPath] = useState<string>("");
const [version, setVersion] = useState<string>("");
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [showUpToDate, setShowUpToDate] = useState(false);
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } =
useUpdate();
useEffect(() => {
loadSettings();
loadConfigPath();
loadVersion();
}, []);
const loadVersion = async () => {
try {
const appVersion = await getVersion();
setVersion(appVersion);
} catch (error) {
console.error("获取版本信息失败:", error);
// 失败时不硬编码版本号,显示为未知
setVersion("未知");
}
};
const loadSettings = async () => {
try {
const loadedSettings = await window.api.getSettings();
if ((loadedSettings as any)?.showInTray !== undefined) {
setSettings({ showInTray: (loadedSettings as any).showInTray });
} else if ((loadedSettings as any)?.showInDock !== undefined) {
// 向后兼容:若历史上有 showInDock则映射为 showInTray
setSettings({ showInTray: (loadedSettings as any).showInDock });
}
} catch (error) {
console.error("加载设置失败:", error);
}
};
const loadConfigPath = async () => {
try {
const path = await window.api.getAppConfigPath();
if (path) {
setConfigPath(path);
}
} catch (error) {
console.error("获取配置路径失败:", error);
}
};
const saveSettings = async () => {
try {
await window.api.saveSettings(settings);
onClose();
} catch (error) {
console.error("保存设置失败:", error);
}
};
const handleCheckUpdate = async () => {
if (hasUpdate && updateHandle) {
// 已检测到更新:直接复用 updateHandle 下载并安装,避免重复检查
setIsDownloading(true);
try {
resetDismiss();
await updateHandle.downloadAndInstall();
await relaunchApp();
} catch (error) {
console.error("更新失败:", error);
// 更新失败时回退到打开 Releases 页面
await window.api.checkForUpdates();
} finally {
setIsDownloading(false);
}
} else {
// 尚未检测到更新:先检查
setIsCheckingUpdate(true);
setShowUpToDate(false);
try {
const hasNewUpdate = await checkUpdate();
// 检查完成后,如果没有更新,显示"已是最新"
if (!hasNewUpdate) {
setShowUpToDate(true);
// 3秒后恢复按钮文字
setTimeout(() => {
setShowUpToDate(false);
}, 3000);
}
} catch (error) {
console.error("检查更新失败:", error);
// 在开发模式下,模拟已是最新版本的响应
if (import.meta.env.DEV) {
setShowUpToDate(true);
setTimeout(() => {
setShowUpToDate(false);
}, 3000);
} else {
// 生产环境下如果更新插件不可用,回退到打开 Releases 页面
await window.api.checkForUpdates();
}
} finally {
setIsCheckingUpdate(false);
}
}
};
const handleOpenConfigFolder = async () => {
try {
await window.api.openAppConfigFolder();
} catch (error) {
console.error("打开配置文件夹失败:", error);
}
};
const handleOpenReleaseNotes = async () => {
try {
const targetVersion = updateInfo?.availableVersion || version;
// 如果未知或为空,回退到 releases 首页
if (!targetVersion || targetVersion === "未知") {
await window.api.openExternal(
"https://github.com/farion1231/cc-switch/releases",
);
return;
}
const tag = targetVersion.startsWith("v")
? targetVersion
: `v${targetVersion}`;
await window.api.openExternal(
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`,
);
} catch (error) {
console.error("打开更新日志失败:", error);
}
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
onMouseDown={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div
className={`absolute inset-0 bg-black/50 dark:bg-black/70${
isLinux() ? "" : " backdrop-blur-sm"
}`}
/>
<div className="relative bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-[500px] overflow-hidden">
{/* 标题栏 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
<h2 className="text-lg font-semibold text-blue-500 dark:text-blue-400">
</h2>
<button
onClick={onClose}
className="p-1.5 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
>
<X size={20} className="text-gray-500 dark:text-gray-400" />
</button>
</div>
{/* 设置内容 */}
<div className="px-6 py-4 space-y-6">
{/* 系统托盘设置(未实现)
说明:此开关用于控制是否在系统托盘/菜单栏显示应用图标。 */}
{/* <div>
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
显示设置(系统托盘)
</h3>
<label className="flex items-center justify-between">
<span className="text-sm text-gray-500">
在菜单栏显示图标(系统托盘)
</span>
<input
type="checkbox"
checked={settings.showInTray}
onChange={(e) =>
setSettings({ ...settings, showInTray: e.target.checked })
}
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20"
/>
</label>
</div> */}
{/* VS Code 自动同步设置已移除 */}
{/* 配置文件位置 */}
<div>
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
</h3>
<div className="flex items-center gap-2">
<div className="flex-1 px-3 py-2 bg-gray-100 dark:bg-gray-800 rounded-lg">
<span className="text-xs font-mono text-gray-500 dark:text-gray-400">
{configPath || "加载中..."}
</span>
</div>
<button
onClick={handleOpenConfigFolder}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
title="打开文件夹"
>
<FolderOpen
size={18}
className="text-gray-500 dark:text-gray-400"
/>
</button>
</div>
</div>
{/* 关于 */}
<div>
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
</h3>
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
<div className="flex items-start justify-between">
<div>
<div className="text-sm">
<p className="font-medium text-gray-900 dark:text-gray-100">
CC Switch
</p>
<p className="mt-1 text-gray-500 dark:text-gray-400">
{version}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleOpenReleaseNotes}
className="px-2 py-1 text-xs font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 rounded-lg hover:bg-blue-500/10 transition-colors"
title={
hasUpdate ? "查看该版本更新日志" : "查看当前版本更新日志"
}
>
<span className="inline-flex items-center gap-1">
<ExternalLink size={12} />
</span>
</button>
<button
onClick={handleCheckUpdate}
disabled={isCheckingUpdate || isDownloading}
className={`min-w-[88px] px-3 py-1.5 text-xs font-medium rounded-lg transition-all ${
isCheckingUpdate || isDownloading
? "bg-gray-100 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed border border-transparent"
: hasUpdate
? "bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white border border-transparent"
: showUpToDate
? "bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border border-green-200 dark:border-green-800"
: "bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-blue-500 dark:text-blue-400 border border-gray-200 dark:border-gray-600"
}`}
>
{isDownloading ? (
<span className="flex items-center gap-1">
<Download size={12} className="animate-pulse" />
...
</span>
) : isCheckingUpdate ? (
<span className="flex items-center gap-1">
<RefreshCw size={12} className="animate-spin" />
...
</span>
) : hasUpdate ? (
<span className="flex items-center gap-1">
<Download size={12} />
v{updateInfo?.availableVersion}
</span>
) : showUpToDate ? (
<span className="flex items-center gap-1">
<Check size={12} />
</span>
) : (
"检查更新"
)}
</button>
</div>
</div>
</div>
</div>
</div>
{/* 底部按钮 */}
<div className="flex justify-end gap-3 px-6 py-4 border-t border-gray-200 dark:border-gray-800">
<button
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
>
</button>
<button
onClick={saveSettings}
className="px-4 py-2 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 rounded-lg transition-colors"
>
</button>
</div>
</div>
</div>
);
}