import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { LanguageRounded } from "@mui/icons-material"; import { Box, Divider, MenuItem, Menu, styled, alpha } from "@mui/material"; import { UnlistenFn } from "@tauri-apps/api/event"; import { useLockFn } from "ahooks"; import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { BaseLoading } from "@/components/base"; import { useIconCache } from "@/hooks/use-icon-cache"; import { useListen } from "@/hooks/use-listen"; import { cmdTestDelay } from "@/services/cmds"; import delayManager from "@/services/delay"; import { showNotice } from "@/services/notice-service"; import { debugLog } from "@/utils/debug"; import { TestBox } from "./test-box"; interface Props { id: string; itemData: IVergeTestItem; onEdit: () => void; onDelete: (uid: string) => void; } export const TestItem = ({ id, itemData, onEdit, onDelete: removeTest, }: Props) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id, }); const { t } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); const [position, setPosition] = useState({ left: 0, top: 0 }); const [delay, setDelay] = useState(-1); const { uid, name, icon, url } = itemData; const iconCachePath = useIconCache({ icon, cacheKey: uid }); const { addListener } = useListen(); const onDelay = useCallback(async () => { setDelay(-2); const result = await cmdTestDelay(url); setDelay(result); }, [url]); const onEditTest = () => { setAnchorEl(null); onEdit(); }; const onDelete = useLockFn(async () => { setAnchorEl(null); try { removeTest(uid); } catch (err: any) { showNotice.error(err); } }); const menu = [ { label: "Edit", handler: onEditTest }, { label: "Delete", handler: onDelete }, ]; useEffect(() => { let unlistenFn: UnlistenFn | null = null; const setupListener = async () => { if (unlistenFn) { unlistenFn(); } unlistenFn = await addListener("verge://test-all", () => { onDelay(); }); }; setupListener(); return () => { if (unlistenFn) { debugLog( `TestItem for ${id} unmounting or url changed, cleaning up test-all listener.`, ); unlistenFn(); } }; }, [url, addListener, onDelay, id]); return ( { const { clientX, clientY } = event; setPosition({ top: clientY, left: clientX }); setAnchorEl(event.currentTarget); event.preventDefault(); }} > {icon && icon.trim() !== "" ? ( {icon.trim().startsWith("http") && ( )} {icon.trim().startsWith("data") && ( )} {icon.trim().startsWith(" )} ) : ( )} {name} {delay === -2 && ( )} {delay === -1 && ( { e.preventDefault(); e.stopPropagation(); onDelay(); }} sx={({ palette }) => ({ ":hover": { bgcolor: alpha(palette.primary.main, 0.15) }, })} > {t("tests.components.item.actions.test")} )} {delay >= 0 && ( // 显示延迟 { e.preventDefault(); e.stopPropagation(); onDelay(); }} color={delayManager.formatDelayColor(delay)} sx={({ palette }) => ({ ":hover": { bgcolor: alpha(palette.primary.main, 0.15), }, })} > {delayManager.formatDelay(delay)} )} setAnchorEl(null)} anchorPosition={position} anchorReference="anchorPosition" transitionDuration={225} MenuListProps={{ sx: { py: 0.5 } }} onContextMenu={(e) => { setAnchorEl(null); e.preventDefault(); }} > {menu.map((item) => ( {t(item.label)} ))} ); }; const Widget = styled(Box)(({ theme: { typography } }) => ({ padding: "3px 6px", fontSize: 14, fontFamily: typography.fontFamily, borderRadius: "4px", }));