diff --git a/src/components/layout/use-custom-theme.ts b/src/components/layout/use-custom-theme.ts
index fc72a5e..de936a9 100644
--- a/src/components/layout/use-custom-theme.ts
+++ b/src/components/layout/use-custom-theme.ts
@@ -1,49 +1,66 @@
-import useSWR from "swr";
-import { useMemo } from "react";
-import { createTheme } from "@mui/material";
-import { getVergeConfig } from "../../services/cmds";
-
-/**
- * wip: custome theme
- */
-export default function useCustomTheme() {
- const { data } = useSWR("getVergeConfig", getVergeConfig);
- const mode = data?.theme_mode ?? "light";
-
- const theme = useMemo(() => {
- // const background = mode === "light" ? "#f5f5f5" : "#000";
- const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
-
- const rootEle = document.documentElement;
- rootEle.style.background = "transparent";
- rootEle.style.setProperty("--selection-color", selectColor);
-
- const theme = createTheme({
- breakpoints: {
- values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
- },
- palette: {
- mode,
- primary: { main: "#5b5c9d" },
- text: { primary: "#637381", secondary: "#909399" },
- },
- });
-
- const { palette } = theme;
-
- setTimeout(() => {
- const dom = document.querySelector("#Gradient2");
- if (dom) {
- dom.innerHTML = `
-
-
-
- `;
- }
- }, 0);
-
- return theme;
- }, [mode]);
-
- return { theme };
-}
+import useSWR from "swr";
+import { useMemo } from "react";
+import { createTheme } from "@mui/material";
+import { getVergeConfig } from "../../services/cmds";
+import { defaultTheme as dt } from "../../pages/_theme";
+
+/**
+ * custome theme
+ */
+export default function useCustomTheme() {
+ const { data } = useSWR("getVergeConfig", getVergeConfig);
+ const { theme_mode, theme_setting } = data ?? {};
+
+ const theme = useMemo(() => {
+ const mode = theme_mode ?? "light";
+ // const background = mode === "light" ? "#f5f5f5" : "#000";
+ const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
+
+ const rootEle = document.documentElement;
+ rootEle.style.background = "transparent";
+ rootEle.style.setProperty("--selection-color", selectColor);
+
+ const setting = theme_setting || {};
+
+ const theme = createTheme({
+ breakpoints: {
+ values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
+ },
+ palette: {
+ mode,
+ primary: { main: setting.primary_color || dt.primary_color },
+ secondary: { main: setting.secondary_color || dt.secondary_color },
+ info: { main: setting.info_color || dt.info_color },
+ error: { main: setting.error_color || dt.error_color },
+ warning: { main: setting.warning_color || dt.warning_color },
+ success: { main: setting.success_color || dt.success_color },
+ text: {
+ primary: setting.primary_text || dt.primary_text,
+ secondary: setting.secondary_text || dt.secondary_text,
+ },
+ },
+ typography: {
+ fontFamily: setting.font_family
+ ? `"${setting.font_family}", ${dt.font_family}`
+ : dt.font_family,
+ },
+ });
+
+ const { palette } = theme;
+
+ setTimeout(() => {
+ const dom = document.querySelector("#Gradient2");
+ if (dom) {
+ dom.innerHTML = `
+
+
+
+ `;
+ }
+ }, 0);
+
+ return theme;
+ }, [theme_mode, theme_setting]);
+
+ return { theme };
+}
diff --git a/src/components/setting/setting-theme.tsx b/src/components/setting/setting-theme.tsx
new file mode 100644
index 0000000..106896e
--- /dev/null
+++ b/src/components/setting/setting-theme.tsx
@@ -0,0 +1,210 @@
+import useSWR from "swr";
+import { useEffect, useState } from "react";
+import { useLockFn } from "ahooks";
+import { useTranslation } from "react-i18next";
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ List,
+ ListItem,
+ ListItemText,
+ styled,
+ TextField,
+} from "@mui/material";
+import { getVergeConfig, patchVergeConfig } from "../../services/cmds";
+import { defaultTheme } from "../../pages/_theme";
+
+interface Props {
+ open: boolean;
+ onClose: () => void;
+ onError?: (err: Error) => void;
+}
+
+const Item = styled(ListItem)(() => ({
+ padding: "5px 2px",
+}));
+
+const Round = styled("div")(() => ({
+ width: "24px",
+ height: "24px",
+ borderRadius: "18px",
+ display: "inline-block",
+ marginRight: "8px",
+}));
+
+const SettingTheme = (props: Props) => {
+ const { open, onClose, onError } = props;
+
+ const { t } = useTranslation();
+ const { data: vergeConfig, mutate } = useSWR(
+ "getVergeConfig",
+ getVergeConfig
+ );
+
+ const { theme_setting } = vergeConfig ?? {};
+ const [theme, setTheme] = useState(theme_setting || {});
+
+ useEffect(() => {
+ setTheme({ ...theme_setting } || {});
+ }, [theme_setting]);
+
+ const textProps = {
+ size: "small",
+ autoComplete: "off",
+ sx: { width: 135 },
+ } as const;
+
+ const handleChange = (field: keyof typeof theme) => (e: any) => {
+ setTheme((t) => ({ ...t, [field]: e.target.value }));
+ };
+
+ const onSave = useLockFn(async () => {
+ try {
+ await patchVergeConfig({ theme_setting: theme });
+ mutate();
+ } catch (err: any) {
+ onError?.(err);
+ }
+ });
+
+ return (
+
+ );
+};
+
+export default SettingTheme;
diff --git a/src/components/setting/setting-verge.tsx b/src/components/setting/setting-verge.tsx
index 19f19cd..2241b96 100644
--- a/src/components/setting/setting-verge.tsx
+++ b/src/components/setting/setting-verge.tsx
@@ -1,4 +1,5 @@
import useSWR, { useSWRConfig } from "swr";
+import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
IconButton,
@@ -20,6 +21,7 @@ import { CmdType } from "../../services/types";
import { version } from "../../../package.json";
import PaletteSwitch from "./palette-switch";
import GuardState from "./guard-state";
+import SettingTheme from "./setting-theme";
interface Props {
onError?: (err: Error) => void;
@@ -32,6 +34,8 @@ const SettingVerge = ({ onError }: Props) => {
const { theme_mode, theme_blur, traffic_graph, language } = vergeConfig ?? {};
+ const [themeOpen, setThemeOpen] = useState(false);
+
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial) => {
mutate("getVergeConfig", { ...vergeConfig, ...patch }, false);
@@ -99,6 +103,17 @@ const SettingVerge = ({ onError }: Props) => {
+
+
+ setThemeOpen(true)}
+ >
+
+
+
+
@@ -117,6 +132,8 @@ const SettingVerge = ({ onError }: Props) => {
v{version}
+
+ setThemeOpen(false)} />
);
};
diff --git a/src/pages/_theme.tsx b/src/pages/_theme.tsx
new file mode 100644
index 0000000..4b92a03
--- /dev/null
+++ b/src/pages/_theme.tsx
@@ -0,0 +1,12 @@
+// default theme setting
+export const defaultTheme = {
+ primary_color: "#5b5c9d",
+ secondary_color: "#9c27b0",
+ primary_text: "#637381",
+ secondary_text: "#909399",
+ info_color: "#0288d1",
+ error_color: "#d32f2f",
+ warning_color: "#ed6c02",
+ success_color: "#2e7d32",
+ font_family: `"Roboto", "Helvetica", "Arial", sans-serif`,
+};
diff --git a/src/services/types.ts b/src/services/types.ts
index 37e2e14..b098c0f 100644
--- a/src/services/types.ts
+++ b/src/services/types.ts
@@ -130,6 +130,18 @@ export namespace CmdType {
enable_system_proxy?: boolean;
enable_proxy_guard?: boolean;
system_proxy_bypass?: string;
+ theme_setting?: {
+ primary_color?: string;
+ secondary_color?: string;
+ primary_text?: string;
+ secondary_text?: string;
+ info_color?: string;
+ error_color?: string;
+ warning_color?: string;
+ success_color?: string;
+ font_face?: string;
+ font_family?: string;
+ };
}
type ClashConfigValue = any;