From d0b87fd7c39de00bec9bee489e9ca29888681d3b Mon Sep 17 00:00:00 2001
From: GyDi <segydi@foxmail.com>
Date: Fri, 21 Jan 2022 02:32:23 +0800
Subject: [PATCH] feat: enable change mixed port

---
 src/components/setting/guard-state.tsx   |  4 +-
 src/components/setting/setting-clash.tsx | 48 ++++++++++++++++++++----
 src/components/traffic.tsx               |  5 ++-
 src/states/setting.ts                    |  5 +++
 4 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/src/components/setting/guard-state.tsx b/src/components/setting/guard-state.tsx
index b2cff75..9b15aa9 100644
--- a/src/components/setting/guard-state.tsx
+++ b/src/components/setting/guard-state.tsx
@@ -7,7 +7,7 @@ interface Props<Value> {
   onChangeProps?: string;
   onChange?: (value: Value) => void;
   onFormat?: (...args: any[]) => Value;
-  onGuard?: (value: Value) => Promise<void>;
+  onGuard?: (value: Value, oldValue: Value) => Promise<void>;
   onCatch?: (error: Error) => void;
   children: ReactNode;
 }
@@ -41,7 +41,7 @@ function GuardState<T>(props: Props<T>) {
         const newValue = (onFormat as any)(...args);
         // 先在ui上响应操作
         onChange(newValue);
-        await onGuard(newValue);
+        await onGuard(newValue, oldValue!);
       } catch (err: any) {
         // 状态回退
         onChange(oldValue!);
diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx
index 766d3e3..eee2e11 100644
--- a/src/components/setting/setting-clash.tsx
+++ b/src/components/setting/setting-clash.tsx
@@ -1,3 +1,6 @@
+import { useEffect, useState } from "react";
+import { useDebounceFn } from "ahooks";
+import { useSetRecoilState } from "recoil";
 import useSWR, { useSWRConfig } from "swr";
 import {
   ListItemText,
@@ -9,11 +12,13 @@ import {
 import { getClashConfig, updateConfigs } from "../../services/api";
 import { SettingList, SettingItem } from "./setting";
 import { patchClashConfig } from "../../services/cmds";
+import { atomClashPort } from "../../states/setting";
 import { ApiType } from "../../services/types";
 import GuardState from "./guard-state";
+import Notice from "../notice";
 
 interface Props {
-  onError?: (err: Error) => void;
+  onError: (err: Error) => void;
 }
 
 const SettingClash = ({ onError }: Props) => {
@@ -24,9 +29,14 @@ const SettingClash = ({ onError }: Props) => {
     ipv6 = false,
     "allow-lan": allowLan = false,
     "log-level": logLevel = "silent",
-    "mixed-port": mixedPort = 7890,
+    "mixed-port": thePort = 0,
   } = clashConfig ?? {};
 
+  const setPort = useSetRecoilState(atomClashPort);
+  const [mixedPort, setMixedPort] = useState(thePort);
+
+  useEffect(() => setMixedPort(thePort), [thePort]);
+
   const onSwitchFormat = (_e: any, value: boolean) => value;
   const onChangeData = (patch: Partial<ApiType.ConfigData>) => {
     mutate("getClashConfig", { ...clashConfig, ...patch }, false);
@@ -36,6 +46,28 @@ const SettingClash = ({ onError }: Props) => {
     await patchClashConfig(patch);
   };
 
+  // restart clash when port is changed
+  const { run: onUpdatePort } = useDebounceFn(
+    async (port: number) => {
+      (async () => {
+        if (port < 1000) {
+          throw new Error("The port should not < 1000");
+        }
+        if (port > 65536) {
+          throw new Error("The port should not > 65536");
+        }
+        await patchClashConfig({ "mixed-port": port });
+        onChangeData({ "mixed-port": port });
+        setPort(port);
+        Notice.success("Change Clash port successfully!");
+      })().catch((err: any) => {
+        setMixedPort(thePort); // back to old port value
+        Notice.error(err.message ?? err.toString());
+      });
+    },
+    { wait: 1000 }
+  );
+
   return (
     <SettingList title="Clash Setting">
       <SettingItem>
@@ -87,12 +119,14 @@ const SettingClash = ({ onError }: Props) => {
 
       <SettingItem>
         <ListItemText primary="Mixed Port" />
-        <TextField
-          size="small"
+        <GuardState
           value={mixedPort!}
-          sx={{ width: 120 }}
-          disabled
-        />
+          onFormat={(e: any) => +e.target.value?.replace(/\D+/, "")}
+          onChange={setMixedPort}
+          onGuard={onUpdatePort}
+        >
+          <TextField autoComplete="off" size="small" sx={{ width: 120 }} />
+        </GuardState>
       </SettingItem>
     </SettingList>
   );
diff --git a/src/components/traffic.tsx b/src/components/traffic.tsx
index e0e7d91..52d5f61 100644
--- a/src/components/traffic.tsx
+++ b/src/components/traffic.tsx
@@ -1,11 +1,14 @@
 import { useEffect, useState } from "react";
+import { useRecoilValue } from "recoil";
 import { Box, Typography } from "@mui/material";
 import { ArrowDownward, ArrowUpward } from "@mui/icons-material";
 import { getInfomation } from "../services/api";
 import { ApiType } from "../services/types";
+import { atomClashPort } from "../states/setting";
 import parseTraffic from "../utils/parse-traffic";
 
 const Traffic = () => {
+  const portValue = useRecoilValue(atomClashPort);
   const [traffic, setTraffic] = useState({ up: 0, down: 0 });
 
   useEffect(() => {
@@ -21,7 +24,7 @@ const Traffic = () => {
     });
 
     return () => ws?.close();
-  }, []);
+  }, [portValue]);
 
   const [up, upUnit] = parseTraffic(traffic.up);
   const [down, downUnit] = parseTraffic(traffic.down);
diff --git a/src/states/setting.ts b/src/states/setting.ts
index e425e6e..65095e5 100644
--- a/src/states/setting.ts
+++ b/src/states/setting.ts
@@ -9,3 +9,8 @@ export const atomThemeBlur = atom<boolean>({
   key: "atomThemeBlur",
   default: false,
 });
+
+export const atomClashPort = atom<number>({
+  key: "atomClashPort",
+  default: 0,
+});
-- 
GitLab