From c7232522eeb363d721c7ccb9fc4ed12afabdcd3e Mon Sep 17 00:00:00 2001 From: GyDi Date: Sat, 26 Feb 2022 17:39:08 +0800 Subject: [PATCH] feat: refactor proxy page --- src/components/proxy/proxy-global.tsx | 98 ++++++++++++++++++++++++ src/components/proxy/proxy-group.tsx | 21 ++---- src/pages/proxies.tsx | 104 ++++++++------------------ src/services/api.ts | 24 ++++-- 4 files changed, 151 insertions(+), 96 deletions(-) create mode 100644 src/components/proxy/proxy-global.tsx diff --git a/src/components/proxy/proxy-global.tsx b/src/components/proxy/proxy-global.tsx new file mode 100644 index 0000000..ab26799 --- /dev/null +++ b/src/components/proxy/proxy-global.tsx @@ -0,0 +1,98 @@ +import { useEffect, useRef, useState } from "react"; +import { useSWRConfig } from "swr"; +import { useLockFn } from "ahooks"; +import { Virtuoso } from "react-virtuoso"; +import { Box, IconButton } from "@mui/material"; +import { MyLocationRounded, NetworkCheckRounded } from "@mui/icons-material"; +import { ApiType } from "../../services/types"; +import { updateProxy } from "../../services/api"; +import delayManager from "../../services/delay"; +import ProxyItem from "./proxy-item"; + +interface Props { + groupName: string; + curProxy?: string; + proxies: ApiType.ProxyItem[]; +} + +const ProxyGlobal = (props: Props) => { + const { groupName, curProxy, proxies } = props; + + const { mutate } = useSWRConfig(); + const virtuosoRef = useRef(); + const [now, setNow] = useState(curProxy || "DIRECT"); + + const onChangeProxy = useLockFn(async (name: string) => { + await updateProxy("GLOBAL", name); + mutate("getProxies"); + setNow(name); + }); + + const onLocation = (smooth = true) => { + const index = proxies.findIndex((p) => p.name === now); + + if (index >= 0) { + virtuosoRef.current?.scrollToIndex?.({ + index, + align: "center", + behavior: smooth ? "smooth" : "auto", + }); + } + }; + + const onCheckAll = useLockFn(async () => { + // rerender quickly + if (proxies.length) setTimeout(() => mutate("getProxies"), 500); + + let names = proxies.map((p) => p.name); + while (names.length) { + const list = names.slice(0, 8); + names = names.slice(8); + + await Promise.all(list.map((n) => delayManager.checkDelay(n, groupName))); + + mutate("getProxies"); + } + }); + + useEffect(() => onLocation(false), [groupName]); + + useEffect(() => { + if (groupName === "DIRECT") setNow("DIRECT"); + if (groupName === "GLOBAL") setNow(curProxy || "DIRECT"); + }, [groupName, curProxy]); + + return ( + <> + + onLocation(true)} + > + + + + + + + + ( + + )} + /> + + ); +}; + +export default ProxyGlobal; diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx index b620c6d..e52b01c 100644 --- a/src/components/proxy/proxy-group.tsx +++ b/src/components/proxy/proxy-group.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { useSWRConfig } from "swr"; +import { useLockFn } from "ahooks"; import { Virtuoso } from "react-virtuoso"; import { Box, @@ -35,14 +36,10 @@ const ProxyGroup = ({ group }: Props) => { const virtuosoRef = useRef(); const proxies = group.all ?? []; - const selectLockRef = useRef(false); - const onSelect = async (name: string) => { + const onSelect = useLockFn(async (name: string) => { // Todo: support another proxy group type if (group.type !== "Selector") return; - if (selectLockRef.current) return; - selectLockRef.current = true; - const oldValue = now; try { setNow(name); @@ -50,8 +47,6 @@ const ProxyGroup = ({ group }: Props) => { } catch { setNow(oldValue); return; // do not update profile - } finally { - selectLockRef.current = false; } try { @@ -73,7 +68,7 @@ const ProxyGroup = ({ group }: Props) => { } catch (err) { console.error(err); } - }; + }); const onLocation = (smooth = true) => { const index = proxies.findIndex((p) => p.name === now); @@ -87,11 +82,7 @@ const ProxyGroup = ({ group }: Props) => { } }; - const checkLockRef = useRef(false); - const onCheckAll = async () => { - if (checkLockRef.current) return; - checkLockRef.current = true; - + const onCheckAll = useLockFn(async () => { // rerender quickly if (proxies.length) setTimeout(() => mutate("getProxies"), 500); @@ -106,9 +97,7 @@ const ProxyGroup = ({ group }: Props) => { mutate("getProxies"); } - - checkLockRef.current = false; - }; + }); // auto scroll to current index useEffect(() => { diff --git a/src/pages/proxies.tsx b/src/pages/proxies.tsx index c95b447..20f6935 100644 --- a/src/pages/proxies.tsx +++ b/src/pages/proxies.tsx @@ -1,90 +1,51 @@ import useSWR, { useSWRConfig } from "swr"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { Virtuoso } from "react-virtuoso"; +import { useEffect } from "react"; +import { useLockFn } from "ahooks"; import { Button, ButtonGroup, List, Paper } from "@mui/material"; -import { getClashConfig, updateConfigs, updateProxy } from "../services/api"; +import { getClashConfig, updateConfigs } from "../services/api"; import { patchClashConfig } from "../services/cmds"; import { getProxies } from "../services/api"; import BasePage from "../components/base/base-page"; -import ProxyItem from "../components/proxy/proxy-item"; import ProxyGroup from "../components/proxy/proxy-group"; +import ProxyGlobal from "../components/proxy/proxy-global"; const ProxyPage = () => { const { mutate } = useSWRConfig(); const { data: proxiesData } = useSWR("getProxies", getProxies); const { data: clashConfig } = useSWR("getClashConfig", getClashConfig); - const [curProxy, setCurProxy] = useState("DIRECT"); - const curMode = clashConfig?.mode.toLowerCase(); - - // proxy groups - const { groups = [] } = proxiesData ?? {}; - // proxies and sorted - const filterProxies = useMemo(() => { - if (!proxiesData?.proxies) return []; - - const list = Object.values(proxiesData.proxies); - const retList = list.filter( - (p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT" - ); - const direct = list.filter((p) => p.name === "DIRECT"); - const reject = list.filter((p) => p.name === "REJECT"); - - return direct.concat(retList).concat(reject); - }, [proxiesData]); const modeList = ["rule", "global", "direct"]; - const asGroup = curMode === "rule" && groups.length; + const curMode = clashConfig?.mode.toLowerCase() ?? "direct"; + const { groups = [], proxies = [] } = proxiesData ?? {}; // make sure that fetch the proxies successfully useEffect(() => { if ( (curMode === "rule" && !groups.length) || - (curMode === "global" && filterProxies.length < 4) + (curMode === "global" && proxies.length < 2) ) { setTimeout(() => mutate("getProxies"), 500); } - }, [groups, filterProxies, curMode]); + }, [groups, proxies, curMode]); - // update the current proxy - useEffect(() => { - if (curMode === "direct") setCurProxy("DIRECT"); - if (curMode === "global") { - const globalNow = proxiesData?.proxies?.GLOBAL?.now; - setCurProxy(globalNow || "DIRECT"); - } - }, [curMode, proxiesData]); - - const changeLockRef = useRef(false); - const onChangeMode = async (mode: string) => { - if (changeLockRef.current) return; - changeLockRef.current = true; - - try { - // switch rapidly - await updateConfigs({ mode }); - await patchClashConfig({ mode }); - mutate("getClashConfig"); - } finally { - changeLockRef.current = false; - } - }; - - const onChangeProxy = async (name: string) => { - if (curMode !== "global") return; - await updateProxy("GLOBAL", name); - setCurProxy(name); - }; + const onChangeMode = useLockFn(async (mode: string) => { + // switch rapidly + await updateConfigs({ mode }); + await patchClashConfig({ mode }); + mutate("getClashConfig"); + }); // difference style - const pageStyle = asGroup ? {} : { height: "100%" }; - const paperStyle: any = asGroup + const showGroup = curMode === "rule" && !!groups.length; + const pageStyle = showGroup ? {} : { height: "100%" }; + const paperStyle: any = showGroup ? { mb: 0.5 } : { py: 1, height: "100%", boxSizing: "border-box" }; return ( {modeList.map((mode) => ( @@ -101,26 +62,25 @@ const ProxyPage = () => { } > - {asGroup ? ( + {curMode === "rule" && !!groups.length && ( {groups.map((group) => ( ))} - ) : ( - // virtual list - ( - - )} + )} + {((curMode === "rule" && !groups.length) || curMode === "global") && ( + + )} + {curMode === "direct" && ( + )} diff --git a/src/services/api.ts b/src/services/api.ts index 844c5e3..751e56f 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -84,33 +84,41 @@ export async function updateProxy(group: string, proxy: string) { export async function getProxies() { const instance = await getAxios(); const response = await instance.get("/proxies"); - const proxies = (response?.proxies ?? {}) as Record< + const records = (response?.proxies ?? {}) as Record< string, ApiType.ProxyItem >; - const global = proxies["GLOBAL"]; + const global = records["GLOBAL"]; + const direct = records["DIRECT"]; + const reject = records["REJECT"]; const order = global?.all; let groups: ApiType.ProxyGroupItem[] = []; if (order) { groups = order - .filter((name) => proxies[name]?.all) - .map((name) => proxies[name]) + .filter((name) => records[name]?.all) + .map((name) => records[name]) .map((each) => ({ ...each, - all: each.all!.map((item) => proxies[item]), + all: each.all!.map((item) => records[item]), })); } else { - groups = Object.values(proxies) + groups = Object.values(records) .filter((each) => each.name !== "GLOBAL" && each.all) .map((each) => ({ ...each, - all: each.all!.map((item) => proxies[item]), + all: each.all!.map((item) => records[item]), })); groups.sort((a, b) => b.name.localeCompare(a.name)); } - return { global, groups, proxies }; + const proxies = [direct, reject].concat( + Object.values(records).filter( + (p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT" + ) + ); + + return { global, direct, groups, records, proxies }; }