feat: theme mode support follows system

This commit is contained in:
GyDi 2022-07-17 16:02:17 +08:00
parent 115e604627
commit 3d1b6d7de7
9 changed files with 97 additions and 43 deletions

View File

@ -1,7 +1,10 @@
import useSWR from "swr"; import useSWR from "swr";
import { useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useRecoilState } from "recoil";
import { createTheme } from "@mui/material"; import { createTheme } from "@mui/material";
import { appWindow } from "@tauri-apps/api/window";
import { getVergeConfig } from "../../services/cmds"; import { getVergeConfig } from "../../services/cmds";
import { atomThemeMode } from "../../services/states";
import { defaultTheme, defaultDarkTheme } from "../../pages/_theme"; import { defaultTheme, defaultDarkTheme } from "../../pages/_theme";
/** /**
@ -10,10 +13,23 @@ import { defaultTheme, defaultDarkTheme } from "../../pages/_theme";
export default function useCustomTheme() { export default function useCustomTheme() {
const { data } = useSWR("getVergeConfig", getVergeConfig); const { data } = useSWR("getVergeConfig", getVergeConfig);
const { theme_mode, theme_setting } = data ?? {}; const { theme_mode, theme_setting } = data ?? {};
const [mode, setMode] = useRecoilState(atomThemeMode);
useEffect(() => {
if (theme_mode !== "system") {
setMode(theme_mode ?? "light");
return;
}
appWindow.theme().then((m) => m && setMode(m));
const unlisten = appWindow.onThemeChanged((e) => setMode(e.payload));
return () => {
unlisten.then((fn) => fn());
};
}, [theme_mode]);
const theme = useMemo(() => { const theme = useMemo(() => {
const mode = theme_mode ?? "light";
const setting = theme_setting || {}; const setting = theme_setting || {};
const dt = mode === "light" ? defaultTheme : defaultDarkTheme; const dt = mode === "light" ? defaultTheme : defaultDarkTheme;
@ -78,7 +94,7 @@ export default function useCustomTheme() {
}, 0); }, 0);
return theme; return theme;
}, [theme_mode, theme_setting]); }, [mode, theme_setting]);
return { theme }; return { theme };
} }

View File

@ -1,6 +1,6 @@
import useSWR from "swr";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { useRecoilValue } from "recoil";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Button, Button,
@ -9,11 +9,8 @@ import {
DialogContent, DialogContent,
DialogTitle, DialogTitle,
} from "@mui/material"; } from "@mui/material";
import { import { atomThemeMode } from "../../services/states";
getVergeConfig, import { readProfileFile, saveProfileFile } from "../../services/cmds";
readProfileFile,
saveProfileFile,
} from "../../services/cmds";
import Notice from "../base/base-notice"; import Notice from "../base/base-notice";
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js"; import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js";
@ -35,8 +32,7 @@ const FileEditor = (props: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const editorRef = useRef<any>(); const editorRef = useRef<any>();
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null); const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig); const themeMode = useRecoilValue(atomThemeMode);
const { theme_mode } = vergeConfig ?? {};
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
@ -50,7 +46,7 @@ const FileEditor = (props: Props) => {
instanceRef.current = editor.create(editorRef.current, { instanceRef.current = editor.create(editorRef.current, {
value: data, value: data,
language: mode, language: mode,
theme: theme_mode === "light" ? "vs" : "vs-dark", theme: themeMode === "light" ? "vs" : "vs-dark",
minimap: { enabled: false }, minimap: { enabled: false },
}); });
}); });

View File

@ -1,5 +1,6 @@
import { styled, Switch } from "@mui/material"; import { styled, Switch } from "@mui/material";
// todo: deprecated
// From: https://mui.com/components/switches/ // From: https://mui.com/components/switches/
const PaletteSwitch = styled(Switch)(({ theme }) => ({ const PaletteSwitch = styled(Switch)(({ theme }) => ({
width: 62, width: 62,

View File

@ -19,7 +19,7 @@ import { ArrowForward } from "@mui/icons-material";
import { SettingList, SettingItem } from "./setting"; import { SettingList, SettingItem } from "./setting";
import { CmdType } from "../../services/types"; import { CmdType } from "../../services/types";
import { version } from "../../../package.json"; import { version } from "../../../package.json";
import PaletteSwitch from "./palette-switch"; import ThemeModeSwitch from "./theme-mode-switch";
import GuardState from "./guard-state"; import GuardState from "./guard-state";
import SettingTheme from "./setting-theme"; import SettingTheme from "./setting-theme";
@ -43,19 +43,31 @@ const SettingVerge = ({ onError }: Props) => {
return ( return (
<SettingList title={t("Verge Setting")}> <SettingList title={t("Verge Setting")}>
<SettingItem>
<ListItemText primary={t("Language")} />
<GuardState
value={language ?? "en"}
onCatch={onError}
onFormat={(e: any) => e.target.value}
onChange={(e) => onChangeData({ language: e })}
onGuard={(e) => patchVergeConfig({ language: e })}
>
<Select size="small" sx={{ width: 100 }}>
<MenuItem value="zh"></MenuItem>
<MenuItem value="en">English</MenuItem>
</Select>
</GuardState>
</SettingItem>
<SettingItem> <SettingItem>
<ListItemText primary={t("Theme Mode")} /> <ListItemText primary={t("Theme Mode")} />
<GuardState <GuardState
value={theme_mode === "dark"} value={theme_mode}
valueProps="checked"
onCatch={onError} onCatch={onError}
onFormat={onSwitchFormat} onChange={(e) => onChangeData({ theme_mode: e })}
onChange={(e) => onChangeData({ theme_mode: e ? "dark" : "light" })} onGuard={(e) => patchVergeConfig({ theme_mode: e })}
onGuard={(e) =>
patchVergeConfig({ theme_mode: e ? "dark" : "light" })
}
> >
<PaletteSwitch edge="end" /> <ThemeModeSwitch />
</GuardState> </GuardState>
</SettingItem> </SettingItem>
@ -87,22 +99,6 @@ const SettingVerge = ({ onError }: Props) => {
</GuardState> </GuardState>
</SettingItem> </SettingItem>
<SettingItem>
<ListItemText primary={t("Language")} />
<GuardState
value={language ?? "en"}
onCatch={onError}
onFormat={(e: any) => e.target.value}
onChange={(e) => onChangeData({ language: e })}
onGuard={(e) => patchVergeConfig({ language: e })}
>
<Select size="small" sx={{ width: 100 }}>
<MenuItem value="zh"></MenuItem>
<MenuItem value="en">English</MenuItem>
</Select>
</GuardState>
</SettingItem>
<SettingItem> <SettingItem>
<ListItemText primary={t("Theme Setting")} /> <ListItemText primary={t("Theme Setting")} />
<IconButton <IconButton
@ -129,7 +125,7 @@ const SettingVerge = ({ onError }: Props) => {
</SettingItem> </SettingItem>
<SettingItem> <SettingItem>
<ListItemText primary={t("Version")} /> <ListItemText primary={t("Verge Version")} />
<Typography sx={{ py: "6px" }}>v{version}</Typography> <Typography sx={{ py: "6px" }}>v{version}</Typography>
</SettingItem> </SettingItem>

View File

@ -0,0 +1,34 @@
import { useTranslation } from "react-i18next";
import { Button, ButtonGroup } from "@mui/material";
import { CmdType } from "../../services/types";
type ThemeValue = CmdType.VergeConfig["theme_mode"];
interface Props {
value?: ThemeValue;
onChange?: (value: ThemeValue) => void;
}
const ThemeModeSwitch = (props: Props) => {
const { value, onChange } = props;
const { t } = useTranslation();
const modes = ["light", "dark", "system"] as const;
return (
<ButtonGroup size="small">
{modes.map((mode) => (
<Button
key={mode}
variant={mode === value ? "contained" : "outlined"}
onClick={() => onChange?.(mode)}
sx={{ textTransform: "capitalize" }}
>
{t(`theme.${mode}`)}
</Button>
))}
</ButtonGroup>
);
};
export default ThemeModeSwitch;

View File

@ -55,7 +55,10 @@
"Language": "Language", "Language": "Language",
"Open App Dir": "Open App Dir", "Open App Dir": "Open App Dir",
"Open Logs Dir": "Open Logs Dir", "Open Logs Dir": "Open Logs Dir",
"Version": "Version", "Verge Version": "Verge Version",
"theme.light": "Light",
"theme.dark": "Dark",
"theme.system": "System",
"Save": "Save", "Save": "Save",
"Cancel": "Cancel" "Cancel": "Cancel"

View File

@ -48,14 +48,17 @@
"System Proxy": "系统代理", "System Proxy": "系统代理",
"Proxy Guard": "系统代理守卫", "Proxy Guard": "系统代理守卫",
"Proxy Bypass": "Proxy Bypass", "Proxy Bypass": "Proxy Bypass",
"Theme Mode": "暗夜模式", "Theme Mode": "主题模式",
"Theme Blur": "背景模糊", "Theme Blur": "背景模糊",
"Theme Setting": "主题设置", "Theme Setting": "主题设置",
"Traffic Graph": "流量图显", "Traffic Graph": "流量图显",
"Language": "语言设置", "Language": "语言设置",
"Open App Dir": "应用目录", "Open App Dir": "应用目录",
"Open Logs Dir": "日志目录", "Open Logs Dir": "日志目录",
"Version": "版本", "Verge Version": "应用版本",
"theme.light": "浅色",
"theme.dark": "深色",
"theme.system": "系统",
"Save": "保存", "Save": "保存",
"Cancel": "取消" "Cancel": "取消"

View File

@ -1,6 +1,11 @@
import { atom } from "recoil"; import { atom } from "recoil";
import { ApiType } from "./types"; import { ApiType } from "./types";
export const atomThemeMode = atom<"light" | "dark">({
key: "atomThemeMode",
default: "light",
});
export const atomClashPort = atom<number>({ export const atomClashPort = atom<number>({
key: "atomClashPort", key: "atomClashPort",
default: 0, default: 0,

View File

@ -126,7 +126,7 @@ export namespace CmdType {
export interface VergeConfig { export interface VergeConfig {
language?: string; language?: string;
clash_core?: string; clash_core?: string;
theme_mode?: "light" | "dark"; theme_mode?: "light" | "dark" | "system";
theme_blur?: boolean; theme_blur?: boolean;
traffic_graph?: boolean; traffic_graph?: boolean;
enable_tun_mode?: boolean; enable_tun_mode?: boolean;