feat: profile enhanced ui

This commit is contained in:
GyDi 2022-03-05 22:54:39 +08:00
parent f44039b628
commit a43dab8057
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
4 changed files with 293 additions and 44 deletions

View File

@ -50,11 +50,8 @@ const ProfileEdit = (props: Props) => {
} as const; } as const;
const type = const type =
form.type ?? form.url form.type ||
? "remote" (form.url ? "remote" : form.file?.endsWith("js") ? "script" : "local");
: form.file?.endsWith("js")
? "script"
: "local";
return ( return (
<Dialog open={open} onClose={onClose}> <Dialog open={open} onClose={onClose}>

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import { useEffect, useState, MouseEvent } from "react"; import { useEffect, useState } from "react";
import { import {
alpha, alpha,
Box, Box,
@ -84,7 +84,7 @@ const ProfileItem = (props: Props) => {
try { try {
await viewProfile(itemData.uid); await viewProfile(itemData.uid);
} catch (err: any) { } catch (err: any) {
Notice.error(err.toString()); Notice.error(err?.message || err.toString());
} }
}; };
@ -109,7 +109,6 @@ const ProfileItem = (props: Props) => {
const onDelete = useLockFn(async () => { const onDelete = useLockFn(async () => {
setAnchorEl(null); setAnchorEl(null);
try { try {
await deleteProfile(itemData.uid); await deleteProfile(itemData.uid);
mutate("getProfiles"); mutate("getProfiles");
@ -118,13 +117,6 @@ const ProfileItem = (props: Props) => {
} }
}); });
const handleContextMenu = (event: MouseEvent<HTMLDivElement, MouseEvent>) => {
const { clientX, clientY } = event;
setPosition({ top: clientY, left: clientX });
setAnchorEl(event.currentTarget);
event.preventDefault();
};
const boxStyle = { const boxStyle = {
height: 26, height: 26,
display: "flex", display: "flex",
@ -178,7 +170,12 @@ const ProfileItem = (props: Props) => {
return { bgcolor, color, "& h2": { color: h2color } }; return { bgcolor, color, "& h2": { color: h2color } };
}} }}
onClick={() => onSelect(false)} onClick={() => onSelect(false)}
onContextMenu={handleContextMenu as any} onContextMenu={(event) => {
const { clientX, clientY } = event;
setPosition({ top: clientY, left: clientX });
setAnchorEl(event.currentTarget);
event.preventDefault();
}}
> >
<Box display="flex" justifyContent="space-between"> <Box display="flex" justifyContent="space-between">
<Typography <Typography
@ -263,6 +260,10 @@ const ProfileItem = (props: Props) => {
onClose={() => setAnchorEl(null)} onClose={() => setAnchorEl(null)}
anchorPosition={position} anchorPosition={position}
anchorReference="anchorPosition" anchorReference="anchorPosition"
onContextMenu={(e) => {
setAnchorEl(null);
e.preventDefault();
}}
> >
{(hasUrl ? urlModeMenu : fileModeMenu).map((item) => ( {(hasUrl ? urlModeMenu : fileModeMenu).map((item) => (
<MenuItem <MenuItem
@ -275,11 +276,13 @@ const ProfileItem = (props: Props) => {
))} ))}
</Menu> </Menu>
{editOpen && (
<ProfileEdit <ProfileEdit
open={editOpen} open={editOpen}
itemData={itemData} itemData={itemData}
onClose={() => setEditOpen(false)} onClose={() => setEditOpen(false)}
/> />
)}
</> </>
); );
}; };

View File

@ -0,0 +1,222 @@
import dayjs from "dayjs";
import { useLockFn } from "ahooks";
import { useSWRConfig } from "swr";
import { useState } from "react";
import {
alpha,
Box,
Chip,
styled,
Typography,
MenuItem,
Menu,
} from "@mui/material";
import { CmdType } from "../../services/types";
import { deleteProfile, viewProfile } from "../../services/cmds";
import relativeTime from "dayjs/plugin/relativeTime";
import ProfileEdit from "./profile-edit";
import Notice from "../base/base-notice";
dayjs.extend(relativeTime);
const Wrapper = styled(Box)(({ theme }) => ({
width: "100%",
display: "block",
cursor: "pointer",
textAlign: "left",
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[2],
padding: "8px 16px",
boxSizing: "border-box",
}));
interface Props {
selected: boolean;
itemData: CmdType.ProfileItem;
onEnable: () => void;
onDisable: () => void;
onMoveTop: () => void;
onMoveEnd: () => void;
}
// profile enhanced item
const ProfileMore = (props: Props) => {
const {
selected,
itemData,
onEnable,
onDisable,
onMoveTop,
onMoveEnd,
} = props;
const { type } = itemData;
const { mutate } = useSWRConfig();
const [anchorEl, setAnchorEl] = useState<any>(null);
const [position, setPosition] = useState({ left: 0, top: 0 });
const [editOpen, setEditOpen] = useState(false);
const onEdit = () => {
setAnchorEl(null);
setEditOpen(true);
};
const onView = async () => {
setAnchorEl(null);
try {
await viewProfile(itemData.uid);
} catch (err: any) {
Notice.error(err?.message || err.toString());
}
};
const onDelete = useLockFn(async () => {
setAnchorEl(null);
try {
await deleteProfile(itemData.uid);
mutate("getProfiles");
} catch (err: any) {
Notice.error(err?.message || err.toString());
}
});
const enableMenu = [
{ label: "Disable", handler: onDisable },
{ label: "Edit", handler: onEdit },
{ label: "View File", handler: onView },
{ label: "To Top", handler: onMoveTop },
{ label: "To End", handler: onMoveEnd },
{ label: "Delete", handler: onDelete },
];
const disableMenu = [
{ label: "Enable", handler: onEnable },
{ label: "Edit", handler: onEdit },
{ label: "View File", handler: onView },
{ label: "Delete", handler: onDelete },
];
const boxStyle = {
height: 26,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
lineHeight: 1,
};
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={() => onSelect(false)}
onContextMenu={(event) => {
const { clientX, clientY } = event;
setPosition({ top: clientY, left: clientX });
setAnchorEl(event.currentTarget);
event.preventDefault();
}}
>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography
width="calc(100% - 52px)"
variant="h6"
component="h2"
noWrap
title={itemData.name}
>
{itemData.name}
</Typography>
<Chip
label={type}
color="primary"
size="small"
variant="outlined"
sx={{ textTransform: "capitalize" }}
/>
</Box>
<Box sx={boxStyle}>
<Typography
noWrap
title={itemData.desc}
sx={{ width: "calc(100% - 75px)" }}
>
{itemData.desc}
</Typography>
<Typography
component="span"
title="updated time"
style={{ fontSize: 14 }}
>
{parseExpire(itemData.updated)}
</Typography>
</Box>
</Wrapper>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={() => setAnchorEl(null)}
anchorPosition={position}
anchorReference="anchorPosition"
onContextMenu={(e) => {
setAnchorEl(null);
e.preventDefault();
}}
>
{(selected ? enableMenu : disableMenu).map((item) => (
<MenuItem
key={item.label}
onClick={item.handler}
sx={{ minWidth: 133 }}
>
{item.label}
</MenuItem>
))}
</Menu>
{editOpen && (
<ProfileEdit
open={editOpen}
itemData={itemData}
onClose={() => setEditOpen(false)}
/>
)}
</>
);
};
function parseExpire(expire?: number) {
if (!expire) return "-";
return dayjs(expire * 1000).format("YYYY-MM-DD");
}
export default ProfileMore;

View File

@ -1,7 +1,7 @@
import useSWR, { useSWRConfig } from "swr"; import useSWR, { useSWRConfig } from "swr";
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { Box, Button, Grid, TextField, Typography } from "@mui/material"; import { Box, Button, Grid, TextField } from "@mui/material";
import { import {
getProfiles, getProfiles,
selectProfile, selectProfile,
@ -13,21 +13,34 @@ import Notice from "../components/base/base-notice";
import BasePage from "../components/base/base-page"; import BasePage from "../components/base/base-page";
import ProfileNew from "../components/profile/profile-new"; import ProfileNew from "../components/profile/profile-new";
import ProfileItem from "../components/profile/profile-item"; import ProfileItem from "../components/profile/profile-item";
import ProfileMore from "../components/profile/profile-more";
const ProfilePage = () => { const ProfilePage = () => {
const { mutate } = useSWRConfig();
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
const { mutate } = useSWRConfig();
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
const { regularItems, enhanceItems } = useMemo(() => {
const { items = [] } = profiles;
const regularItems = items.filter((i) =>
["local", "remote"].includes(i.type!)
);
const enhanceItems = items.filter((i) =>
["merge", "script"].includes(i.type!)
);
return { regularItems, enhanceItems };
}, [profiles]);
useEffect(() => { useEffect(() => {
if (profiles.current == null) return; if (profiles.current == null) return;
if (!profiles.items) profiles.items = [];
const current = profiles.current; const current = profiles.current;
const profile = profiles.items.find((p) => p.uid === current); const profile = regularItems.find((p) => p.uid === current);
if (!profile) return; if (!profile) return;
setTimeout(async () => { setTimeout(async () => {
@ -62,7 +75,7 @@ const ProfilePage = () => {
// update proxies cache // update proxies cache
if (hasChange) mutate("getProxies", getProxies()); if (hasChange) mutate("getProxies", getProxies());
}, 100); }, 100);
}, [profiles]); }, [profiles, regularItems]);
const onImport = async () => { const onImport = async () => {
if (!url) return; if (!url) return;
@ -89,22 +102,27 @@ const ProfilePage = () => {
} }
}; };
const onSelect = useLockFn(async (current: string, force: boolean) => { const onSelect = useLockFn(async (uid: string, force: boolean) => {
if (!force && current === profiles.current) return; if (!force && uid === profiles.current) return;
try { try {
await selectProfile(current); await selectProfile(uid);
mutate("getProfiles", { ...profiles, current: current }, true); mutate("getProfiles", { ...profiles, current: uid }, true);
} catch (err: any) { } catch (err: any) {
err && Notice.error(err.message || err.toString()); Notice.error(err?.message || err.toString());
} }
}); });
const onEnhanceEnable = useLockFn(async (uid: string) => {});
const onEnhanceDisable = useLockFn(async (uid: string) => {});
const onMoveTop = useLockFn(async (uid: string) => {});
const onMoveEnd = useLockFn(async (uid: string) => {});
return ( return (
<BasePage title="Profiles"> <BasePage title="Profiles">
<Box sx={{ display: "flex", mb: 3 }}> <Box sx={{ display: "flex", mb: 2.5 }}>
<TextField <TextField
id="profile_url" id="clas_verge_profile_url"
name="profile_url" name="profile_url"
label="Profile URL" label="Profile URL"
size="small" size="small"
@ -126,8 +144,8 @@ const ProfilePage = () => {
</Button> </Button>
</Box> </Box>
<Grid container spacing={3}> <Grid container spacing={2}>
{profiles?.items?.map((item) => ( {regularItems.map((item) => (
<Grid item xs={12} sm={6} key={item.file}> <Grid item xs={12} sm={6} key={item.file}>
<ProfileItem <ProfileItem
selected={profiles.current === item.uid} selected={profiles.current === item.uid}
@ -138,13 +156,22 @@ const ProfilePage = () => {
))} ))}
</Grid> </Grid>
<ProfileNew open={dialogOpen} onClose={() => setDialogOpen(false)} /> <Grid container spacing={2} sx={{ mt: 3 }}>
{enhanceItems.map((item) => (
<Grid item xs={12} sm={6} key={item.file}>
<ProfileMore
selected={!!profiles.chain?.includes(item.uid)}
itemData={item}
onEnable={() => onEnhanceEnable(item.uid)}
onDisable={() => onEnhanceDisable(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
/>
</Grid>
))}
</Grid>
<header data-windrag style={{ marginTop: 20, userSelect: "none" }}> <ProfileNew open={dialogOpen} onClose={() => setDialogOpen(false)} />
<Typography variant="h5" component="h2" data-windrag>
Enhanced
</Typography>
</header>
</BasePage> </BasePage>
); );
}; };