From b23d3f7c8b6cfadfee507df016a94ec4bc3bb3ef Mon Sep 17 00:00:00 2001
From: GyDi <segydi@foxmail.com>
Date: Sun, 18 Sep 2022 23:19:02 +0800
Subject: [PATCH] feat: add rule page

---
 src/components/rule/rule-item.tsx | 72 +++++++++++++++++++++++++++++++
 src/locales/en.json               |  2 +
 src/locales/zh.json               |  2 +
 src/pages/_routers.tsx            |  6 +++
 src/pages/rules.tsx               | 65 ++++++++++++++++++++++++++++
 src/services/api.ts               |  3 +-
 6 files changed, 149 insertions(+), 1 deletion(-)
 create mode 100644 src/components/rule/rule-item.tsx
 create mode 100644 src/pages/rules.tsx

diff --git a/src/components/rule/rule-item.tsx b/src/components/rule/rule-item.tsx
new file mode 100644
index 0000000..b663e86
--- /dev/null
+++ b/src/components/rule/rule-item.tsx
@@ -0,0 +1,72 @@
+import { styled, Box, Typography } from "@mui/material";
+
+const Item = styled(Box)(({ theme }) => ({
+  display: "flex",
+  padding: "6px 16px",
+  color: theme.palette.text.primary,
+  marginBottom: "6px",
+}));
+
+const COLOR = [
+  "primary",
+  "secondary",
+  "info",
+  "warning",
+  "error",
+  "success",
+  "text",
+];
+
+interface Props {
+  index: number;
+  value: ApiType.RuleItem;
+}
+
+const parseColor = (text: string) => {
+  let sum = 0;
+  for (let i = 0; i < text.length; i++) {
+    sum += text.charCodeAt(i);
+  }
+  return COLOR[sum % COLOR.length];
+};
+
+const RuleItem = (props: Props) => {
+  const { index, value } = props;
+
+  return (
+    <Item>
+      <Typography
+        color="text.secondary"
+        variant="body2"
+        sx={{ lineHeight: 2, minWidth: 30, mr: 2.25, textAlign: "center" }}
+      >
+        {index}
+      </Typography>
+
+      <Box>
+        <Typography component="h6" variant="subtitle1" color="text.primary">
+          {value.payload || "-"}
+        </Typography>
+
+        <Typography
+          component="span"
+          variant="body2"
+          color="text.secondary"
+          sx={{ mr: 3, minWidth: 120, display: "inline-block" }}
+        >
+          {value.type}
+        </Typography>
+
+        <Typography
+          component="span"
+          variant="body2"
+          color={parseColor(value.proxy)}
+        >
+          {value.proxy}
+        </Typography>
+      </Box>
+    </Item>
+  );
+};
+
+export default RuleItem;
diff --git a/src/locales/en.json b/src/locales/en.json
index cbaab63..6819cbe 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -3,6 +3,7 @@
   "Label-Profiles": "Profiles",
   "Label-Connections": "Connections",
   "Label-Logs": "Logs",
+  "Label-Rules": "Rules",
   "Label-Settings": "Settings",
 
   "Connections": "Connections",
@@ -73,6 +74,7 @@
   "Theme Mode": "Theme Mode",
   "Theme Blur": "Theme Blur",
   "Theme Setting": "Theme Setting",
+  "Hotkey Setting": "Hotkey Setting",
   "Traffic Graph": "Traffic Graph",
   "Language": "Language",
   "Open App Dir": "Open App Dir",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index 05cd83b..d4d0142 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -3,6 +3,7 @@
   "Label-Profiles": "配 置",
   "Label-Connections": "连 接",
   "Label-Logs": "日 志",
+  "Label-Rules": "规 则",
   "Label-Settings": "设 置",
 
   "Connections": "连接",
@@ -73,6 +74,7 @@
   "Theme Mode": "主题模式",
   "Theme Blur": "背景模糊",
   "Theme Setting": "主题设置",
+  "Hotkey Setting": "热键设置",
   "Traffic Graph": "流量图显",
   "Language": "语言设置",
   "Open App Dir": "应用目录",
diff --git a/src/pages/_routers.tsx b/src/pages/_routers.tsx
index 9ab4620..bb3d7c9 100644
--- a/src/pages/_routers.tsx
+++ b/src/pages/_routers.tsx
@@ -3,6 +3,7 @@ import ProxiesPage from "./proxies";
 import ProfilesPage from "./profiles";
 import SettingsPage from "./settings";
 import ConnectionsPage from "./connections";
+import RulesPage from "./rules";
 
 export const routers = [
   {
@@ -15,6 +16,11 @@ export const routers = [
     link: "/profile",
     ele: ProfilesPage,
   },
+  {
+    label: "Label-Rules",
+    link: "/rules",
+    ele: RulesPage,
+  },
   {
     label: "Label-Connections",
     link: "/connections",
diff --git a/src/pages/rules.tsx b/src/pages/rules.tsx
new file mode 100644
index 0000000..6397d08
--- /dev/null
+++ b/src/pages/rules.tsx
@@ -0,0 +1,65 @@
+import useSWR from "swr";
+import { useState, useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { Virtuoso } from "react-virtuoso";
+import { Box, Button, MenuItem, Paper, Select, TextField } from "@mui/material";
+import { getRules } from "@/services/api";
+import BasePage from "@/components/base/base-page";
+import BaseEmpty from "@/components/base/base-empty";
+import RuleItem from "@/components/rule/rule-item";
+
+const RulesPage = () => {
+  const { t } = useTranslation();
+  const { data = [] } = useSWR("getRules", getRules);
+
+  const [filterText, setFilterText] = useState("");
+
+  const rules = useMemo(() => {
+    return data.filter((each) => each.payload.includes(filterText));
+  }, [data, filterText]);
+
+  return (
+    <BasePage title={t("Rules")} contentStyle={{ height: "100%" }}>
+      <Paper sx={{ boxSizing: "border-box", boxShadow: 2, height: "100%" }}>
+        <Box
+          sx={{
+            pt: 1,
+            mb: 0.5,
+            mx: "12px",
+            height: "36px",
+            display: "flex",
+            alignItems: "center",
+          }}
+        >
+          <TextField
+            hiddenLabel
+            fullWidth
+            size="small"
+            autoComplete="off"
+            variant="outlined"
+            placeholder={t("Filter conditions")}
+            value={filterText}
+            onChange={(e) => setFilterText(e.target.value)}
+            sx={{ input: { py: 0.65, px: 1.25 } }}
+          />
+        </Box>
+
+        <Box height="calc(100% - 50px)">
+          {rules.length > 0 ? (
+            <Virtuoso
+              data={rules}
+              itemContent={(index, item) => (
+                <RuleItem index={index + 1} value={item} />
+              )}
+              followOutput={"smooth"}
+            />
+          ) : (
+            <BaseEmpty text="No Rules" />
+          )}
+        </Box>
+      </Paper>
+    </BasePage>
+  );
+};
+
+export default RulesPage;
diff --git a/src/services/api.ts b/src/services/api.ts
index 4794cf7..96e9128 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -62,7 +62,8 @@ export async function updateConfigs(config: Partial<ApiType.ConfigData>) {
 /// Get current rules
 export async function getRules() {
   const instance = await getAxios();
-  return instance.get("/rules") as Promise<ApiType.RuleItem[]>;
+  const response = await instance.get<any, any>("/rules");
+  return response?.rules as ApiType.RuleItem[];
 }
 
 /// Get Proxy delay