From 6a8ffe164292088c49dd7fc88ea9b161ce031db7 Mon Sep 17 00:00:00 2001
From: GyDi <segydi@foxmail.com>
Date: Tue, 25 Jan 2022 01:51:44 +0800
Subject: [PATCH] feat: enable change proxy mode

---
 src/pages/proxies.tsx | 103 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 87 insertions(+), 16 deletions(-)

diff --git a/src/pages/proxies.tsx b/src/pages/proxies.tsx
index f491dd4..c5deb2e 100644
--- a/src/pages/proxies.tsx
+++ b/src/pages/proxies.tsx
@@ -1,6 +1,8 @@
 import useSWR, { useSWRConfig } from "swr";
-import { useEffect } from "react";
-import { List, Paper } from "@mui/material";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { Button, ButtonGroup, List, Paper } from "@mui/material";
+import { getClashConfig, updateConfigs, updateProxy } from "../services/api";
+import { patchClashConfig } from "../services/cmds";
 import { getProxies } from "../services/api";
 import BasePage from "../components/base-page";
 import ProxyItem from "../components/proxy-item";
@@ -9,34 +11,103 @@ import ProxyGroup from "../components/proxy-group";
 const ProxyPage = () => {
   const { mutate } = useSWRConfig();
   const { data: proxiesData } = useSWR("getProxies", getProxies);
-  const { groups = [], proxies = [] } = proxiesData ?? {};
+  const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
+  const [curProxy, setCurProxy] = useState<string>("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;
+
+  // make sure that fetch the proxies successfully
+  useEffect(() => {
+    if (
+      (curMode === "rule" && !groups.length) ||
+      (curMode === "global" && filterProxies.length < 4)
+    ) {
+      setTimeout(() => mutate("getProxies"), 500);
+    }
+  }, [groups, filterProxies, curMode]);
+
+  // update the current proxy
   useEffect(() => {
-    // fix the empty proxies on the first sight
-    // this bud only show on the build version
-    // call twice to avoid something unknown or the delay of the clash startup
-    setTimeout(() => mutate("getProxies"), 250);
-    setTimeout(() => mutate("getProxies"), 1000);
-  }, []);
+    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);
+  };
 
   return (
-    <BasePage title={groups.length ? "Proxy Groups" : "Proxies"}>
+    <BasePage
+      title={asGroup ? "Proxy Groups" : "Proxies"}
+      header={
+        <ButtonGroup size="small">
+          {modeList.map((mode) => (
+            <Button
+              key={mode}
+              variant={mode === curMode ? "contained" : "outlined"}
+              onClick={() => onChangeMode(mode)}
+              sx={{ textTransform: "capitalize" }}
+            >
+              {mode}
+            </Button>
+          ))}
+        </ButtonGroup>
+      }
+    >
       <Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 1 }}>
-        {groups.length > 0 && (
+        {asGroup ? (
           <List>
             {groups.map((group) => (
               <ProxyGroup key={group.name} group={group} />
             ))}
           </List>
-        )}
-
-        {!groups.length && (
+        ) : (
+          // todo: virtual list
           <List>
-            {Object.values(proxies).map((proxy) => (
+            {filterProxies.map((proxy) => (
               <ProxyItem
                 key={proxy.name}
                 proxy={proxy}
-                selected={false}
+                selected={proxy.name === curProxy}
+                onClick={onChangeProxy}
                 sx={{ py: 0, px: 2 }}
               />
             ))}
-- 
GitLab