mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 03:32:17 +08:00
use custom theme
This commit is contained in:
parent
aae16c3c71
commit
ddfa4e75f3
|
@ -8,7 +8,8 @@ import {
|
|||
overrideConfigPath,
|
||||
overrideDir,
|
||||
profileConfigPath,
|
||||
profilesDir
|
||||
profilesDir,
|
||||
themesDir
|
||||
} from '../utils/dirs'
|
||||
|
||||
export async function webdavBackup(): Promise<boolean> {
|
||||
|
@ -21,6 +22,7 @@ export async function webdavBackup(): Promise<boolean> {
|
|||
zip.addLocalFile(controledMihomoConfigPath())
|
||||
zip.addLocalFile(profileConfigPath())
|
||||
zip.addLocalFile(overrideConfigPath())
|
||||
zip.addLocalFolder(themesDir(), 'themes')
|
||||
zip.addLocalFolder(profilesDir(), 'profiles')
|
||||
zip.addLocalFolder(overrideDir(), 'override')
|
||||
zip.addLocalFolder(overrideDir(), 'substore')
|
||||
|
|
56
src/main/resolve/theme.ts
Normal file
56
src/main/resolve/theme.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { readdir, readFile } from 'fs/promises'
|
||||
import { themesDir } from '../utils/dirs'
|
||||
import path from 'path'
|
||||
import axios from 'axios'
|
||||
import AdmZip from 'adm-zip'
|
||||
import { getControledMihomoConfig } from '../config'
|
||||
import { existsSync } from 'fs'
|
||||
import { mainWindow } from '..'
|
||||
|
||||
let insertedCSSKey: string | undefined = undefined
|
||||
|
||||
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
|
||||
const files = await readdir(themesDir())
|
||||
const themes = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const css = (await readFile(path.join(themesDir(), file), 'utf-8')) || ''
|
||||
let name = file
|
||||
if (css.startsWith('/*')) {
|
||||
name = css.split('\n')[0].replace('/*', '').replace('*/', '').trim() || file
|
||||
}
|
||||
return { key: file, label: name }
|
||||
})
|
||||
)
|
||||
return [{ key: 'default.css', label: '默认' }, ...themes]
|
||||
}
|
||||
|
||||
export async function fetchThemes(): Promise<void> {
|
||||
const zipUrl = 'https://github.com/mihomo-party-org/theme-hub/releases/download/latest/themes.zip'
|
||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||
const zipData = await axios.get(zipUrl, {
|
||||
responseType: 'arraybuffer',
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
proxy: {
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: mixedPort
|
||||
}
|
||||
})
|
||||
const zip = new AdmZip(zipData.data as Buffer)
|
||||
zip.extractAllTo(themesDir(), true)
|
||||
}
|
||||
|
||||
export async function applyTheme(theme: string): Promise<void> {
|
||||
if (theme === 'default.css') {
|
||||
if (insertedCSSKey) {
|
||||
await mainWindow?.webContents.removeInsertedCSS(insertedCSSKey)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!existsSync(path.join(themesDir(), theme))) return
|
||||
const css = await readFile(path.join(themesDir(), theme), 'utf-8')
|
||||
if (insertedCSSKey) {
|
||||
await mainWindow?.webContents.removeInsertedCSS(insertedCSSKey)
|
||||
}
|
||||
insertedCSSKey = await mainWindow?.webContents.insertCSS(css)
|
||||
}
|
|
@ -53,6 +53,10 @@ export function resourcesFilesDir(): string {
|
|||
return path.join(resourcesDir(), 'files')
|
||||
}
|
||||
|
||||
export function themesDir(): string {
|
||||
return path.join(dataDir(), 'themes')
|
||||
}
|
||||
|
||||
export function mihomoCoreDir(): string {
|
||||
return path.join(resourcesDir(), 'sidecar')
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
profilePath,
|
||||
profilesDir,
|
||||
resourcesFilesDir,
|
||||
subStoreDir
|
||||
subStoreDir,
|
||||
themesDir
|
||||
} from './dirs'
|
||||
import {
|
||||
defaultConfig,
|
||||
|
@ -38,6 +39,9 @@ async function initDirs(): Promise<void> {
|
|||
if (!existsSync(dataDir())) {
|
||||
await mkdir(dataDir())
|
||||
}
|
||||
if (!existsSync(themesDir())) {
|
||||
await mkdir(themesDir())
|
||||
}
|
||||
if (!existsSync(profilesDir())) {
|
||||
await mkdir(profilesDir())
|
||||
}
|
||||
|
|
|
@ -65,13 +65,12 @@ import { getInterfaces } from '../sys/interface'
|
|||
import { copyEnv } from '../resolve/tray'
|
||||
import { registerShortcut } from '../resolve/shortcut'
|
||||
import { mainWindow } from '..'
|
||||
import { applyTheme, fetchThemes, resolveThemes } from '../resolve/theme'
|
||||
import { subStoreCollections, subStoreSubs } from '../core/subStoreApi'
|
||||
import { logDir } from './dirs'
|
||||
import path from 'path'
|
||||
import v8 from 'v8'
|
||||
|
||||
let insertedCSSKey: string | undefined
|
||||
|
||||
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): (...args: any[]) => Promise<T | { invokeError: unknown }> {
|
||||
|
@ -203,14 +202,9 @@ export function registerIpcMainHandlers(): void {
|
|||
ipcMain.handle('createHeapSnapshot', () => {
|
||||
v8.writeHeapSnapshot(path.join(logDir(), `${Date.now()}.heapsnapshot`))
|
||||
})
|
||||
ipcMain.handle('insertCSS', (_e, css) =>
|
||||
ipcErrorWrapper(async (css) => {
|
||||
if (insertedCSSKey) {
|
||||
await mainWindow?.webContents.removeInsertedCSS(insertedCSSKey)
|
||||
}
|
||||
insertedCSSKey = await mainWindow?.webContents.insertCSS(css)
|
||||
})(css)
|
||||
)
|
||||
ipcMain.handle('resolveThemes', () => ipcErrorWrapper(resolveThemes)())
|
||||
ipcMain.handle('fetchThemes', () => ipcErrorWrapper(fetchThemes)())
|
||||
ipcMain.handle('applyTheme', (_e, theme) => ipcErrorWrapper(applyTheme)(theme))
|
||||
ipcMain.handle('copyEnv', ipcErrorWrapper(copyEnv))
|
||||
ipcMain.handle('alert', (_e, msg) => {
|
||||
dialog.showErrorBox('Mihomo Party', msg)
|
||||
|
|
|
@ -28,7 +28,7 @@ import MihomoCoreCard from '@renderer/components/sider/mihomo-core-card'
|
|||
import ResourceCard from '@renderer/components/sider/resource-card'
|
||||
import UpdaterButton from '@renderer/components/updater/updater-button'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { insertCSS, setNativeTheme, setTitleBarOverlay } from '@renderer/utils/ipc'
|
||||
import { applyTheme, setNativeTheme, setTitleBarOverlay } from '@renderer/utils/ipc'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { TitleBarOverlayOptions } from 'electron'
|
||||
import SubStoreCard from '@renderer/components/sider/substore-card'
|
||||
|
@ -42,8 +42,8 @@ const App: React.FC = () => {
|
|||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const {
|
||||
appTheme = 'system',
|
||||
customTheme,
|
||||
useWindowFrame = false,
|
||||
injectCSS,
|
||||
siderOrder = [
|
||||
'sysproxy',
|
||||
'tun',
|
||||
|
@ -79,15 +79,10 @@ const App: React.FC = () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!injectCSS) return
|
||||
console.log('injectCSS', injectCSS)
|
||||
insertCSS(injectCSS)
|
||||
}, [injectCSS])
|
||||
|
||||
useEffect(() => {
|
||||
setNativeTheme(appTheme)
|
||||
setTheme(appTheme)
|
||||
if (customTheme) applyTheme(customTheme)
|
||||
if (!useWindowFrame) {
|
||||
const options = { height: 48 } as TitleBarOverlayOptions
|
||||
try {
|
||||
|
@ -100,7 +95,7 @@ const App: React.FC = () => {
|
|||
// ignore
|
||||
}
|
||||
}
|
||||
}, [appTheme, systemTheme])
|
||||
}, [appTheme, systemTheme, customTheme])
|
||||
|
||||
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
||||
const { active, over } = event
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||
import { BaseEditor } from '@renderer/components/base/base-editor'
|
||||
import React, { useState } from 'react'
|
||||
interface Props {
|
||||
css: string
|
||||
onCancel: () => void
|
||||
onConfirm: (script: string) => void
|
||||
}
|
||||
const CSSEditorModal: React.FC<Props> = (props) => {
|
||||
const { css, onCancel, onConfirm } = props
|
||||
const [currData, setCurrData] = useState(css)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
backdrop="blur"
|
||||
classNames={{ backdrop: 'top-[48px]' }}
|
||||
size="5xl"
|
||||
hideCloseButton
|
||||
isOpen={true}
|
||||
onOpenChange={onCancel}
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalContent className="h-full w-[calc(100%-100px)]">
|
||||
<ModalHeader className="flex pb-0">编辑 CSS</ModalHeader>
|
||||
<ModalBody className="h-full">
|
||||
<BaseEditor
|
||||
language="css"
|
||||
value={currData}
|
||||
onChange={(value) => setCurrData(value || '')}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter className="pt-0">
|
||||
<Button size="sm" variant="light" onPress={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button size="sm" color="primary" onPress={() => onConfirm(currData)}>
|
||||
确认
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSSEditorModal
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import SettingCard from '../base/base-setting-card'
|
||||
import SettingItem from '../base/base-setting-item'
|
||||
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@nextui-org/react'
|
||||
|
@ -9,19 +9,23 @@ import {
|
|||
copyEnv,
|
||||
disableAutoRun,
|
||||
enableAutoRun,
|
||||
fetchThemes,
|
||||
relaunchApp,
|
||||
resolveThemes,
|
||||
restartCore
|
||||
} from '@renderer/utils/ipc'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IoIosHelpCircle } from 'react-icons/io'
|
||||
import CSSEditorModal from './css-editor-modal'
|
||||
import { IoIosHelpCircle, IoMdCloudDownload } from 'react-icons/io'
|
||||
|
||||
const GeneralConfig: React.FC = () => {
|
||||
const { data: enable, mutate: mutateEnable } = useSWR('checkAutoRun', checkAutoRun)
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const [openCSSEditor, setOpenCSSEditor] = useState(false)
|
||||
const [customThemes, setCustomThemes] = React.useState([
|
||||
{ key: 'default.css', label: '默认', content: '' }
|
||||
])
|
||||
const [fetching, setFetching] = useState(false)
|
||||
const { setTheme } = useTheme()
|
||||
const {
|
||||
silentStart = false,
|
||||
|
@ -31,24 +35,20 @@ const GeneralConfig: React.FC = () => {
|
|||
useWindowFrame = false,
|
||||
autoQuitWithoutCore = false,
|
||||
autoQuitWithoutCoreDelay = 60,
|
||||
injectCSS = DEFAULT_CSS,
|
||||
customTheme = 'default.css',
|
||||
envType = platform === 'win32' ? 'powershell' : 'bash',
|
||||
autoCheckUpdate,
|
||||
appTheme = 'system'
|
||||
} = appConfig || {}
|
||||
|
||||
useEffect(() => {
|
||||
resolveThemes().then((themes) => {
|
||||
setCustomThemes(themes)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{openCSSEditor && (
|
||||
<CSSEditorModal
|
||||
css={injectCSS}
|
||||
onCancel={() => setOpenCSSEditor(false)}
|
||||
onConfirm={async (css: string) => {
|
||||
await patchAppConfig({ injectCSS: css })
|
||||
setOpenCSSEditor(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<SettingCard>
|
||||
<SettingItem title="开机自启" divider>
|
||||
<Switch
|
||||
|
@ -194,12 +194,7 @@ const GeneralConfig: React.FC = () => {
|
|||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem title="自定义样式" divider>
|
||||
<Button size="sm" onPress={() => setOpenCSSEditor(true)} variant="bordered">
|
||||
编辑 CSS 样式
|
||||
</Button>
|
||||
</SettingItem>
|
||||
<SettingItem title="背景色">
|
||||
<SettingItem title="背景色" divider>
|
||||
<Tabs
|
||||
size="sm"
|
||||
color="primary"
|
||||
|
@ -214,157 +209,52 @@ const GeneralConfig: React.FC = () => {
|
|||
<Tab key="light" title="浅色" />
|
||||
</Tabs>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
title="主题"
|
||||
actions={
|
||||
<Button
|
||||
size="sm"
|
||||
isLoading={fetching}
|
||||
isIconOnly
|
||||
title="拉取主题"
|
||||
variant="light"
|
||||
className="ml-2"
|
||||
onPress={async () => {
|
||||
setFetching(true)
|
||||
try {
|
||||
await fetchThemes()
|
||||
setCustomThemes(await resolveThemes())
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
} finally {
|
||||
setFetching(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IoMdCloudDownload className="text-lg" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
className="w-[60%]"
|
||||
size="sm"
|
||||
selectedKeys={new Set([customTheme])}
|
||||
onSelectionChange={async (v) => {
|
||||
try {
|
||||
await patchAppConfig({ customTheme: v.currentKey as string })
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{customThemes.map((theme) => (
|
||||
<SelectItem key={theme.key}>{theme.label}</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</SettingItem>
|
||||
</SettingCard>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DEFAULT_CSS = `/* 使用 !important 以覆盖默认样式 */
|
||||
/* --nextui-xxx 变量只支持hsl色值 */
|
||||
/* 若要对所有主题生效,可直接给html元素设置样式 */
|
||||
|
||||
/* 深色-蓝色 */
|
||||
.dark, [data-theme="dark"] {
|
||||
--nextui-background: 0 0% 0%;
|
||||
--nextui-foreground-50: 240 5.88% 10%;
|
||||
--nextui-foreground-100: 240 3.7% 15.88%;
|
||||
--nextui-foreground-200: 240 5.26% 26.08%;
|
||||
--nextui-foreground-300: 240 5.2% 33.92%;
|
||||
--nextui-foreground-400: 240 3.83% 46.08%;
|
||||
--nextui-foreground-500: 240 5.03% 64.9%;
|
||||
--nextui-foreground-600: 240 4.88% 83.92%;
|
||||
--nextui-foreground-700: 240 5.88% 90%;
|
||||
--nextui-foreground-800: 240 4.76% 95.88%;
|
||||
--nextui-foreground-900: 0 0% 98.04%;
|
||||
--nextui-foreground: 210 5.56% 92.94%;
|
||||
--nextui-focus: 212.01999999999998 100% 46.67%;
|
||||
--nextui-overlay: 0 0% 0%;
|
||||
--nextui-divider: 0 0% 100%;
|
||||
--nextui-divider-opacity: 0.15;
|
||||
--nextui-content1: 240 5.88% 10%;
|
||||
--nextui-content1-foreground: 0 0% 98.04%;
|
||||
--nextui-content2: 240 3.7% 15.88%;
|
||||
--nextui-content2-foreground: 240 4.76% 95.88%;
|
||||
--nextui-content3: 240 5.26% 26.08%;
|
||||
--nextui-content3-foreground: 240 5.88% 90%;
|
||||
--nextui-content4: 240 5.2% 33.92%;
|
||||
--nextui-content4-foreground: 240 4.88% 83.92%;
|
||||
--nextui-default-50: 240 5.88% 10%;
|
||||
--nextui-default-100: 240 3.7% 15.88%;
|
||||
--nextui-default-200: 240 5.26% 26.08%;
|
||||
--nextui-default-300: 240 5.2% 33.92%;
|
||||
--nextui-default-400: 240 3.83% 46.08%;
|
||||
--nextui-default-500: 240 5.03% 64.9%;
|
||||
--nextui-default-600: 240 4.88% 83.92%;
|
||||
--nextui-default-700: 240 5.88% 90%;
|
||||
--nextui-default-800: 240 4.76% 95.88%;
|
||||
--nextui-default-900: 0 0% 98.04%;
|
||||
--nextui-default-foreground: 0 0% 100%;
|
||||
--nextui-default: 240 5.26% 26.08%;
|
||||
--nextui-primary-50: 211.84000000000003 100% 9.61%;
|
||||
--nextui-primary-100: 211.84000000000003 100% 19.22%;
|
||||
--nextui-primary-200: 212.24 100% 28.82%;
|
||||
--nextui-primary-300: 212.14 100% 38.43%;
|
||||
--nextui-primary-400: 212.01999999999998 100% 46.67%;
|
||||
--nextui-primary-500: 212.14 92.45% 58.43%;
|
||||
--nextui-primary-600: 212.24 92.45% 68.82%;
|
||||
--nextui-primary-700: 211.84000000000003 92.45% 79.22%;
|
||||
--nextui-primary-800: 211.84000000000003 92.45% 89.61%;
|
||||
--nextui-primary-900: 212.5 92.31% 94.9%;
|
||||
--nextui-primary: 212.01999999999998 100% 46.67%;
|
||||
--nextui-primary-foreground: 0 0% 100%;
|
||||
--nextui-secondary-50: 270 66.67% 9.41%;
|
||||
--nextui-secondary-100: 270 66.67% 18.82%;
|
||||
--nextui-secondary-200: 270 66.67% 28.24%;
|
||||
--nextui-secondary-300: 270 66.67% 37.65%;
|
||||
--nextui-secondary-400: 270 66.67% 47.06%;
|
||||
--nextui-secondary-500: 270 59.26% 57.65%;
|
||||
--nextui-secondary-600: 270 59.26% 68.24%;
|
||||
--nextui-secondary-700: 270 59.26% 78.82%;
|
||||
--nextui-secondary-800: 270 59.26% 89.41%;
|
||||
--nextui-secondary-900: 270 61.54% 94.9%;
|
||||
--nextui-secondary-foreground: 0 0% 100%;
|
||||
--nextui-secondary: 270 59.26% 57.65%;
|
||||
--nextui-success-50: 145.71000000000004 77.78% 8.82%;
|
||||
--nextui-success-100: 146.2 79.78% 17.45%;
|
||||
--nextui-success-200: 145.78999999999996 79.26% 26.47%;
|
||||
--nextui-success-300: 146.01 79.89% 35.1%;
|
||||
--nextui-success-400: 145.96000000000004 79.46% 43.92%;
|
||||
--nextui-success-500: 146.01 62.45% 55.1%;
|
||||
--nextui-success-600: 145.78999999999996 62.57% 66.47%;
|
||||
--nextui-success-700: 146.2 61.74% 77.45%;
|
||||
--nextui-success-800: 145.71000000000004 61.4% 88.82%;
|
||||
--nextui-success-900: 146.66999999999996 64.29% 94.51%;
|
||||
--nextui-success-foreground: 0 0% 0%;
|
||||
--nextui-success: 145.96000000000004 79.46% 43.92%;
|
||||
--nextui-warning-50: 37.139999999999986 75% 10.98%;
|
||||
--nextui-warning-100: 37.139999999999986 75% 21.96%;
|
||||
--nextui-warning-200: 36.95999999999998 73.96% 33.14%;
|
||||
--nextui-warning-300: 37.00999999999999 74.22% 44.12%;
|
||||
--nextui-warning-400: 37.02999999999997 91.27% 55.1%;
|
||||
--nextui-warning-500: 37.00999999999999 91.26% 64.12%;
|
||||
--nextui-warning-600: 36.95999999999998 91.24% 73.14%;
|
||||
--nextui-warning-700: 37.139999999999986 91.3% 81.96%;
|
||||
--nextui-warning-800: 37.139999999999986 91.3% 90.98%;
|
||||
--nextui-warning-900: 54.55000000000001 91.67% 95.29%;
|
||||
--nextui-warning-foreground: 0 0% 0%;
|
||||
--nextui-warning: 37.02999999999997 91.27% 55.1%;
|
||||
--nextui-danger-50: 340 84.91% 10.39%;
|
||||
--nextui-danger-100: 339.3299999999999 86.54% 20.39%;
|
||||
--nextui-danger-200: 339.11 85.99% 30.78%;
|
||||
--nextui-danger-300: 339 86.54% 40.78%;
|
||||
--nextui-danger-400: 339.20000000000005 90.36% 51.18%;
|
||||
--nextui-danger-500: 339 90% 60.78%;
|
||||
--nextui-danger-600: 339.11 90.6% 70.78%;
|
||||
--nextui-danger-700: 339.3299999999999 90% 80.39%;
|
||||
--nextui-danger-800: 340 91.84% 90.39%;
|
||||
--nextui-danger-900: 339.13 92% 95.1%;
|
||||
--nextui-danger-foreground: 0 0% 100%;
|
||||
--nextui-danger: 339.20000000000005 90.36% 51.18%;
|
||||
--nextui-divider-weight: 1px;
|
||||
--nextui-disabled-opacity: .5;
|
||||
--nextui-font-size-tiny: 0.75rem;
|
||||
--nextui-font-size-small: 0.875rem;
|
||||
--nextui-font-size-medium: 1rem;
|
||||
--nextui-font-size-large: 1.125rem;
|
||||
--nextui-line-height-tiny: 1rem;
|
||||
--nextui-line-height-small: 1.25rem;
|
||||
--nextui-line-height-medium: 1.5rem;
|
||||
--nextui-line-height-large: 1.75rem;
|
||||
--nextui-radius-small: 8px;
|
||||
--nextui-radius-medium: 12px;
|
||||
--nextui-radius-large: 14px;
|
||||
--nextui-border-width-small: 1px;
|
||||
--nextui-border-width-medium: 2px;
|
||||
--nextui-border-width-large: 3px;
|
||||
--nextui-box-shadow-small: 0px 0px 5px 0px rgb(0 0 0 / 0.05), 0px 2px 10px 0px rgb(0 0 0 / 0.2), inset 0px 0px 1px 0px rgb(255 255 255 / 0.15);
|
||||
--nextui-box-shadow-medium: 0px 0px 15px 0px rgb(0 0 0 / 0.06), 0px 2px 30px 0px rgb(0 0 0 / 0.22), inset 0px 0px 1px 0px rgb(255 255 255 / 0.15);
|
||||
--nextui-box-shadow-large: 0px 0px 30px 0px rgb(0 0 0 / 0.07), 0px 30px 60px 0px rgb(0 0 0 / 0.26), inset 0px 0px 1px 0px rgb(255 255 255 / 0.15);
|
||||
--nextui-hover-opacity: .9;
|
||||
}
|
||||
/* 灰色-蓝色 */
|
||||
.gray, [data-theme="gray"] {
|
||||
}
|
||||
/* 浅色-蓝色 */
|
||||
.light, [data-theme="light"] {
|
||||
}
|
||||
/* 深色-粉色 */
|
||||
.dark-pink, [data-theme="dark-pink"] {
|
||||
}
|
||||
/* 灰色-粉色 */
|
||||
.gray-pink, [data-theme="gray-pink"] {
|
||||
}
|
||||
/* 浅色-粉色 */
|
||||
.light-pink, [data-theme="light-pink"] {
|
||||
}
|
||||
/* 深色-绿色 */
|
||||
.dark-green, [data-theme="dark-green"] {
|
||||
}
|
||||
/* 灰色-绿色 */
|
||||
.gray-green, [data-theme="gray-green"] {
|
||||
}
|
||||
/* 浅色-绿色 */
|
||||
.light-green, [data-theme="light-green"] {
|
||||
}
|
||||
`
|
||||
export default GeneralConfig
|
||||
|
|
|
@ -19,7 +19,7 @@ let drawing = false
|
|||
const ConnCard: React.FC = () => {
|
||||
const { theme = 'system', systemTheme = 'dark' } = useTheme()
|
||||
const { appConfig } = useAppConfig()
|
||||
const { showTraffic, connectionCardStatus = 'col-span-2' } = appConfig || {}
|
||||
const { showTraffic, connectionCardStatus = 'col-span-2', customTheme } = appConfig || {}
|
||||
const location = useLocation()
|
||||
const match = location.pathname.includes('/connections')
|
||||
|
||||
|
@ -43,7 +43,7 @@ const ConnCard: React.FC = () => {
|
|||
: islight
|
||||
? window.getComputedStyle(document.documentElement).color
|
||||
: 'rgb(255,255,255)'
|
||||
}, [theme, systemTheme, match])
|
||||
}, [theme, systemTheme, match, customTheme])
|
||||
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
useEffect(() => {
|
||||
|
|
|
@ -331,8 +331,16 @@ export async function createHeapSnapshot(): Promise<void> {
|
|||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('createHeapSnapshot'))
|
||||
}
|
||||
|
||||
export async function insertCSS(css: string): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('insertCSS', css))
|
||||
export async function resolveThemes(): Promise<{ key: string; label: string; content: string }[]> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('resolveThemes'))
|
||||
}
|
||||
|
||||
export async function fetchThemes(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchThemes'))
|
||||
}
|
||||
|
||||
export async function applyTheme(theme: string): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('applyTheme', theme))
|
||||
}
|
||||
|
||||
export async function registerShortcut(
|
||||
|
|
2
src/shared/types.d.ts
vendored
2
src/shared/types.d.ts
vendored
|
@ -223,7 +223,6 @@ interface IAppConfig {
|
|||
substoreCardStatus?: CardStatus
|
||||
sysproxyCardStatus?: CardStatus
|
||||
tunCardStatus?: CardStatus
|
||||
injectCSS?: string
|
||||
useSubStore: boolean
|
||||
subStoreBackendSyncCron?: string
|
||||
subStoreBackendDownloadCron?: string
|
||||
|
@ -238,6 +237,7 @@ interface IAppConfig {
|
|||
proxyInTray: boolean
|
||||
siderOrder: string[]
|
||||
appTheme: AppTheme
|
||||
customTheme: string
|
||||
autoCheckUpdate: boolean
|
||||
silentStart: boolean
|
||||
autoCloseConnection: boolean
|
||||
|
|
Loading…
Reference in New Issue
Block a user