import React, { useEffect, useMemo, useState } from "react" import { Button, ColorPicker, Input, Segmented, Space, Typography, message } from "antd" import type { Color } from "antd/es/color-picker" import { useTranslation } from "react-i18next" import { useTheme } from "../contexts/ThemeContext" import type { themeConfig } from "../preload/types" type props = { compact?: boolean } const presetPrimaryColors = [ "#1677FF", "#2F54EB", "#722ED1", "#EB2F96", "#F5222D", "#FA8C16", "#FADB14", "#52C41A", "#13C2C2", ] const presetGradients: { id: string labelKey: string light: string dark: string }[] = [ { id: "blue", labelKey: "blue", light: "linear-gradient(180deg, #f7fbff 0%, #f1f7ff 55%, #f8f9fc 100%)", dark: "linear-gradient(180deg, #0f1220 0%, #101524 55%, #0b0d16 100%)", }, { id: "pink", labelKey: "pink", light: "linear-gradient(180deg, #fff7f1 0%, #fff1f1 55%, #f7f7fb 100%)", dark: "linear-gradient(180deg, #1a0f14 0%, #1d1218 55%, #120c10 100%)", }, { id: "cyan", labelKey: "cyan", light: "linear-gradient(180deg, #f0f9ff 0%, #e0f2fe 55%, #f0f9ff 100%)", dark: "linear-gradient(180deg, #050b10 0%, #06121a 55%, #05070a 100%)", }, { id: "purple", labelKey: "purple", light: "linear-gradient(180deg, #faf5ff 0%, #f3e8ff 55%, #faf5ff 100%)", dark: "linear-gradient(180deg, #0f0a14 0%, #151020 55%, #0d0910 100%)", }, ] const deepClone = (v: T): T => JSON.parse(JSON.stringify(v)) as T const normalizeHex = (value: string): string | null => { const s = String(value || "").trim() if (!s) return null const m = s.match(/^#?([0-9a-fA-F]{6})$/) if (!m) return null return `#${m[1].toUpperCase()}` } const buildGradient = (a: string, b: string, dir: "v" | "h" | "d"): string => { const angle = dir === "h" ? "90deg" : dir === "d" ? "135deg" : "180deg" return `linear-gradient(${angle}, ${a} 0%, ${b} 100%)` } export const ThemeQuickSettings: React.FC = ({ compact }) => { const { t } = useTranslation() const { currentTheme, setTheme, themes, applyTheme } = useTheme() const [messageApi, holder] = message.useMessage() const [workingTheme, setWorkingTheme] = useState(null) const [saving, setSaving] = useState(false) const [primaryInput, setPrimaryInput] = useState("") const primaryColor = useMemo(() => { const fromTheme = workingTheme?.config?.tdesign?.brandColor return normalizeHex(primaryInput) || normalizeHex(fromTheme || "") || "#1677FF" }, [primaryInput, workingTheme]) const [gradientDir, setGradientDir] = useState<"v" | "h" | "d">("v") const [g1, setG1] = useState("#f7fbff") const [g2, setG2] = useState("#f8f9fc") const currentBg = workingTheme?.config?.custom?.["--ss-bg-color"] || "" useEffect(() => { if (!currentTheme) return const base = deepClone(currentTheme) const editable = base.id === "custom-default" || base.id.startsWith("custom-") ? base : { ...base, id: "custom-default", name: t("theme.myTheme") } setWorkingTheme(editable) setPrimaryInput(editable.config?.tdesign?.brandColor || "") }, [currentTheme, t]) // Real-time theme preview useEffect(() => { if (!workingTheme) return applyTheme(workingTheme) }, [workingTheme, applyTheme]) const ensureCustomThemeSelected = async (theme: themeConfig): Promise => { if (!(window as any).api) return false const exists = themes.some((t) => t.id === theme.id) if (!exists) { const res = await (window as any).api.saveTheme(theme) if (!res?.success) return false } if (currentTheme?.id !== theme.id) { await setTheme(theme.id) } return true } const saveThemeToDb = async (theme: themeConfig) => { if (!(window as any).api) return false try { const exists = themes.some((t) => t.id === theme.id) if (!exists) { const res = await (window as any).api.saveTheme(theme) if (!res?.success) return false } if (currentTheme?.id !== theme.id) { await setTheme(theme.id) } const res = await (window as any).api.saveTheme(theme) return res?.success } catch { return false } } const save = async () => { if (!(window as any).api) return if (!workingTheme) return setSaving(true) try { const ok = await ensureCustomThemeSelected(workingTheme) if (!ok) throw new Error("保存失败") const res = await (window as any).api.saveTheme(workingTheme) if (!res?.success) throw new Error(res?.message || "保存失败") messageApi.success(t("theme.saved")) } catch (e: any) { messageApi.error(e?.message || t("theme.saveFailed")) } finally { setSaving(false) } } const setPrimary = async (hex: string) => { setPrimaryInput(hex) setWorkingTheme((prev: themeConfig | null) => { if (!prev) return prev const next = deepClone(prev) next.config = next.config || ({} as any) next.config.tdesign = { ...(next.config.tdesign || {}), brandColor: hex } saveThemeToDb(next) return next }) } const setGradientPreset = async (value: string) => { setWorkingTheme((prev) => { if (!prev) return prev const next = deepClone(prev) next.config = next.config || ({} as any) next.config.custom = { ...(next.config.custom || {}), "--ss-bg-color": value } saveThemeToDb(next) return next }) } const setGradientFromPickers = async () => { const g = buildGradient(g1, g2, gradientDir) setWorkingTheme((prev: themeConfig | null) => { if (!prev) return prev const next = deepClone(prev) next.config = next.config || ({} as any) next.config.custom = { ...(next.config.custom || {}), "--ss-bg-color": g } saveThemeToDb(next) return next }) } const setMode = async (mode: "light" | "dark") => { const targetThemeId = mode === "light" ? "light-default" : "dark-default" await setTheme(targetThemeId) } if (!workingTheme) return null return (
{holder}
{t("theme.colorAndBackground")}
{t("theme.mode")} setMode(v as any)} options={[ { label: t("settings.lightMode"), value: "light" }, { label: t("settings.darkMode"), value: "dark" }, ]} />
{t("settings.primaryColor")}
setPrimaryInput(e.target.value)} onBlur={() => { const n = normalizeHex(primaryInput) if (n) setPrimary(n) else setPrimary(primaryColor) }} style={{ width: 120 }} /> {presetPrimaryColors.map((c) => (
{t("settings.backgroundGradient")}
{presetGradients.map((g) => { const gradientValue = g[workingTheme.mode] const active = currentBg === gradientValue return ( ) })}
setGradientDir(v as any)} options={[ { label: t("theme.gradientDirections.vertical"), value: "v" }, { label: t("theme.gradientDirections.horizontal"), value: "h" }, { label: t("theme.gradientDirections.diagonal"), value: "d" }, ]} /> setG1(c.toHexString())} /> setG2(c.toHexString())} />
) }