From ea8f1c52f95f27606435779c00f99f56d26b0442 Mon Sep 17 00:00:00 2001
From: GyDi <segydi@foxmail.com>
Date: Sat, 8 Jan 2022 16:52:18 +0800
Subject: [PATCH] feat: profiles add menu and delete button

---
 src/components/profile-item.tsx | 218 +++++++++++++++++++-------------
 src/pages/profile.tsx           |  12 +-
 2 files changed, 138 insertions(+), 92 deletions(-)

diff --git a/src/components/profile-item.tsx b/src/components/profile-item.tsx
index fb6f021..6f1f70f 100644
--- a/src/components/profile-item.tsx
+++ b/src/components/profile-item.tsx
@@ -8,13 +8,15 @@ import {
   LinearProgress,
   IconButton,
   keyframes,
+  MenuItem,
+  Menu,
 } from "@mui/material";
 import { useSWRConfig } from "swr";
 import { RefreshRounded } from "@mui/icons-material";
 import { CmdType } from "../services/types";
+import { updateProfile, deleteProfile } from "../services/cmds";
 import parseTraffic from "../utils/parse-traffic";
 import relativeTime from "dayjs/plugin/relativeTime";
-import { updateProfile } from "../services/cmds";
 
 dayjs.extend(relativeTime);
 
