Ver código fonte

feat: display rules list

MystiPanda 11 meses atrás
pai
commit
9089c30d57

+ 63 - 0
src/components/profile/rule-item.tsx

@@ -0,0 +1,63 @@
+import {
+  Box,
+  Divider,
+  IconButton,
+  ListItem,
+  ListItemText,
+  Typography,
+  alpha,
+} from "@mui/material";
+import { DeleteForeverRounded, UndoRounded } from "@mui/icons-material";
+interface Props {
+  type: "prepend" | "original" | "delete" | "append";
+  ruleRaw: string;
+  onDelete: () => void;
+}
+
+export const RuleItem = (props: Props) => {
+  let { type, ruleRaw, onDelete } = props;
+  const rule = ruleRaw.replace(",no-resolve", "").split(",");
+
+  return (
+    <ListItem
+      sx={({ palette }) => ({
+        p: 0,
+        borderRadius: "10px",
+        border: "solid 2px",
+        borderColor:
+          type === "original"
+            ? "var(--divider-color)"
+            : type === "delete"
+            ? alpha(palette.error.main, 0.5)
+            : alpha(palette.success.main, 0.5),
+        mb: 1,
+      })}
+    >
+      <ListItemText
+        sx={{ px: 1 }}
+        primary={
+          <>
+            <Typography
+              sx={{ textDecoration: type === "delete" ? "line-through" : "" }}
+              variant="h6"
+              component="span"
+              noWrap
+            >
+              {rule.length === 3 ? rule[1] : "-"}
+            </Typography>
+          </>
+        }
+        secondary={
+          <Box sx={{ display: "flex", justifyContent: "space-between" }}>
+            <Box>{rule[0]}</Box>
+            <Box>{rule.length === 3 ? rule[2] : rule[1]}</Box>
+          </Box>
+        }
+      />
+      <Divider orientation="vertical" flexItem />
+      <IconButton size="small" color="inherit" onClick={onDelete}>
+        {type === "delete" ? <UndoRounded /> : <DeleteForeverRounded />}
+      </IconButton>
+    </ListItem>
+  );
+};

+ 86 - 96
src/components/profile/rules-editor-viewer.tsx

@@ -16,11 +16,11 @@ import {
   TextField,
   styled,
 } from "@mui/material";
-import { useThemeMode } from "@/services/states";
+
 import { readProfileFile, saveProfileFile } from "@/services/cmds";
 import { Notice, Switch } from "@/components/base";
 import getSystem from "@/utils/get-system";
