mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
先提交上去
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user