diff --git a/src/components/profile/profile-edit.tsx b/src/components/profile/profile-edit.tsx index cda39d2..ae5856d 100644 --- a/src/components/profile/profile-edit.tsx +++ b/src/components/profile/profile-edit.tsx @@ -50,11 +50,8 @@ const ProfileEdit = (props: Props) => { } as const; const type = - form.type ?? form.url - ? "remote" - : form.file?.endsWith("js") - ? "script" - : "local"; + form.type || + (form.url ? "remote" : form.file?.endsWith("js") ? "script" : "local"); return ( diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 238974d..af4dc53 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import { useLockFn } from "ahooks"; import { useSWRConfig } from "swr"; -import { useEffect, useState, MouseEvent } from "react"; +import { useEffect, useState } from "react"; import { alpha, Box, @@ -84,7 +84,7 @@ const ProfileItem = (props: Props) => { try { await viewProfile(itemData.uid); } 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 () => { setAnchorEl(null); - try { await deleteProfile(itemData.uid); mutate("getProfiles"); @@ -118,13 +117,6 @@ const ProfileItem = (props: Props) => { } }); - const handleContextMenu = (event: MouseEvent) => { - const { clientX, clientY } = event; - setPosition({ top: clientY, left: clientX }); - setAnchorEl(event.currentTarget); - event.preventDefault(); - }; - const boxStyle = { height: 26, display: "flex", @@ -178,7 +170,12 @@ const ProfileItem = (props: Props) => { return { bgcolor, color, "& h2": { color: h2color } }; }} onClick={() => onSelect(false)} - onContextMenu={handleContextMenu as any} + onContextMenu={(event) => { + const { clientX, clientY } = event; + setPosition({ top: clientY, left: clientX }); + setAnchorEl(event.currentTarget); + event.preventDefault(); + }} > { onClose={() => setAnchorEl(null)} anchorPosition={position} anchorReference="anchorPosition" + onContextMenu={(e) => { + setAnchorEl(null); + e.preventDefault(); + }} > {(hasUrl ? urlModeMenu : fileModeMenu).map((item) => ( { ))} - setEditOpen(false)} - /> + {editOpen && ( + setEditOpen(false)} + /> + )} ); }; diff --git a/src/components/profile/profile-more.tsx b/src/components/profile/profile-more.tsx new file mode 100644 index 0000000..367ac5a --- /dev/null +++ b/src/components/profile/profile-more.tsx @@ -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(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 ( + <> + { + 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(); + }} + > + + + {itemData.name} + + + + + + + + {itemData.desc} + + + + {parseExpire(itemData.updated)} + + + + + setAnchorEl(null)} + anchorPosition={position} + anchorReference="anchorPosition" + onContextMenu={(e) => { + setAnchorEl(null); + e.preventDefault(); + }} + > + {(selected ? enableMenu : disableMenu).map((item) => ( + + {item.label} + + ))} + + + {editOpen && ( + setEditOpen(false)} + /> + )} + + ); +}; + +function parseExpire(expire?: number) { + if (!expire) return "-"; + return dayjs(expire * 1000).format("YYYY-MM-DD"); +} + +export default ProfileMore; diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index b7ee9f9..1557d59 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -1,7 +1,7 @@ import useSWR, { useSWRConfig } from "swr"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useLockFn } from "ahooks"; -import { Box, Button, Grid, TextField, Typography } from "@mui/material"; +import { Box, Button, Grid, TextField } from "@mui/material"; import { getProfiles, selectProfile, @@ -13,21 +13,34 @@ import Notice from "../components/base/base-notice"; import BasePage from "../components/base/base-page"; import ProfileNew from "../components/profile/profile-new"; import ProfileItem from "../components/profile/profile-item"; +import ProfileMore from "../components/profile/profile-more"; const ProfilePage = () => { + const { mutate } = useSWRConfig(); + const [url, setUrl] = useState(""); const [disabled, setDisabled] = useState(false); - - const { mutate } = useSWRConfig(); - const { data: profiles = {} } = useSWR("getProfiles", getProfiles); 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(() => { if (profiles.current == null) return; - if (!profiles.items) profiles.items = []; const current = profiles.current; - const profile = profiles.items.find((p) => p.uid === current); + const profile = regularItems.find((p) => p.uid === current); if (!profile) return; setTimeout(async () => { @@ -62,7 +75,7 @@ const ProfilePage = () => { // update proxies cache if (hasChange) mutate("getProxies", getProxies()); }, 100); - }, [profiles]); + }, [profiles, regularItems]); const onImport = async () => { if (!url) return; @@ -89,22 +102,27 @@ const ProfilePage = () => { } }; - const onSelect = useLockFn(async (current: string, force: boolean) => { - if (!force && current === profiles.current) return; + const onSelect = useLockFn(async (uid: string, force: boolean) => { + if (!force && uid === profiles.current) return; try { - await selectProfile(current); - mutate("getProfiles", { ...profiles, current: current }, true); + await selectProfile(uid); + mutate("getProfiles", { ...profiles, current: uid }, true); } 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 ( - + { - - {profiles?.items?.map((item) => ( + + {regularItems.map((item) => ( { ))} - setDialogOpen(false)} /> + + {enhanceItems.map((item) => ( + + onEnhanceEnable(item.uid)} + onDisable={() => onEnhanceDisable(item.uid)} + onMoveTop={() => onMoveTop(item.uid)} + onMoveEnd={() => onMoveEnd(item.uid)} + /> + + ))} + -
- - Enhanced - -
+ setDialogOpen(false)} />
); };