先提交上去

This commit is contained in:
Fox_block
2026-03-16 20:49:46 +08:00
parent 86abcb5602
commit 192b48cab0
303 changed files with 30019 additions and 5846 deletions
+12
View File
@@ -0,0 +1,12 @@
import { createContext, useContext } from "react"
import { ClientContext } from "../ClientContext"
const ServiceContext = createContext<ClientContext | null>(null)
export const ServiceProvider = ServiceContext.Provider
export const useService = () => {
const ctx = useContext(ServiceContext)
if (!ctx) throw new Error("No ServiceProvider")
return ctx
}
+168
View File
@@ -0,0 +1,168 @@
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from "react"
import { themeConfig } from "../preload/types"
import { generateColorMap } from "../utils/color"
interface themeContextType {
currentTheme: themeConfig | null
setTheme: (id: string) => Promise<void>
themes: themeConfig[]
applyTheme: (theme: themeConfig) => void
}
const ThemeContext = createContext<themeContextType | undefined>(undefined)
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [currentTheme, setCurrentTheme] = useState<themeConfig | null>(null)
const [themes, setThemes] = useState<themeConfig[]>([])
const appliedStyleKeysRef = useRef<string[]>([])
const currentThemeRef = useRef<themeConfig | null>(null)
const applyThemeConfig = useCallback((theme: themeConfig) => {
const { tdesign, custom } = theme.config
const root = document.documentElement
const prevKeys = appliedStyleKeysRef.current
for (const k of prevKeys) root.style.removeProperty(k)
const nextKeys: string[] = []
root.setAttribute("theme-mode", theme.mode)
const brandColor = tdesign.brandColor || "#1677FF"
const hex = brandColor.replace("#", "")
const r = parseInt(hex.substring(0, 2), 16)
const g = parseInt(hex.substring(2, 4), 16)
const b = parseInt(hex.substring(4, 6), 16)
root.style.setProperty("--ss-primary-color", brandColor)
root.style.setProperty("--ss-primary-rgb", `${r}, ${g}, ${b}`)
nextKeys.push("--ss-primary-color")
nextKeys.push("--ss-primary-rgb")
if (brandColor) {
const colorMap = generateColorMap(brandColor, theme.mode)
Object.entries(colorMap).forEach(([key, value]) => {
root.style.setProperty(key, value)
nextKeys.push(key)
})
root.style.setProperty("--ant-color-primary", brandColor)
nextKeys.push("--ant-color-primary")
root.style.setProperty("--ant-color-primary-hover", `rgba(${r}, ${g}, ${b}, 0.85)`)
root.style.setProperty("--ant-color-primary-active", `rgba(${r}, ${g}, ${b}, 0.7)`)
root.style.setProperty("--ant-color-primary-bg", `rgba(${r}, ${g}, ${b}, 0.1)`)
root.style.setProperty("--ant-color-primary-bg-hover", `rgba(${r}, ${g}, ${b}, 0.2)`)
root.style.setProperty("--ant-color-primary-border", `rgba(${r}, ${g}, ${b}, 0.3)`)
nextKeys.push("--ant-color-primary-hover")
nextKeys.push("--ant-color-primary-active")
nextKeys.push("--ant-color-primary-bg")
nextKeys.push("--ant-color-primary-bg-hover")
nextKeys.push("--ant-color-primary-border")
}
if (tdesign.warningColor) {
root.style.setProperty("--ant-color-warning", tdesign.warningColor)
nextKeys.push("--ant-color-warning")
}
if (tdesign.errorColor) {
root.style.setProperty("--ant-color-error", tdesign.errorColor)
nextKeys.push("--ant-color-error")
}
if (tdesign.successColor) {
root.style.setProperty("--ant-color-success", tdesign.successColor)
nextKeys.push("--ant-color-success")
}
Object.entries(custom).forEach(([key, value]) => {
root.style.setProperty(key, String(value))
nextKeys.push(key)
})
const bgLight = custom["--ss-bg-color-light"]
const bgDark = custom["--ss-bg-color-dark"]
if (bgLight || bgDark) {
const resolved = theme.mode === "dark" ? bgDark || bgLight : bgLight || bgDark
if (resolved) {
root.style.setProperty("--ss-bg-color", resolved)
nextKeys.push("--ss-bg-color")
}
}
if (!custom["--ss-sidebar-active-bg"]) {
const alpha = theme.mode === "dark" ? 0.22 : 0.12
root.style.setProperty("--ss-sidebar-active-bg", `rgba(${r}, ${g}, ${b}, ${alpha})`)
nextKeys.push("--ss-sidebar-active-bg")
}
if (!custom["--ss-sidebar-active-text"]) {
root.style.setProperty("--ss-sidebar-active-text", brandColor)
nextKeys.push("--ss-sidebar-active-text")
}
appliedStyleKeysRef.current = nextKeys
}, [])
const loadThemes = useCallback(async () => {
if (!(window as any).api) return
const res = await (window as any).api.getThemes()
if (res.success && res.data) {
setThemes(res.data)
}
}, [])
const loadCurrentTheme = useCallback(async () => {
if (!(window as any).api) return
const res = await (window as any).api.getCurrentTheme()
if (res.success && res.data) {
setCurrentTheme(res.data)
currentThemeRef.current = res.data
applyThemeConfig(res.data)
}
}, [applyThemeConfig])
useEffect(() => {
if (!(window as any).api) return
;(async () => {
await loadThemes()
await loadCurrentTheme()
})()
const unsubscribe = (window as any).api.onThemeChanged((theme: themeConfig) => {
setCurrentTheme(theme)
currentThemeRef.current = theme
applyThemeConfig(theme)
loadThemes()
})
return () => {
if (typeof unsubscribe === "function") {
unsubscribe()
}
}
}, [applyThemeConfig, loadCurrentTheme, loadThemes])
const setTheme = async (id: string) => {
const res = await (window as any).api.setTheme(id)
if (res.success) {
await loadCurrentTheme()
}
}
const applyTheme = useCallback(
(theme: themeConfig) => {
applyThemeConfig(theme)
},
[applyThemeConfig]
)
return (
<ThemeContext.Provider value={{ currentTheme, setTheme, themes, applyTheme }}>
{children}
</ThemeContext.Provider>
)
}
export const useTheme = () => {
const context = useContext(ThemeContext)
if (!context) throw new Error("useTheme must be used within ThemeProvider")
return context
}
+89
View File
@@ -0,0 +1,89 @@
import React, { createContext, useContext, useState, useCallback } from "react"
import { themeConfig } from "../preload/types"
import { useTheme } from "./ThemeContext"
interface ThemeEditorContextType {
isEditing: boolean
editingTheme: themeConfig | null
startEditing: (theme?: themeConfig) => void
updateEditingTheme: (updates: Partial<themeConfig>) => void
updateConfig: (type: "tdesign" | "custom", key: string, value: string) => void
saveEditingTheme: () => Promise<void>
cancelEditing: () => void
}
const ThemeEditorContext = createContext<ThemeEditorContextType | undefined>(undefined)
export const ThemeEditorProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { currentTheme } = useTheme()
const [isEditing, setIsEditing] = useState(false)
const [editingTheme, setEditingTheme] = useState<themeConfig | null>(null)
const startEditing = useCallback(
(theme?: themeConfig) => {
if (theme) {
setEditingTheme(JSON.parse(JSON.stringify(theme)))
} else if (currentTheme) {
const newTheme: themeConfig = JSON.parse(JSON.stringify(currentTheme))
newTheme.id = `custom-${Date.now()}`
newTheme.name = "新自定义主题"
setEditingTheme(newTheme)
}
setIsEditing(true)
},
[currentTheme]
)
const updateEditingTheme = useCallback((updates: Partial<themeConfig>) => {
setEditingTheme((prev: themeConfig | null) => (prev ? { ...prev, ...updates } : null))
}, [])
const updateConfig = useCallback((type: "tdesign" | "custom", key: string, value: string) => {
setEditingTheme((prev: themeConfig | null) => {
if (!prev) return null
const next = { ...prev }
next.config = { ...next.config }
next.config[type] = { ...next.config[type], [key]: value }
return next
})
}, [])
const saveEditingTheme = useCallback(async () => {
if (!editingTheme) return
const res = await (window as any).api.saveTheme(editingTheme)
if (res.success) {
await (window as any).api.setTheme(editingTheme.id)
setIsEditing(false)
setEditingTheme(null)
} else {
console.error("Failed to save theme:", res.message)
}
}, [editingTheme])
const cancelEditing = useCallback(() => {
setIsEditing(false)
setEditingTheme(null)
}, [])
return (
<ThemeEditorContext.Provider
value={{
isEditing,
editingTheme,
startEditing,
updateEditingTheme,
updateConfig,
saveEditingTheme,
cancelEditing,
}}
>
{children}
</ThemeEditorContext.Provider>
)
}
export const useThemeEditor = () => {
const context = useContext(ThemeEditorContext)
if (!context) throw new Error("useThemeEditor must be used within ThemeEditorProvider")
return context
}