@@ -46,6 +48,8 @@ const ProfileItemComp: React.FC<Props> = (props) => {
 
   const { mutate } = useSWRConfig();
   const [loading, setLoading] = useState(false);
+  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const [position, setPosition] = useState({ left: 0, top: 0 });
 
   const { name = "Profile", extra, updated = 0 } = itemData;
   const { upload = 0, download = 0, total = 0 } = extra ?? {};
@@ -55,6 +59,7 @@ const ProfileItemComp: React.FC<Props> = (props) => {
   const fromnow = updated > 0 ? dayjs(updated * 1000).fromNow() : "";
 
   const onUpdate = async () => {
+    setAnchorEl(null);
     if (loading) return;
     setLoading(true);
     try {
@@ -67,98 +72,135 @@ const ProfileItemComp: React.FC<Props> = (props) => {
     }
   };
 
-  return (
-    <Wrapper
-      sx={({ palette }) => {
-        const { mode, primary, text, grey } = palette;
-        const isDark = mode === "dark";
-
-        if (selected) {
-          const bgcolor = isDark
-            ? alpha(primary.main, 0.35)
-            : alpha(primary.main, 0.15);
-
-          return {
-            bgcolor,
-            color: isDark ? alpha(text.secondary, 0.6) : text.secondary,
-            "& h2": {
-              color: isDark ? primary.light : primary.main,
-            },
-          };
-        }
-        const bgcolor = isDark
-          ? alpha(grey[700], 0.35)
-          : palette.background.paper;
-        return {
-          bgcolor,
-          color: isDark ? alpha(text.secondary, 0.6) : text.secondary,
-          "& h2": {
-            color: isDark ? text.primary : text.primary,
-          },
-        };
-      }}
-      onClick={onClick}
-    >
-      <Box display="flex" justifyContent="space-between">
-        <Typography
-          width="calc(100% - 40px)"
-          variant="h6"
-          component="h2"
-          noWrap
-          title={name}
-        >
-          {name}
-        </Typography>
+  const onDelete = async () => {
+    setAnchorEl(null);
+    try {
+      await deleteProfile(index);
+      mutate("getProfiles");
+    } catch (err) {
+      console.error(err);
+    }
+  };
+
+  const handleContextMenu = (
+    event: React.MouseEvent<HTMLDivElement, MouseEvent>
+  ) => {
+    const { clientX, clientY } = event;
+    setPosition({ top: clientY, left: clientX });
+    setAnchorEl(event.currentTarget);
+    event.preventDefault();
+  };
 
-        <IconButton
+  return (
+    <>
+      <Wrapper
+        sx={({ palette }) => {
+          const { mode, primary, text, grey } = palette;
+          const key = `${mode}-${selected}`;
+
+          const bgcolor = {
+            "light-true": alpha(primary.main, 0.15),
+            "light-false": palette.background.paper,
+            "dark-true": alpha(primary.main, 0.35),
+            "dark-false": alpha(grey[700], 0.35),
+          }[key]!;
+
+          const color = {
+            "light-true": text.secondary,
+            "light-false": text.secondary,
+            "dark-true": alpha(text.secondary, 0.6),
+            "dark-false": alpha(text.secondary, 0.6),
+          }[key]!;
+
+          const h2color = {
+            "light-true": primary.main,
+            "light-false": text.primary,
+            "dark-true": primary.light,
+            "dark-false": text.primary,
+          }[key]!;
+
+          return { bgcolor, color, "& h2": { color: h2color } };
+        }}
+        onClick={onClick}
+        onContextMenu={handleContextMenu}
+      >
+        <Box display="flex" justifyContent="space-between">
+          <Typography
+            width="calc(100% - 40px)"
+            variant="h6"
+            component="h2"
+            noWrap
+            title={name}
+          >
+            {name}
+          </Typography>
+
+          <IconButton
+            sx={{
+              width: 30,
+              height: 30,
+              animation: loading ? `1s linear infinite ${round}` : "none",
+            }}
+            color="inherit"
+            disabled={loading}
+            onClick={(e) => {
+              e.stopPropagation();
+              onUpdate();
+            }}
+          >
+            <RefreshRounded />
+          </IconButton>
+        </Box>
+
+        <Box display="flex" justifyContent="space-between" alignItems="center">
+          <Typography noWrap title={`From: ${from}`}>
+            {from}
+          </Typography>
+
+          <Typography
+            noWrap
+            flex="1 0 auto"
+            fontSize={14}
+            textAlign="right"
+            title="updated time"
+          >
+            {fromnow}
+          </Typography>
+        </Box>
+
+        <Box
           sx={{
-            width: 30,
-            height: 30,
-            animation: loading ? `1s linear infinite ${round}` : "none",
+            my: 0.5,
+            fontSize: 14,
+            display: "flex",
+            justifyContent: "space-between",
           }}
-          color="inherit"
-          disabled={loading}
-          onClick={(e) => {
-            e.stopPropagation();
-            onUpdate();
-          }}
-        >
-          <RefreshRounded />
-        </IconButton>
-      </Box>
-
-      <Box display="flex" justifyContent="space-between" alignItems="center">
-        <Typography noWrap title={`From: ${from}`}>
-          {from}
-        </Typography>
-
-        <Typography
-          noWrap
-          flex="1 0 auto"
-          fontSize={14}
-          textAlign="right"
-          title="updated time"
         >
-          {fromnow}
-        </Typography>
-      </Box>
-
-      <Box
-        sx={{
-          my: 0.5,
-          fontSize: 14,
-          display: "flex",
-          justifyContent: "space-between",
-        }}
+          <span title="used / total">
+            {parseTraffic(upload + download)} / {parseTraffic(total)}
+          </span>
+          <span title="expire time">{expire}</span>
+        </Box>
+
+        <LinearProgress
+          variant="determinate"
+          value={progress}
+          color="inherit"
+        />
+      </Wrapper>
+
+      <Menu
+        open={!!anchorEl}
+        anchorEl={anchorEl}
+        onClose={() => setAnchorEl(null)}
+        anchorPosition={position}
+        anchorReference="anchorPosition"
       >
-        <span title="used / total">
-          {parseTraffic(upload + download)} / {parseTraffic(total)}
-        </span>
-        <span title="expire time">{expire}</span>
-      </Box>
-
-      <LinearProgress variant="determinate" value={progress} color="inherit" />
-    </Wrapper>
+        <MenuItem onClick={onUpdate}>Update</MenuItem>
+        <MenuItem onClick={onDelete}>Delete</MenuItem>
+        {/* <MenuItem>Update(proxy)</MenuItem> */}
+      </Menu>
+    </>
   );
 };
 
diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx
index e374509..adeef37 100644
--- a/src/pages/profile.tsx
+++ b/src/pages/profile.tsx
@@ -24,16 +24,20 @@ const ProfilePage = () => {
     if (profiles.current == null) return;
     if (!profiles.items) profiles.items = [];
 
-    const profile = profiles.items![profiles.current];
+    const current = profiles.current;
+    const profile = profiles.items![current];
     if (!profile) return;
 
-    getProxies().then((proxiesData) => {
+    setTimeout(async () => {
+      const proxiesData = await getProxies();
       mutate("getProxies", proxiesData);
+
       // init selected array
       const { selected = [] } = profile;
       const selectedMap = Object.fromEntries(
         selected.map((each) => [each.name!, each.now!])
       );
+
       // todo: enhance error handle
       let hasChange = false;
       proxiesData.groups.forEach((group) => {
@@ -52,10 +56,10 @@ const ProfilePage = () => {
         name,
         now,
       }));
-      patchProfile(profiles.current!, profile).catch(console.error);
+      patchProfile(current!, profile).catch(console.error);
       // update proxies cache
       if (hasChange) mutate("getProxies", getProxies());
-    });
+    }, 100);
   }, [profiles]);
 
   const onImport = async () => {
-- 
GitLab