import { CheckCircleOutlineRounded } from '@mui/icons-material' import { alpha, Box, ListItemButton, styled, Typography } from '@mui/material' import { useLockFn } from 'ahooks' import { useCallback, useEffect, useReducer } from 'react' import { useTranslation } from 'react-i18next' import { BaseLoading } from '@/components/base' import { useVerge } from '@/hooks/use-verge' import delayManager, { DelayUpdate } from '@/services/delay' interface Props { group: IProxyGroupItem proxy: IProxyItem selected: boolean showType?: boolean onClick?: (name: string) => void } // 多列布局 export const ProxyItemMini = (props: Props) => { const { group, proxy, selected, showType = true, onClick } = props const { t } = useTranslation() const presetList = ['DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE'] const isPreset = presetList.includes(proxy.name) // -1/<=0 为不显示,-2 为 loading const [delayState, setDelayState] = useReducer( (_: DelayUpdate, next: DelayUpdate) => next, { delay: -1, updatedAt: 0, }, ) const { verge } = useVerge() const timeout = verge?.default_latency_timeout || 10000 useEffect(() => { if (isPreset) return delayManager.setListener(proxy.name, group.name, setDelayState) return () => { delayManager.removeListener(proxy.name, group.name) } }, [isPreset, proxy.name, group.name]) const updateDelay = useCallback(() => { if (!proxy) return const cachedUpdate = delayManager.getDelayUpdate(proxy.name, group.name) if (cachedUpdate) { setDelayState({ ...cachedUpdate }) return } const fallbackDelay = delayManager.getDelayFix(proxy, group.name) if (fallbackDelay === -1) { setDelayState({ delay: -1, updatedAt: 0 }) return } let updatedAt = 0 const history = proxy.history if (history && history.length > 0) { const lastRecord = history[history.length - 1] const parsed = Date.parse(lastRecord.time) if (!Number.isNaN(parsed)) { updatedAt = parsed } } setDelayState({ delay: fallbackDelay, updatedAt, }) }, [proxy, group.name]) useEffect(() => { updateDelay() }, [updateDelay]) const onDelay = useLockFn(async () => { setDelayState({ delay: -2, updatedAt: Date.now() }) setDelayState( await delayManager.checkDelay(proxy.name, group.name, timeout), ) }) const delayValue = delayState.delay return ( onClick?.(proxy.name)} sx={[ { height: 56, borderRadius: 1.5, pl: 1.5, pr: 1, justifyContent: 'space-between', alignItems: 'center', }, ({ palette: { mode, primary } }) => { const bgcolor = mode === 'light' ? '#ffffff' : '#24252f' const showDelay = delayValue > 0 const selectColor = mode === 'light' ? primary.main : primary.light return { '&:hover .the-check': { display: !showDelay ? 'block' : 'none' }, '&:hover .the-delay': { display: showDelay ? 'block' : 'none' }, '&:hover .the-icon': { display: 'none' }, '& .the-pin, & .the-unpin': { position: 'absolute', fontSize: '12px', top: '-5px', right: '-5px', }, '& .the-unpin': { filter: 'grayscale(1)' }, '&.Mui-selected': { width: `calc(100% + 3px)`, marginLeft: `-3px`, borderLeft: `3px solid ${selectColor}`, bgcolor: mode === 'light' ? alpha(primary.main, 0.15) : alpha(primary.main, 0.35), }, backgroundColor: bgcolor, } }, ]} > {proxy.name} {showType && ( {proxy.now && ( {proxy.now} )} {!!proxy.provider && ( {proxy.provider} )} {proxy.type} {proxy.udp && ( UDP )} {proxy.xudp && ( XUDP )} {proxy.tfo && ( TFO )} {proxy.mptcp && ( MPTCP )} {proxy.smux && ( SMUX )} )} {delayValue === -2 && ( )} {!proxy.provider && delayValue !== -2 && ( // provider 的节点不支持检测 { e.preventDefault() e.stopPropagation() onDelay() }} sx={({ palette }) => ({ display: 'none', // hover 时显示 ':hover': { bgcolor: alpha(palette.primary.main, 0.15) }, })} > Check )} {delayValue >= 0 && ( // 显示延迟 { if (proxy.provider) return e.preventDefault() e.stopPropagation() onDelay() }} sx={({ palette }) => ({ color: delayManager.formatDelayColor(delayValue, timeout), ...(!proxy.provider ? { ':hover': { bgcolor: alpha(palette.primary.main, 0.15) } } : {}), })} > {delayManager.formatDelay(delayValue, timeout)} )} {proxy.type !== 'Direct' && delayValue !== -2 && delayValue < 0 && selected && ( // 展示已选择的 icon )} {group.fixed && group.fixed === proxy.name && ( // 展示 fixed 状态 📌 )} ) } const Widget = styled(Box)(({ theme: { typography } }) => ({ padding: '2px 4px', fontSize: 14, fontFamily: typography.fontFamily, borderRadius: '4px', })) const TypeBox = styled(Box, { shouldForwardProp: (prop) => prop !== 'component', })<{ component?: React.ElementType }>(({ theme: { typography } }) => ({ display: 'inline-block', border: '1px solid #ccc', borderColor: 'text.secondary', color: 'text.secondary', borderRadius: 4, fontSize: 10, fontFamily: typography.fontFamily, marginRight: '4px', marginTop: 'auto', padding: '0 4px', lineHeight: 1.5, }))