Skip to content
Snippets Groups Projects
Commit ea8f1c52 authored by GyDi's avatar GyDi
Browse files

feat: profiles add menu and delete button

parent a4c1573c
No related branches found
No related tags found
No related merge requests found
...@@ -8,13 +8,15 @@ import { ...@@ -8,13 +8,15 @@ import {
LinearProgress, LinearProgress,
IconButton, IconButton,
keyframes, keyframes,
MenuItem,
Menu,
} from "@mui/material"; } from "@mui/material";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import { RefreshRounded } from "@mui/icons-material"; import { RefreshRounded } from "@mui/icons-material";
import { CmdType } from "../services/types"; import { CmdType } from "../services/types";
import { updateProfile, deleteProfile } from "../services/cmds";
import parseTraffic from "../utils/parse-traffic"; import parseTraffic from "../utils/parse-traffic";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import { updateProfile } from "../services/cmds";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
...@@ -46,6 +48,8 @@ const ProfileItemComp: React.FC<Props> = (props) => { ...@@ -46,6 +48,8 @@ const ProfileItemComp: React.FC<Props> = (props) => {
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();
const [loading, setLoading] = useState(false); 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 { name = "Profile", extra, updated = 0 } = itemData;
const { upload = 0, download = 0, total = 0 } = extra ?? {}; const { upload = 0, download = 0, total = 0 } = extra ?? {};
...@@ -55,6 +59,7 @@ const ProfileItemComp: React.FC<Props> = (props) => { ...@@ -55,6 +59,7 @@ const ProfileItemComp: React.FC<Props> = (props) => {
const fromnow = updated > 0 ? dayjs(updated * 1000).fromNow() : ""; const fromnow = updated > 0 ? dayjs(updated * 1000).fromNow() : "";
const onUpdate = async () => { const onUpdate = async () => {
setAnchorEl(null);
if (loading) return; if (loading) return;
setLoading(true); setLoading(true);
try { try {
...@@ -67,98 +72,135 @@ const ProfileItemComp: React.FC<Props> = (props) => { ...@@ -67,98 +72,135 @@ const ProfileItemComp: React.FC<Props> = (props) => {
} }
}; };
return ( const onDelete = async () => {
<Wrapper setAnchorEl(null);
sx={({ palette }) => { try {
const { mode, primary, text, grey } = palette; await deleteProfile(index);
const isDark = mode === "dark"; mutate("getProfiles");
} catch (err) {
if (selected) { console.error(err);
const bgcolor = isDark }
? alpha(primary.main, 0.35) };
: alpha(primary.main, 0.15);
const handleContextMenu = (
return { event: React.MouseEvent<HTMLDivElement, MouseEvent>
bgcolor, ) => {
color: isDark ? alpha(text.secondary, 0.6) : text.secondary, const { clientX, clientY } = event;
"& h2": { setPosition({ top: clientY, left: clientX });
color: isDark ? primary.light : primary.main, setAnchorEl(event.currentTarget);
}, event.preventDefault();
}; };
}
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>
<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={{ sx={{
width: 30, my: 0.5,
height: 30, fontSize: 14,
animation: loading ? `1s linear infinite ${round}` : "none", 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} <span title="used / total">
</Typography> {parseTraffic(upload + download)} / {parseTraffic(total)}
</Box> </span>
<span title="expire time">{expire}</span>
<Box </Box>
sx={{
my: 0.5, <LinearProgress
fontSize: 14, variant="determinate"
display: "flex", value={progress}
justifyContent: "space-between", color="inherit"
}} />
</Wrapper>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorPosition={position}
anchorReference="anchorPosition"
> >
<span title="used / total"> <MenuItem onClick={onUpdate}>Update</MenuItem>
{parseTraffic(upload + download)} / {parseTraffic(total)} <MenuItem onClick={onDelete}>Delete</MenuItem>
</span> {/* <MenuItem>Update(proxy)</MenuItem> */}
<span title="expire time">{expire}</span> </Menu>
</Box> </>
<LinearProgress variant="determinate" value={progress} color="inherit" />
</Wrapper>
); );
}; };
......
...@@ -24,16 +24,20 @@ const ProfilePage = () => { ...@@ -24,16 +24,20 @@ const ProfilePage = () => {
if (profiles.current == null) return; if (profiles.current == null) return;
if (!profiles.items) profiles.items = []; if (!profiles.items) profiles.items = [];
const profile = profiles.items![profiles.current]; const current = profiles.current;
const profile = profiles.items![current];
if (!profile) return; if (!profile) return;
getProxies().then((proxiesData) => { setTimeout(async () => {
const proxiesData = await getProxies();
mutate("getProxies", proxiesData); mutate("getProxies", proxiesData);
// init selected array // init selected array
const { selected = [] } = profile; const { selected = [] } = profile;
const selectedMap = Object.fromEntries( const selectedMap = Object.fromEntries(
selected.map((each) => [each.name!, each.now!]) selected.map((each) => [each.name!, each.now!])
); );
// todo: enhance error handle // todo: enhance error handle
let hasChange = false; let hasChange = false;
proxiesData.groups.forEach((group) => { proxiesData.groups.forEach((group) => {
...@@ -52,10 +56,10 @@ const ProfilePage = () => { ...@@ -52,10 +56,10 @@ const ProfilePage = () => {
name, name,
now, now,
})); }));
patchProfile(profiles.current!, profile).catch(console.error); patchProfile(current!, profile).catch(console.error);
// update proxies cache // update proxies cache
if (hasChange) mutate("getProxies", getProxies()); if (hasChange) mutate("getProxies", getProxies());
}); }, 100);
}, [profiles]); }, [profiles]);
const onImport = async () => { const onImport = async () => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment