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', }))