diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index be16c85..708ce61 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -26,6 +26,9 @@ pub struct IVerge { /// enable traffic graph default is true pub traffic_graph: Option, + /// show memory info (only for Clash Meta) + pub enable_memory_usage: Option, + /// clash tun mode pub enable_tun_mode: Option, @@ -125,6 +128,7 @@ impl IVerge { theme_mode: Some("system".into()), theme_blur: Some(false), traffic_graph: Some(true), + enable_memory_usage: Some(true), enable_auto_launch: Some(false), enable_silent_start: Some(false), enable_system_proxy: Some(false), @@ -158,6 +162,7 @@ impl IVerge { patch!(theme_mode); patch!(theme_blur); patch!(traffic_graph); + patch!(enable_memory_usage); patch!(enable_tun_mode); patch!(enable_service_mode); diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 4095c05..01e55e5 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -1,6 +1,10 @@ import { useEffect, useRef, useState } from "react"; import { Box, Typography } from "@mui/material"; -import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; +import { + ArrowDownward, + ArrowUpward, + MemoryOutlined, +} from "@mui/icons-material"; import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { TrafficGraph, type TrafficRef } from "./traffic-graph"; @@ -12,13 +16,15 @@ import parseTraffic from "@/utils/parse-traffic"; // setup the traffic const LayoutTraffic = () => { const { clashInfo } = useClashInfo(); + const { verge } = useVerge(); // whether hide traffic graph - const { verge } = useVerge(); const trafficGraph = verge?.traffic_graph ?? true; const trafficRef = useRef(null); const [traffic, setTraffic] = useState({ up: 0, down: 0 }); + const [memory, setMemory] = useState({ inuse: 0 }); + const pageVisible = useVisibility(); // setup log ws during layout useLogSetup(); @@ -29,8 +35,6 @@ const LayoutTraffic = () => { setTraffic(data); }); - const pageVisible = useVisibility(); - useEffect(() => { if (!clashInfo || !pageVisible) return; @@ -42,14 +46,38 @@ const LayoutTraffic = () => { }; }, [clashInfo, pageVisible]); + /* --------- meta memory information --------- */ + const isMetaCore = verge?.clash_core === "clash-meta"; + const displayMemory = isMetaCore && (verge?.enable_memory_usage ?? true); + + const memoryWs = useWebsocket( + (event) => { + setMemory(JSON.parse(event.data)); + }, + { onError: () => setMemory({ inuse: 0 }) } + ); + + useEffect(() => { + if (!clashInfo || !pageVisible || !displayMemory) return; + const { server = "", secret = "" } = clashInfo; + memoryWs.connect( + `ws://${server}/memory?token=${encodeURIComponent(secret)}` + ); + return () => memoryWs.disconnect(); + }, [clashInfo, pageVisible, displayMemory]); + const [up, upUnit] = parseTraffic(traffic.up); const [down, downUnit] = parseTraffic(traffic.down); + const [inuse, inuseUnit] = parseTraffic(memory.inuse); + const iconStyle: any = { + sx: { mr: "8px", fontSize: 16 }, + }; const valStyle: any = { component: "span", color: "primary", textAlign: "center", - sx: { flex: "1 1 54px", userSelect: "none" }, + sx: { flex: "1 1 56px", userSelect: "none" }, }; const unitStyle: any = { component: "span", @@ -71,22 +99,37 @@ const LayoutTraffic = () => { )} - - 0 ? "primary" : "disabled"} - /> - {up} - {upUnit}/s - + + + 0 ? "primary" : "disabled"} + /> + {up} + {upUnit}/s + - - 0 ? "primary" : "disabled"} - /> - {down} - {downUnit}/s + + 0 ? "primary" : "disabled"} + /> + {down} + {downUnit}/s + + + {displayMemory && ( + + + {inuse} + {inuseUnit} + + )} ); diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx new file mode 100644 index 0000000..baeca95 --- /dev/null +++ b/src/components/setting/mods/layout-viewer.tsx @@ -0,0 +1,80 @@ +import { forwardRef, useImperativeHandle, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { List, Switch } from "@mui/material"; +import { useVerge } from "@/hooks/use-verge"; +import { BaseDialog, DialogRef, Notice } from "@/components/base"; +import { SettingItem } from "./setting-comp"; +import { GuardState } from "./guard-state"; + +export const LayoutViewer = forwardRef((props, ref) => { + const { t } = useTranslation(); + const { verge, patchVerge, mutateVerge } = useVerge(); + + const [open, setOpen] = useState(false); + + useImperativeHandle(ref, () => ({ + open: () => setOpen(true), + close: () => setOpen(false), + })); + + const onSwitchFormat = (_e: any, value: boolean) => value; + const onError = (err: any) => { + Notice.error(err.message || err.toString()); + }; + const onChangeData = (patch: Partial) => { + mutateVerge({ ...verge, ...patch }, false); + }; + + return ( + setOpen(false)} + onCancel={() => setOpen(false)} + > + + + onChangeData({ theme_blur: e })} + onGuard={(e) => patchVerge({ theme_blur: e })} + > + + + + + + onChangeData({ traffic_graph: e })} + onGuard={(e) => patchVerge({ traffic_graph: e })} + > + + + + + + onChangeData({ enable_memory_usage: e })} + onGuard={(e) => patchVerge({ enable_memory_usage: e })} + > + + + + + + ); +}); diff --git a/src/components/setting/mods/setting-comp.tsx b/src/components/setting/mods/setting-comp.tsx index 9b4225b..e0cd181 100644 --- a/src/components/setting/mods/setting-comp.tsx +++ b/src/components/setting/mods/setting-comp.tsx @@ -11,10 +11,11 @@ interface ItemProps { label: ReactNode; extra?: ReactNode; children?: ReactNode; + secondary?: ReactNode; } export const SettingItem: React.FC = (props) => { - const { label, extra, children } = props; + const { label, extra, children, secondary } = props; const primary = !extra ? ( label @@ -27,7 +28,7 @@ export const SettingItem: React.FC = (props) => { return ( - + {children} ); diff --git a/src/components/setting/setting-verge.tsx b/src/components/setting/setting-verge.tsx index 0de06e1..9805e5e 100644 --- a/src/components/setting/setting-verge.tsx +++ b/src/components/setting/setting-verge.tsx @@ -1,12 +1,6 @@ import { useRef } from "react"; import { useTranslation } from "react-i18next"; -import { - IconButton, - MenuItem, - Select, - Switch, - Typography, -} from "@mui/material"; +import { IconButton, MenuItem, Select, Typography } from "@mui/material"; import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds"; import { ArrowForward } from "@mui/icons-material"; import { useVerge } from "@/hooks/use-verge"; @@ -19,6 +13,7 @@ import { HotkeyViewer } from "./mods/hotkey-viewer"; import { MiscViewer } from "./mods/misc-viewer"; import { ThemeViewer } from "./mods/theme-viewer"; import { GuardState } from "./mods/guard-state"; +import { LayoutViewer } from "./mods/layout-viewer"; interface Props { onError?: (err: Error) => void; @@ -35,6 +30,7 @@ const SettingVerge = ({ onError }: Props) => { const hotkeyRef = useRef(null); const miscRef = useRef(null); const themeRef = useRef(null); + const layoutRef = useRef(null); const onSwitchFormat = (_e: any, value: boolean) => value; const onChangeData = (patch: Partial) => { @@ -47,6 +43,7 @@ const SettingVerge = ({ onError }: Props) => { + { - - onChangeData({ theme_blur: e })} - onGuard={(e) => patchVerge({ theme_blur: e })} + + themeRef.current?.open()} > - - + + - - onChangeData({ traffic_graph: e })} - onGuard={(e) => patchVerge({ traffic_graph: e })} + + layoutRef.current?.open()} > - - + + @@ -112,17 +105,6 @@ const SettingVerge = ({ onError }: Props) => { - - themeRef.current?.open()} - > - - - - ) => void; export interface WsOptions { errorCount?: number; // default is 5 retryInterval?: number; // default is 2500 + onError?: () => void; } export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { @@ -38,6 +39,9 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { if (errorCount >= 0) { timerRef.current = setTimeout(connectHelper, 2500); + } else { + disconnect(); + options?.onError?.(); } }); }; diff --git a/src/locales/en.json b/src/locales/en.json index f43abdc..3a14bc5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -55,6 +55,8 @@ "Descriptions": "Descriptions", "Subscription URL": "Subscription URL", "Update Interval": "Update Interval", + "Use System Proxy": "Use System Proxy", + "Use Clash Proxy": "Use Clash Proxy", "Settings": "Settings", "Clash Setting": "Clash Setting", @@ -64,6 +66,7 @@ "IPv6": "IPv6", "Log Level": "Log Level", "Mixed Port": "Mixed Port", + "External": "External", "Clash Core": "Clash Core", "Tun Mode": "Tun Mode", "Service Mode": "Service Mode", @@ -78,8 +81,11 @@ "Theme Mode": "Theme Mode", "Theme Blur": "Theme Blur", "Theme Setting": "Theme Setting", + "Layout Setting": "Layout Setting", + "Miscellaneous": "Miscellaneous", "Hotkey Setting": "Hotkey Setting", "Traffic Graph": "Traffic Graph", + "Memory Usage": "Memory Usage", "Language": "Language", "Open App Dir": "Open App Dir", "Open Core Dir": "Open Core Dir", @@ -97,6 +103,10 @@ "Save": "Save", "Cancel": "Cancel", + "Default": "Default", + "Download Speed": "Download Speed", + "Upload Speed": "Upload Speed", + "clash_mode_rule": "Rule Mode", "clash_mode_global": "Global Mode", "clash_mode_direct": "Direct Mode", diff --git a/src/locales/zh.json b/src/locales/zh.json index 950ebf7..65a1340 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -80,10 +80,12 @@ "Current System Proxy": "当前系统代理", "Theme Mode": "主题模式", "Theme Blur": "背景模糊", - "Miscellaneous": "杂项设置", "Theme Setting": "主题设置", + "Layout Setting": "界面设置", + "Miscellaneous": "杂项设置", "Hotkey Setting": "热键设置", "Traffic Graph": "流量图显", + "Memory Usage": "内存使用", "Language": "语言设置", "Open App Dir": "应用目录", "Open Core Dir": "内核目录", diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 819c5d0..57e3316 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -159,6 +159,7 @@ interface IVergeConfig { theme_mode?: "light" | "dark" | "system"; theme_blur?: boolean; traffic_graph?: boolean; + enable_memory_usage?: boolean; enable_tun_mode?: boolean; enable_auto_launch?: boolean; enable_service_mode?: boolean;