From 28d3691e0b7f7155a3bf322b4baf1d68ba123627 Mon Sep 17 00:00:00 2001 From: GyDi Date: Wed, 23 Nov 2022 17:44:40 +0800 Subject: [PATCH] feat: add use clash hook --- src/components/layout/layout-traffic.tsx | 51 ++++-------- src/components/layout/traffic-graph.tsx | 44 +++++----- src/components/layout/use-log-setup.ts | 36 +++----- .../setting/mods/clash-port-viewer.tsx | 38 +++------ .../setting/mods/controller-viewer.tsx | 16 ++-- src/components/setting/mods/web-ui-viewer.tsx | 11 +-- src/components/setting/setting-clash.tsx | 29 ++----- src/hooks/use-clash.ts | 83 +++++++++++++++++++ src/pages/connections.tsx | 69 +++++++-------- src/services/api.ts | 1 + src/services/states.ts | 5 -- src/services/types.d.ts | 4 +- 12 files changed, 196 insertions(+), 191 deletions(-) create mode 100644 src/hooks/use-clash.ts diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 296f677..7d8787d 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -1,57 +1,40 @@ import { useEffect, useRef, useState } from "react"; -import { useRecoilValue } from "recoil"; import { Box, Typography } from "@mui/material"; import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; -import { listen } from "@tauri-apps/api/event"; -import { getInformation } from "@/services/api"; -import { atomClashPort } from "@/services/states"; +import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; -import TrafficGraph from "./traffic-graph"; -import useLogSetup from "./use-log-setup"; +import { TrafficGraph, type TrafficRef } from "./traffic-graph"; +import { useLogSetup } from "./use-log-setup"; import parseTraffic from "@/utils/parse-traffic"; // setup the traffic const LayoutTraffic = () => { - const portValue = useRecoilValue(atomClashPort); - const [traffic, setTraffic] = useState({ up: 0, down: 0 }); - const [refresh, setRefresh] = useState({}); - - const trafficRef = useRef(); + const { clashInfo } = useClashInfo(); // 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 }); + // setup log ws during layout useLogSetup(); useEffect(() => { - // should reconnect the traffic ws - const unlisten = listen("verge://refresh-clash-config", () => - setRefresh({}) - ); + if (!clashInfo) return; - return () => { - unlisten.then((fn) => fn()); - }; - }, []); + const { server = "", secret = "" } = clashInfo; + const ws = new WebSocket(`ws://${server}/traffic?token=${secret}`); - useEffect(() => { - let ws: WebSocket | null = null; - - getInformation().then((result) => { - const { server = "", secret = "" } = result; - ws = new WebSocket(`ws://${server}/traffic?token=${secret}`); - - ws.addEventListener("message", (event) => { - const data = JSON.parse(event.data) as ITrafficItem; - trafficRef.current?.appendData(data); - setTraffic(data); - }); + ws.addEventListener("message", (event) => { + const data = JSON.parse(event.data) as ITrafficItem; + trafficRef.current?.appendData(data); + setTraffic(data); }); return () => ws?.close(); - }, [portValue, refresh]); + }, [clashInfo]); const [up, upUnit] = parseTraffic(traffic.up); const [down, downUnit] = parseTraffic(traffic.down); @@ -60,7 +43,7 @@ const LayoutTraffic = () => { component: "span", color: "primary", textAlign: "center", - sx: { flex: "1 1 54px" }, + sx: { flex: "1 1 54px", userSelect: "none" }, }; const unitStyle: any = { component: "span", @@ -78,7 +61,7 @@ const LayoutTraffic = () => { > {trafficGraph && (
- +
)} diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx index 0a9d376..5c1a6b7 100644 --- a/src/components/layout/traffic-graph.tsx +++ b/src/components/layout/traffic-graph.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; import { useTheme } from "@mui/material"; const maxPoint = 30; @@ -16,34 +16,40 @@ const defaultList = Array(maxPoint + 2).fill({ up: 0, down: 0 }); type TrafficData = { up: number; down: number }; -interface Props { - instance: React.MutableRefObject<{ - appendData: (data: TrafficData) => void; - toggleStyle: () => void; - }>; +export interface TrafficRef { + appendData: (data: TrafficData) => void; + toggleStyle: () => void; } /** * draw the traffic graph */ -const TrafficGraph = (props: Props) => { - const { instance } = props; - +export const TrafficGraph = forwardRef((props, ref) => { const countRef = useRef(0); const styleRef = useRef(true); const listRef = useRef(defaultList); const canvasRef = useRef(null!); + const cacheRef = useRef(null); + const { palette } = useTheme(); + useImperativeHandle(ref, () => ({ + appendData: (data: TrafficData) => { + cacheRef.current = data; + }, + toggleStyle: () => { + styleRef.current = !styleRef.current; + }, + })); + useEffect(() => { let timer: any; - let cache: TrafficData | null = null; const zero = { up: 0, down: 0 }; const handleData = () => { - const data = cache ? cache : zero; - cache = null; + const data = cacheRef.current ? cacheRef.current : zero; + cacheRef.current = null; const list = listRef.current; if (list.length > maxPoint + 2) list.shift(); @@ -53,19 +59,9 @@ const TrafficGraph = (props: Props) => { timer = setTimeout(handleData, 1000); }; - instance.current = { - appendData: (data: TrafficData) => { - cache = data; - }, - toggleStyle: () => { - styleRef.current = !styleRef.current; - }, - }; - handleData(); return () => { - instance.current = null!; if (timer) clearTimeout(timer); }; }, []); @@ -196,6 +192,4 @@ const TrafficGraph = (props: Props) => { }, [palette]); return ; -}; - -export default TrafficGraph; +}); diff --git a/src/components/layout/use-log-setup.ts b/src/components/layout/use-log-setup.ts index a563393..d7f06a1 100644 --- a/src/components/layout/use-log-setup.ts +++ b/src/components/layout/use-log-setup.ts @@ -1,48 +1,36 @@ import dayjs from "dayjs"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useRecoilValue, useSetRecoilState } from "recoil"; -import { listen } from "@tauri-apps/api/event"; -import { getInformation } from "@/services/api"; import { getClashLogs } from "@/services/cmds"; +import { useClashInfo } from "@/hooks/use-clash"; import { atomEnableLog, atomLogData } from "@/services/states"; const MAX_LOG_NUM = 1000; // setup the log websocket -export default function useLogSetup() { - const [refresh, setRefresh] = useState({}); +export const useLogSetup = () => { + const { clashInfo } = useClashInfo(); const enableLog = useRecoilValue(atomEnableLog); const setLogData = useSetRecoilState(atomLogData); useEffect(() => { - if (!enableLog) return; + if (!enableLog || !clashInfo) return; getClashLogs().then(setLogData); - const handler = (event: MessageEvent) => { + const { server = "", secret = "" } = clashInfo; + const ws = new WebSocket(`ws://${server}/logs?token=${secret}`); + + ws.addEventListener("message", (event) => { const data = JSON.parse(event.data) as ILogItem; const time = dayjs().format("MM-DD HH:mm:ss"); setLogData((l) => { if (l.length >= MAX_LOG_NUM) l.shift(); return [...l, { ...data, time }]; }); - }; - - const ws = getInformation().then((info) => { - const { server = "", secret = "" } = info; - const ws = new WebSocket(`ws://${server}/logs?token=${secret}`); - ws.addEventListener("message", handler); - return ws; }); - const unlisten = listen("verge://refresh-clash-config", () => - setRefresh({}) - ); - - return () => { - ws.then((ws) => ws?.close()); - unlisten.then((fn) => fn()); - }; - }, [refresh, enableLog]); -} + return () => ws?.close(); + }, [clashInfo, enableLog]); +}; diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx index e09fe54..2079740 100644 --- a/src/components/setting/mods/clash-port-viewer.tsx +++ b/src/components/setting/mods/clash-port-viewer.tsx @@ -1,55 +1,37 @@ -import useSWR from "swr"; import { forwardRef, useImperativeHandle, useState } from "react"; -import { useSetRecoilState } from "recoil"; import { useTranslation } from "react-i18next"; import { useLockFn } from "ahooks"; import { List, ListItem, ListItemText, TextField } from "@mui/material"; -import { atomClashPort } from "@/services/states"; -import { getClashConfig } from "@/services/api"; -import { patchClashConfig } from "@/services/cmds"; +import { useClashInfo } from "@/hooks/use-clash"; import { BaseDialog, DialogRef, Notice } from "@/components/base"; export const ClashPortViewer = forwardRef((props, ref) => { const { t } = useTranslation(); - const { data: config, mutate: mutateClash } = useSWR( - "getClashConfig", - getClashConfig - ); + const { clashInfo, patchInfo } = useClashInfo(); const [open, setOpen] = useState(false); - const [port, setPort] = useState(config?.["mixed-port"] ?? 9090); - - const setGlobalClashPort = useSetRecoilState(atomClashPort); + const [port, setPort] = useState(clashInfo?.port ?? 7890); useImperativeHandle(ref, () => ({ open: () => { - if (config?.["mixed-port"]) { - setPort(config["mixed-port"]); - } + if (clashInfo?.port) setPort(clashInfo?.port); setOpen(true); }, close: () => setOpen(false), })); const onSave = useLockFn(async () => { - if (port < 1000) { - return Notice.error("The port should not < 1000"); + if (port === clashInfo?.port) { + setOpen(false); + return; } - if (port > 65536) { - return Notice.error("The port should not > 65536"); - } - - setOpen(false); - if (port === config?.["mixed-port"]) return; - try { - await patchClashConfig({ "mixed-port": port }); - setGlobalClashPort(port); + await patchInfo({ "mixed-port": port }); + setOpen(false); Notice.success("Change Clash port successfully!", 1000); - mutateClash(); } catch (err: any) { - Notice.error(err.message || err.toString(), 5000); + Notice.error(err.message || err.toString(), 4000); } }); diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx index d3d6a43..17205d9 100644 --- a/src/components/setting/mods/controller-viewer.tsx +++ b/src/components/setting/mods/controller-viewer.tsx @@ -1,17 +1,16 @@ -import useSWR from "swr"; import { forwardRef, useImperativeHandle, useState } from "react"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { List, ListItem, ListItemText, TextField } from "@mui/material"; -import { getClashInfo, patchClashConfig } from "@/services/cmds"; -import { getAxios } from "@/services/api"; +import { useClashInfo } from "@/hooks/use-clash"; import { BaseDialog, DialogRef, Notice } from "@/components/base"; export const ControllerViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); - const { data: clashInfo, mutate } = useSWR("getClashInfo", getClashInfo); + const { clashInfo, patchInfo } = useClashInfo(); + const [controller, setController] = useState(clashInfo?.server || ""); const [secret, setSecret] = useState(clashInfo?.secret || ""); @@ -26,14 +25,11 @@ export const ControllerViewer = forwardRef((props, ref) => { const onSave = useLockFn(async () => { try { - await patchClashConfig({ "external-controller": controller, secret }); - mutate(); - // 刷新接口 - getAxios(true); + await patchInfo({ "external-controller": controller, secret }); Notice.success("Change Clash Config successfully!", 1000); setOpen(false); - } catch (err) { - console.log(err); + } catch (err: any) { + Notice.error(err.message || err.toString(), 4000); } }); diff --git a/src/components/setting/mods/web-ui-viewer.tsx b/src/components/setting/mods/web-ui-viewer.tsx index bb47a99..ae1bf31 100644 --- a/src/components/setting/mods/web-ui-viewer.tsx +++ b/src/components/setting/mods/web-ui-viewer.tsx @@ -1,23 +1,22 @@ -import useSWR from "swr"; import { forwardRef, useImperativeHandle, useState } from "react"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { Button, Box, Typography } from "@mui/material"; import { useVerge } from "@/hooks/use-verge"; -import { getClashInfo, openWebUrl } from "@/services/cmds"; +import { openWebUrl } from "@/services/cmds"; import { BaseDialog, BaseEmpty, DialogRef, Notice } from "@/components/base"; +import { useClashInfo } from "@/hooks/use-clash"; import { WebUIItem } from "./web-ui-item"; export const WebUIViewer = forwardRef((props, ref) => { const { t } = useTranslation(); + const { clashInfo } = useClashInfo(); const { verge, patchVerge, mutateVerge } = useVerge(); const [open, setOpen] = useState(false); const [editing, setEditing] = useState(false); - const { data: clashInfo } = useSWR("getClashInfo", getClashInfo); - useImperativeHandle(ref, () => ({ open: () => setOpen(true), close: () => setOpen(false), @@ -53,9 +52,7 @@ export const WebUIViewer = forwardRef((props, ref) => { if (url.includes("%port") || url.includes("%secret")) { if (!clashInfo) throw new Error("failed to get clash info"); if (!clashInfo.server?.includes(":")) { - throw new Error( - `failed to parse server with status ${clashInfo.status}` - ); + throw new Error(`failed to parse the server "${clashInfo.server}"`); } const port = clashInfo.server diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index caaf5de..a3af397 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -1,4 +1,3 @@ -import useSWR from "swr"; import { useRef } from "react"; import { useTranslation } from "react-i18next"; import { @@ -10,9 +9,8 @@ import { IconButton, } from "@mui/material"; import { ArrowForward } from "@mui/icons-material"; -import { patchClashConfig } from "@/services/cmds"; -import { getClashConfig, getVersion, updateConfigs } from "@/services/api"; import { DialogRef } from "@/components/base"; +import { useClash } from "@/hooks/use-clash"; import { GuardState } from "./mods/guard-state"; import { CoreSwitch } from "./mods/core-switch"; import { WebUIViewer } from "./mods/web-ui-viewer"; @@ -28,18 +26,14 @@ interface Props { const SettingClash = ({ onError }: Props) => { const { t } = useTranslation(); - const { data: clashConfig, mutate: mutateClash } = useSWR( - "getClashConfig", - getClashConfig - ); - const { data: versionData } = useSWR("getVersion", getVersion); + const { clash, version, mutateClash, patchClash } = useClash(); const { ipv6, "allow-lan": allowLan, "log-level": logLevel, "mixed-port": mixedPort, - } = clashConfig ?? {}; + } = clash ?? {}; const webRef = useRef(null); const fieldRef = useRef(null); @@ -50,15 +44,6 @@ const SettingClash = ({ onError }: Props) => { const onChangeData = (patch: Partial) => { mutateClash((old) => ({ ...(old! || {}), ...patch }), false); }; - const onUpdateData = async (patch: Partial) => { - await updateConfigs(patch); - await patchClashConfig(patch); - }; - - // get clash core version - const clashVer = versionData?.premium - ? `${versionData.version} Premium` - : versionData?.version || "-"; return ( @@ -74,7 +59,7 @@ const SettingClash = ({ onError }: Props) => { onCatch={onError} onFormat={onSwitchFormat} onChange={(e) => onChangeData({ "allow-lan": e })} - onGuard={(e) => onUpdateData({ "allow-lan": e })} + onGuard={(e) => patchClash({ "allow-lan": e })} > @@ -87,7 +72,7 @@ const SettingClash = ({ onError }: Props) => { onCatch={onError} onFormat={onSwitchFormat} onChange={(e) => onChangeData({ ipv6: e })} - onGuard={(e) => onUpdateData({ ipv6: e })} + onGuard={(e) => patchClash({ ipv6: e })} > @@ -100,7 +85,7 @@ const SettingClash = ({ onError }: Props) => { onCatch={onError} onFormat={(e: any) => e.target.value} onChange={(e) => onChangeData({ "log-level": e })} - onGuard={(e) => onUpdateData({ "log-level": e })} + onGuard={(e) => patchClash({ "log-level": e })} >