From 9e8466f8553d043711fbbcda76df004d3fa00c78 Mon Sep 17 00:00:00 2001 From: pompurin404 Date: Thu, 19 Sep 2024 11:40:10 +0800 Subject: [PATCH] support css inject --- changelog.md | 7 +- electron.vite.config.ts | 2 +- src/main/utils/ipc.ts | 10 + src/renderer/src/App.tsx | 10 +- .../src/components/base/base-editor.tsx | 2 +- .../components/settings/css-editor-modal.tsx | 45 ++ .../components/settings/general-config.tsx | 488 ++++++++++++------ .../components/sysproxy/pac-editor-modal.tsx | 4 +- src/renderer/src/pages/syspeoxy.tsx | 4 +- src/renderer/src/utils/ipc.ts | 4 + src/shared/types.d.ts | 1 + 11 files changed, 404 insertions(+), 173 deletions(-) create mode 100644 src/renderer/src/components/settings/css-editor-modal.tsx diff --git a/changelog.md b/changelog.md index e0caf6e..29e1183 100644 --- a/changelog.md +++ b/changelog.md @@ -4,9 +4,4 @@ ### Features -- 支持托盘菜单切换订阅 -- Windows 支持一键更新 - -### Bug Fixes - -- 修复覆写更新后全局启用状态丢失的问题 +- 支持自定义CSS样式 diff --git a/electron.vite.config.ts b/electron.vite.config.ts index b1c86d5..0c1f0d8 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ plugins: [ react(), monacoEditorPlugin({ - languageWorkers: ['editorWorkerService', 'typescript', 'json'], + languageWorkers: ['editorWorkerService', 'typescript', 'css'], customDistPath: (_, out) => `${out}/monacoeditorwork`, customWorkers: [ { diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 0e06ee1..5e72e10 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -70,6 +70,8 @@ import { logDir } from './dirs' import path from 'path' import v8 from 'v8' +let insertedCSSKey: string | undefined + function ipcErrorWrapper( // eslint-disable-next-line @typescript-eslint/no-explicit-any fn: (...args: any[]) => Promise // eslint-disable-next-line @typescript-eslint/no-explicit-any ): (...args: any[]) => Promise { @@ -201,6 +203,14 @@ 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('copyEnv', ipcErrorWrapper(copyEnv)) ipcMain.handle('alert', (_e, msg) => { dialog.showErrorBox('Mihomo Party', msg) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index f99576e..84e01ae 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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 { setNativeTheme, setTitleBarOverlay } from '@renderer/utils/ipc' +import { insertCSS, setNativeTheme, setTitleBarOverlay } from '@renderer/utils/ipc' import { platform } from '@renderer/utils/init' import { TitleBarOverlayOptions } from 'electron' import SubStoreCard from '@renderer/components/sider/substore-card' @@ -43,6 +43,7 @@ const App: React.FC = () => { const { appTheme = 'system', useWindowFrame = false, + injectCSS, siderOrder = [ 'sysproxy', 'tun', @@ -78,6 +79,13 @@ const App: React.FC = () => { } }, []) + useEffect(() => { + if (!injectCSS) return + console.log('injectCSS', injectCSS) + // window.electron.webFrame.insertCSS(injectCSS) + insertCSS(injectCSS) + }, [injectCSS]) + useEffect(() => { if (appTheme.includes('light')) { setNativeTheme('light') diff --git a/src/renderer/src/components/base/base-editor.tsx b/src/renderer/src/components/base/base-editor.tsx index 6f572b5..5b9e4c3 100644 --- a/src/renderer/src/components/base/base-editor.tsx +++ b/src/renderer/src/components/base/base-editor.tsx @@ -7,7 +7,7 @@ import pac from 'types-pac/pac.d.ts?raw' import { useTheme } from 'next-themes' import { nanoid } from 'nanoid' import React from 'react' -type Language = 'yaml' | 'javascript' | 'json' +type Language = 'yaml' | 'javascript' | 'css' interface Props { value: string diff --git a/src/renderer/src/components/settings/css-editor-modal.tsx b/src/renderer/src/components/settings/css-editor-modal.tsx new file mode 100644 index 0000000..e83e3db --- /dev/null +++ b/src/renderer/src/components/settings/css-editor-modal.tsx @@ -0,0 +1,45 @@ +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) => { + const { css, onCancel, onConfirm } = props + const [currData, setCurrData] = useState(css) + + return ( + + + 编辑 CSS + + setCurrData(value || '')} + /> + + + + + + + + ) +} + +export default CSSEditorModal diff --git a/src/renderer/src/components/settings/general-config.tsx b/src/renderer/src/components/settings/general-config.tsx index 0e6f435..605dfbe 100644 --- a/src/renderer/src/components/settings/general-config.tsx +++ b/src/renderer/src/components/settings/general-config.tsx @@ -1,4 +1,4 @@ -import React, { Key } from 'react' +import React, { Key, 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' @@ -16,10 +16,12 @@ 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' const GeneralConfig: React.FC = () => { const { data: enable, mutate: mutateEnable } = useSWR('checkAutoRun', checkAutoRun) const { appConfig, patchAppConfig } = useAppConfig() + const [openCSSEditor, setOpenCSSEditor] = useState(false) const { setTheme } = useTheme() const { silentStart = false, @@ -29,6 +31,7 @@ const GeneralConfig: React.FC = () => { useWindowFrame = false, autoQuitWithoutCore = false, autoQuitWithoutCoreDelay = 60, + injectCSS = DEFAULT_CSS, envType = platform === 'win32' ? 'powershell' : 'bash', autoCheckUpdate, appTheme = 'system' @@ -59,184 +62,349 @@ const GeneralConfig: React.FC = () => { } return ( - - - { - try { - if (v) { - await enableAutoRun() - } else { - await disableAutoRun() - } - } catch (e) { - alert(e) - } finally { - mutateEnable() - } + <> + {openCSSEditor && ( + setOpenCSSEditor(false)} + onConfirm={async (css: string) => { + await patchAppConfig({ injectCSS: css }) + setOpenCSSEditor(false) }} /> - - - { - patchAppConfig({ autoCheckUpdate: v }) - }} - /> - - - { - patchAppConfig({ silentStart: v }) - }} - /> - - - - - } - divider - > - { - patchAppConfig({ autoQuitWithoutCore: v }) - }} - /> - - {autoQuitWithoutCore && ( - - { - let num = parseInt(v) - if (isNaN(num)) num = 5 - if (num < 5) num = 5 - await patchAppConfig({ autoQuitWithoutCoreDelay: num }) - }} - /> - )} - - - - } - divider - > - - - {platform !== 'linux' && ( - + + { - await patchAppConfig({ proxyInTray: v }) + try { + if (v) { + await enableAutoRun() + } else { + await disableAutoRun() + } + } catch (e) { + alert(e) + } finally { + mutateEnable() + } }} /> - )} - {platform === 'darwin' && ( - <> - - { - await patchAppConfig({ useDockIcon: v }) - }} - /> - - - { - await patchAppConfig({ showTraffic: v }) - await restartCore() - }} - /> - - - )} - - - { - await patchAppConfig({ useWindowFrame: v }) - await relaunchApp() - }} - /> - - - { - onThemeChange(key, 'theme') - }} + + { + patchAppConfig({ autoCheckUpdate: v }) + }} + /> + + + { + patchAppConfig({ silentStart: v }) + }} + /> + + + + + } + divider > - - - - - - - {appTheme !== 'system' && ( - + { + patchAppConfig({ autoQuitWithoutCore: v }) + }} + /> + + {autoQuitWithoutCore && ( + + { + let num = parseInt(v) + if (isNaN(num)) num = 5 + if (num < 5) num = 5 + await patchAppConfig({ autoQuitWithoutCoreDelay: num }) + }} + /> + + )} + + + + } + divider + > + + + {platform !== 'linux' && ( + + { + await patchAppConfig({ proxyInTray: v }) + }} + /> + + )} + {platform === 'darwin' && ( + <> + + { + await patchAppConfig({ useDockIcon: v }) + }} + /> + + + { + await patchAppConfig({ showTraffic: v }) + await restartCore() + }} + /> + + + )} + + + { + await patchAppConfig({ useWindowFrame: v }) + await relaunchApp() + }} + /> + + + + + { - onThemeChange(key, 'color') + onThemeChange(key, 'theme') }} > - - - + + + + - )} - + {appTheme !== 'system' && ( + + { + onThemeChange(key, 'color') + }} + > + + + + + + )} + + ) } +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 diff --git a/src/renderer/src/components/sysproxy/pac-editor-modal.tsx b/src/renderer/src/components/sysproxy/pac-editor-modal.tsx index 153355a..56d8f0d 100644 --- a/src/renderer/src/components/sysproxy/pac-editor-modal.tsx +++ b/src/renderer/src/components/sysproxy/pac-editor-modal.tsx @@ -6,7 +6,7 @@ interface Props { onCancel: () => void onConfirm: (script: string) => void } -const PacEditorViewer: React.FC = (props) => { +const PacEditorModal: React.FC = (props) => { const { script, onCancel, onConfirm } = props const [currData, setCurrData] = useState(script) @@ -42,4 +42,4 @@ const PacEditorViewer: React.FC = (props) => { ) } -export default PacEditorViewer +export default PacEditorModal diff --git a/src/renderer/src/pages/syspeoxy.tsx b/src/renderer/src/pages/syspeoxy.tsx index 1dac33f..b352b1a 100644 --- a/src/renderer/src/pages/syspeoxy.tsx +++ b/src/renderer/src/pages/syspeoxy.tsx @@ -2,7 +2,7 @@ import { Button, Input, Tab, Tabs } from '@nextui-org/react' import BasePage from '@renderer/components/base/base-page' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' -import PacEditorViewer from '@renderer/components/sysproxy/pac-editor-modal' +import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal' import { useAppConfig } from '@renderer/hooks/use-app-config' import { platform } from '@renderer/utils/init' import { openUWPTool, triggerSysProxy } from '@renderer/utils/ipc' @@ -114,7 +114,7 @@ const Sysproxy: React.FC = () => { } > {openPacEditor && ( - setOpenPacEditor(false)} onConfirm={(script: string) => { diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index f6914ef..ce445e1 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -331,6 +331,10 @@ export async function createHeapSnapshot(): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('createHeapSnapshot')) } +export async function insertCSS(css: string): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('insertCSS', css)) +} + export async function registerShortcut( oldShortcut: string, newShortcut: string, diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index 74fe3c3..b0919f6 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -233,6 +233,7 @@ interface IAppConfig { substoreCardStatus?: CardStatus sysproxyCardStatus?: CardStatus tunCardStatus?: CardStatus + injectCSS?: string useSubStore: boolean subStoreBackendSyncCron?: string subStoreBackendDownloadCron?: string