feat: profiles add menu and delete button
This commit is contained in:
parent
a4c1573c45
commit
ea8f1c52f9
@ -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) => {
|
|||||||
|
|
||||||
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) => {
|
|||||||
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) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<>
|
||||||
sx={({ palette }) => {
|
<Wrapper
|
||||||
const { mode, primary, text, grey } = palette;
|
sx={({ palette }) => {
|
||||||
const isDark = mode === "dark";
|
const { mode, primary, text, grey } = palette;
|
||||||
|
const key = `${mode}-${selected}`;
|
||||||
|
|
||||||
if (selected) {
|
const bgcolor = {
|
||||||
const bgcolor = isDark
|
"light-true": alpha(primary.main, 0.15),
|
||||||
? alpha(primary.main, 0.35)
|
"light-false": palette.background.paper,
|
||||||
: alpha(primary.main, 0.15);
|
"dark-true": alpha(primary.main, 0.35),
|
||||||
|
"dark-false": alpha(grey[700], 0.35),
|
||||||
|
}[key]!;
|
||||||
|
|
||||||
return {
|
const color = {
|
||||||
bgcolor,
|
"light-true": text.secondary,
|
||||||
color: isDark ? alpha(text.secondary, 0.6) : text.secondary,
|
"light-false": text.secondary,
|
||||||
"& h2": {
|
"dark-true": alpha(text.secondary, 0.6),
|
||||||
color: isDark ? primary.light : primary.main,
|
"dark-false": alpha(text.secondary, 0.6),
|
||||||
},
|
}[key]!;
|
||||||
};
|
|
||||||
}
|
|
||||||
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
|
const h2color = {
|
||||||
sx={{
|
"light-true": primary.main,
|
||||||
width: 30,
|
"light-false": text.primary,
|
||||||
height: 30,
|
"dark-true": primary.light,
|
||||||
animation: loading ? `1s linear infinite ${round}` : "none",
|
"dark-false": text.primary,
|
||||||
}}
|
}[key]!;
|
||||||
color="inherit"
|
|
||||||
disabled={loading}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onUpdate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RefreshRounded />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
return { bgcolor, color, "& h2": { color: h2color } };
|
||||||
<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",
|
|
||||||
}}
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<span title="used / total">
|
<Box display="flex" justifyContent="space-between">
|
||||||
{parseTraffic(upload + download)} / {parseTraffic(total)}
|
<Typography
|
||||||
</span>
|
width="calc(100% - 40px)"
|
||||||
<span title="expire time">{expire}</span>
|
variant="h6"
|
||||||
</Box>
|
component="h2"
|
||||||
|
noWrap
|
||||||
|
title={name}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<LinearProgress variant="determinate" value={progress} color="inherit" />
|
<IconButton
|
||||||
</Wrapper>
|
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={{
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<MenuItem onClick={onUpdate}>Update</MenuItem>
|
||||||
|
<MenuItem onClick={onDelete}>Delete</MenuItem>
|
||||||
|
{/* <MenuItem>Update(proxy)</MenuItem> */}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 = () => {
|
|||||||
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 () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user