feat: support theme setting

This commit is contained in:
GyDi 2022-03-31 23:38:00 +08:00
parent 3ec2b46d28
commit f9a96ff914
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
5 changed files with 317 additions and 49 deletions

View File

@ -1,49 +1,66 @@
import useSWR from "swr"; import useSWR from "swr";
import { useMemo } from "react"; import { useMemo } from "react";
import { createTheme } from "@mui/material"; import { createTheme } from "@mui/material";
import { getVergeConfig } from "../../services/cmds"; import { getVergeConfig } from "../../services/cmds";
import { defaultTheme as dt } from "../../pages/_theme";
/**
* wip: custome theme /**
*/ * custome theme
export default function useCustomTheme() { */
const { data } = useSWR("getVergeConfig", getVergeConfig); export default function useCustomTheme() {
const mode = data?.theme_mode ?? "light"; const { data } = useSWR("getVergeConfig", getVergeConfig);
const { theme_mode, theme_setting } = data ?? {};
const theme = useMemo(() => {
// const background = mode === "light" ? "#f5f5f5" : "#000"; const theme = useMemo(() => {
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5"; const mode = theme_mode ?? "light";
// const background = mode === "light" ? "#f5f5f5" : "#000";
const rootEle = document.documentElement; const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
rootEle.style.background = "transparent";
rootEle.style.setProperty("--selection-color", selectColor); const rootEle = document.documentElement;
rootEle.style.background = "transparent";
const theme = createTheme({ rootEle.style.setProperty("--selection-color", selectColor);
breakpoints: {
values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 }, const setting = theme_setting || {};
},
palette: { const theme = createTheme({
mode, breakpoints: {
primary: { main: "#5b5c9d" }, values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
text: { primary: "#637381", secondary: "#909399" }, },
}, palette: {
}); mode,
primary: { main: setting.primary_color || dt.primary_color },
const { palette } = theme; secondary: { main: setting.secondary_color || dt.secondary_color },
info: { main: setting.info_color || dt.info_color },
setTimeout(() => { error: { main: setting.error_color || dt.error_color },
const dom = document.querySelector("#Gradient2"); warning: { main: setting.warning_color || dt.warning_color },
if (dom) { success: { main: setting.success_color || dt.success_color },
dom.innerHTML = ` text: {
<stop offset="0%" stop-color="${palette.primary.main}" /> primary: setting.primary_text || dt.primary_text,
<stop offset="80%" stop-color="${palette.primary.dark}" /> secondary: setting.secondary_text || dt.secondary_text,
<stop offset="100%" stop-color="${palette.primary.dark}" /> },
`; },
} typography: {
}, 0); fontFamily: setting.font_family
? `"${setting.font_family}", ${dt.font_family}`
return theme; : dt.font_family,
}, [mode]); },
});
return { theme };
} const { palette } = theme;
setTimeout(() => {
const dom = document.querySelector("#Gradient2");
if (dom) {
dom.innerHTML = `
<stop offset="0%" stop-color="${palette.primary.main}" />
<stop offset="80%" stop-color="${palette.primary.dark}" />
<stop offset="100%" stop-color="${palette.primary.dark}" />
`;
}
}, 0);
return theme;
}, [theme_mode, theme_setting]);
return { theme };
}

View File

@ -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 (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{t("Theme Setting")}</DialogTitle>
<DialogContent
sx={{ width: 400, maxHeight: 300, overflow: "auto", pb: 0 }}
>
<List sx={{ pt: 0 }}>
<Item>
<ListItemText primary="Primary Color" />
<Round
sx={{
background: theme.primary_color || defaultTheme.primary_color,
}}
/>
<TextField
{...textProps}
value={theme.primary_color}
placeholder={defaultTheme.primary_color}
onChange={handleChange("primary_color")}
/>
</Item>
<Item>
<ListItemText primary="Secondary Color" />
<Round
sx={{
background:
theme.secondary_color || defaultTheme.secondary_color,
}}
/>
<TextField
{...textProps}
value={theme.secondary_color}
placeholder={defaultTheme.secondary_color}
onChange={handleChange("secondary_color")}
/>
</Item>
<Item>
<ListItemText primary="Info Color" />
<Round
sx={{
background: theme.info_color || defaultTheme.info_color,
}}
/>
<TextField
{...textProps}
value={theme.info_color}
placeholder={defaultTheme.info_color}
onChange={handleChange("info_color")}
/>
</Item>
<Item>
<ListItemText primary="Error Color" />
<Round
sx={{
background: theme.error_color || defaultTheme.error_color,
}}
/>
<TextField
{...textProps}
value={theme.error_color}
placeholder={defaultTheme.error_color}
onChange={handleChange("error_color")}
/>
</Item>
<Item>
<ListItemText primary="Warning Color" />
<Round
sx={{
background: theme.warning_color || defaultTheme.warning_color,
}}
/>
<TextField
{...textProps}
value={theme.warning_color}
placeholder={defaultTheme.warning_color}
onChange={handleChange("warning_color")}
/>
</Item>
<Item>
<ListItemText primary="Success Color" />
<Round
sx={{
background: theme.success_color || defaultTheme.success_color,
}}
/>
<TextField
{...textProps}
value={theme.success_color}
placeholder={defaultTheme.success_color}
onChange={handleChange("success_color")}
/>
</Item>
<Item>
<ListItemText primary="Font Family" />
<TextField
{...textProps}
value={theme.font_family}
onChange={handleChange("font_family")}
/>
</Item>
<Item>
<ListItemText primary="Font Face" />
<TextField
{...textProps}
value={theme.font_face}
onChange={handleChange("font_face")}
/>
</Item>
</List>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>{t("Cancel")}</Button>
<Button onClick={onSave} variant="contained">
{t("Save")}
</Button>
</DialogActions>
</Dialog>
);
};
export default SettingTheme;

View File

@ -1,4 +1,5 @@
import useSWR, { useSWRConfig } from "swr"; import useSWR, { useSWRConfig } from "swr";
import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
IconButton, IconButton,
@ -20,6 +21,7 @@ import { CmdType } from "../../services/types";
import { version } from "../../../package.json"; import { version } from "../../../package.json";
import PaletteSwitch from "./palette-switch"; import PaletteSwitch from "./palette-switch";
import GuardState from "./guard-state"; import GuardState from "./guard-state";
import SettingTheme from "./setting-theme";
interface Props { interface Props {
onError?: (err: Error) => void; onError?: (err: Error) => void;
@ -32,6 +34,8 @@ const SettingVerge = ({ onError }: Props) => {
const { theme_mode, theme_blur, traffic_graph, language } = vergeConfig ?? {}; const { theme_mode, theme_blur, traffic_graph, language } = vergeConfig ?? {};
const [themeOpen, setThemeOpen] = useState(false);
const onSwitchFormat = (_e: any, value: boolean) => value; const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<CmdType.VergeConfig>) => { const onChangeData = (patch: Partial<CmdType.VergeConfig>) => {
mutate("getVergeConfig", { ...vergeConfig, ...patch }, false); mutate("getVergeConfig", { ...vergeConfig, ...patch }, false);
@ -99,6 +103,17 @@ const SettingVerge = ({ onError }: Props) => {
</GuardState> </GuardState>
</SettingItem> </SettingItem>
<SettingItem>
<ListItemText primary={t("Theme Setting")} />
<IconButton
color="inherit"
size="small"
onClick={() => setThemeOpen(true)}
>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem> <SettingItem>
<ListItemText primary={t("Open App Dir")} /> <ListItemText primary={t("Open App Dir")} />
<IconButton color="inherit" size="small" onClick={openAppDir}> <IconButton color="inherit" size="small" onClick={openAppDir}>
@ -117,6 +132,8 @@ const SettingVerge = ({ onError }: Props) => {
<ListItemText primary={t("Version")} /> <ListItemText primary={t("Version")} />
<Typography sx={{ py: "6px" }}>v{version}</Typography> <Typography sx={{ py: "6px" }}>v{version}</Typography>
</SettingItem> </SettingItem>
<SettingTheme open={themeOpen} onClose={() => setThemeOpen(false)} />
</SettingList> </SettingList>
); );
}; };

12
src/pages/_theme.tsx Normal file
View File

@ -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`,
};

View File

@ -130,6 +130,18 @@ export namespace CmdType {
enable_system_proxy?: boolean; enable_system_proxy?: boolean;
enable_proxy_guard?: boolean; enable_proxy_guard?: boolean;
system_proxy_bypass?: string; 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; type ClashConfigValue = any;