-import MonacoEditor from "react-monaco-editor";
+import { RuleItem } from "@/components/profile/rule-item";
 
 interface Props {
   profileUid: string;
@@ -119,10 +119,7 @@ export const RulesEditorViewer = (props: Props) => {
   const { title, profileUid, property, open, onClose, onChange } = props;
   const { t } = useTranslation();
 
-  const themeMode = useThemeMode();
   const [prevData, setPrevData] = useState("");
-  const [currData, setCurrData] = useState("");
-  const [rule, setRule] = useState("");
   const [ruleType, setRuleType] =
     useState<(typeof RuleTypeList)[number]>("DOMAIN");
   const [ruleContent, setRuleContent] = useState("");
@@ -131,28 +128,17 @@ export const RulesEditorViewer = (props: Props) => {
   const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
   const [ruleList, setRuleList] = useState<string[]>([]);
 
-  const editorOptions = {
-    tabSize: 2,
-    minimap: { enabled: false },
-    mouseWheelZoom: true,
-    quickSuggestions: {
-      strings: true,
-      comments: true,
-      other: true,
-    },
-    padding: {
-      top: 33,
-    },
-    fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
-      getSystem() === "windows" ? ", twemoji mozilla" : ""
-    }`,
-    fontLigatures: true,
-    smoothScrolling: true,
-  };
+  const [prependSeq, setPrependSeq] = useState<string[]>([]);
+  const [appendSeq, setAppendSeq] = useState<string[]>([]);
+  const [deleteSeq, setDeleteSeq] = useState<string[]>([]);
 
   const fetchContent = async () => {
     let data = await readProfileFile(property);
-    setCurrData(data);
+    let obj = yaml.load(data) as { prepend: []; append: []; delete: [] };
+
+    setPrependSeq(obj.prepend || []);
+    setAppendSeq(obj.append || []);
+    setDeleteSeq(obj.delete || []);
     setPrevData(data);
   };
 
@@ -170,42 +156,6 @@ export const RulesEditorViewer = (props: Props) => {
     setRuleList(obj.rules);
   };
 
-  const addSeq = async (method: "prepend" | "append" | "delete") => {
-    let obj = yaml.load(currData) as ISeqProfileConfig;
-    if (!obj.prepend) {
-      obj = { prepend: [], append: [], delete: [] };
-    }
-    switch (method) {
-      case "append": {
-        obj.append.push(
-          `${ruleType}${
-            ruleType === "MATCH" ? "" : "," + ruleContent
-          },${proxyPolicy}${
-            NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
-          }`
-        );
-        break;
-      }
-      case "prepend": {
-        obj.prepend.push(
-          `${ruleType}${
-            ruleType === "MATCH" ? "" : "," + ruleContent
-          },${proxyPolicy}${
-            NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
-          }`
-        );
-        break;
-      }
-      case "delete": {
-        obj.delete.push(rule);
-        break;
-      }
-    }
-    let raw = yaml.dump(obj);
-
-    setCurrData(raw);
-  };
-
   useEffect(() => {
     fetchContent();
     fetchProfile();
@@ -213,6 +163,11 @@ export const RulesEditorViewer = (props: Props) => {
 
   const onSave = useLockFn(async () => {
     try {
+      let currData = yaml.dump({
+        prepend: prependSeq,
+        append: appendSeq,
+        delete: deleteSeq,
+      });
       await saveProfileFile(property, currData);
       onChange?.(prevData, currData);
       onClose();
@@ -288,7 +243,15 @@ export const RulesEditorViewer = (props: Props) => {
               fullWidth
               variant="contained"
               onClick={() => {
-                addSeq("prepend");
+                let raw = `${ruleType}${
+                  ruleType === "MATCH" ? "" : "," + ruleContent
+                },${proxyPolicy}${
+                  NoResolveList.includes(ruleType) && noResolve
+                    ? ",no-resolve"
+                    : ""
+                }`;
+                if (prependSeq.includes(raw)) return;
+                setPrependSeq([...prependSeq, raw]);
               }}
             >
               {t("Add Prepend Rule")}
@@ -299,55 +262,82 @@ export const RulesEditorViewer = (props: Props) => {
               fullWidth
               variant="contained"
               onClick={() => {
-                addSeq("append");
+                let raw = `${ruleType}${
+                  ruleType === "MATCH" ? "" : "," + ruleContent
+                },${proxyPolicy}${
+                  NoResolveList.includes(ruleType) && noResolve
+                    ? ",no-resolve"
+                    : ""
+                }`;
+                if (appendSeq.includes(raw)) return;
+                setAppendSeq([...appendSeq, raw]);
               }}
             >
               {t("Add Append Rule")}
             </Button>
           </Item>
-          <Item>
-            <Autocomplete
-              fullWidth
-              size="small"
-              sx={{ minWidth: "240px" }}
-              value={rule}
-              options={ruleList}
-              onChange={(_, v) => {
-                if (v) setRule(v);
-              }}
-              renderInput={(params) => <TextField {...params} />}
-            />
-          </Item>
-          <Item>
-            <Button
-              fullWidth
-              variant="contained"
-              onClick={() => {
-                addSeq("delete");
-              }}
-            >
-              {t("Delete Rule")}
-            </Button>
-          </Item>
         </div>
         <div
           style={{
             display: "inline-block",
             width: "50%",
             height: "100%",
+            overflow: "auto",
             marginLeft: "10px",
           }}
         >
-          <MonacoEditor
-            language="yaml"
-            theme={themeMode === "light" ? "vs" : "vs-dark"}
-            height="100%"
-            value={currData}
-            onChange={(value, _) => {
-              if (value) setCurrData(value);
-            }}
-            options={editorOptions}
-          />
+          {prependSeq.length > 0 && (
+            <List sx={{ borderBottom: "solid 1px var(--divider-color)" }}>
+              {prependSeq.map((item, index) => {
+                return (
+                  <RuleItem
+                    key={`${item}-${index}`}
+                    type="prepend"
+                    ruleRaw={item}
+                    onDelete={() => {
+                      setPrependSeq(prependSeq.filter((v) => v !== item));
+                    }}
+                  />
+                );
+              })}
+            </List>
+          )}
+
+          <List>
+            {ruleList.map((item, index) => {
+              return (
+                <RuleItem
+                  key={`${item}-${index}`}
+                  type={deleteSeq.includes(item) ? "delete" : "original"}
+                  ruleRaw={item}
+                  onDelete={() => {
+                    if (deleteSeq.includes(item)) {
+                      setDeleteSeq(deleteSeq.filter((v) => v !== item));
+                    } else {
+                      setDeleteSeq([...deleteSeq, item]);
+                    }
+                  }}
+                />
+              );
+            })}
+          </List>
+
+          {appendSeq.length > 0 && (
+            <List sx={{ borderTop: "solid 1px var(--divider-color)" }}>
+              {appendSeq.map((item, index) => {
+                return (
+                  <RuleItem
+                    key={`${item}-${index}`}
+                    type="append"
+                    ruleRaw={item}
+                    onDelete={() => {
+                      setAppendSeq(appendSeq.filter((v) => v !== item));
+                    }}
+                  />
+                );
+              })}
+            </List>
+          )}
         </div>
       </DialogContent>