feat: add animation during latency testing for better user feedback

This commit is contained in:
ezequielnick 2025-06-06 19:29:49 +08:00
parent d6e456302e
commit 0b8c77d200
2 changed files with 38 additions and 12 deletions

View File

@ -1,6 +1,7 @@
import { Button, Card, CardBody } from '@heroui/react' import { Button, Card, CardBody } from '@heroui/react'
import { mihomoUnfixedProxy } from '@renderer/utils/ipc' import { mihomoUnfixedProxy } from '@renderer/utils/ipc'
import React, { useMemo, useState } from 'react' import React from 'react'
import { useMemo, useState } from 'react'
import { FaMapPin } from 'react-icons/fa6' import { FaMapPin } from 'react-icons/fa6'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -12,11 +13,12 @@ interface Props {
group: IMihomoMixedGroup group: IMihomoMixedGroup
onSelect: (group: string, proxy: string) => void onSelect: (group: string, proxy: string) => void
selected: boolean selected: boolean
isGroupTesting?: boolean
} }
const ProxyItem: React.FC<Props> = (props) => { const ProxyItem: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { mutateProxies, proxyDisplayMode, group, proxy, selected, onSelect, onProxyDelay } = props const { mutateProxies, proxyDisplayMode, group, proxy, selected, onSelect, onProxyDelay, isGroupTesting = false } = props
const delay = useMemo(() => { const delay = useMemo(() => {
if (proxy.history.length > 0) { if (proxy.history.length > 0) {
@ -26,6 +28,9 @@ const ProxyItem: React.FC<Props> = (props) => {
}, [proxy]) }, [proxy])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const isLoading = loading || isGroupTesting
function delayColor(delay: number): 'primary' | 'success' | 'warning' | 'danger' { function delayColor(delay: number): 'primary' | 'success' | 'warning' | 'danger' {
if (delay === -1) return 'primary' if (delay === -1) return 'primary'
if (delay === 0) return 'danger' if (delay === 0) return 'danger'
@ -106,7 +111,7 @@ const ProxyItem: React.FC<Props> = (props) => {
<Button <Button
isIconOnly isIconOnly
title={proxy.type} title={proxy.type}
isLoading={loading} isLoading={isLoading}
color={delayColor(delay)} color={delayColor(delay)}
onPress={onDelay} onPress={onDelay}
variant="light" variant="light"
@ -144,11 +149,11 @@ const ProxyItem: React.FC<Props> = (props) => {
<Button <Button
isIconOnly isIconOnly
title={proxy.type} title={proxy.type}
isLoading={loading} isLoading={isLoading}
color={delayColor(delay)} color={delayColor(delay)}
onPress={onDelay} onPress={onDelay}
variant="light" variant="light"
className="h-[24px] text-sm px-2 relative w-min whitespace-nowrap" className="h-full text-sm px-2 relative w-min whitespace-nowrap"
> >
<div className="w-full h-full flex items-center justify-end"> <div className="w-full h-full flex items-center justify-end">
{delayText(delay)} {delayText(delay)}

View File

@ -29,7 +29,6 @@ const useProxyState = (groups: IMihomoMixedGroup[]): {
virtuosoRef: React.RefObject<GroupedVirtuosoHandle>; virtuosoRef: React.RefObject<GroupedVirtuosoHandle>;
isOpen: boolean[]; isOpen: boolean[];
setIsOpen: React.Dispatch<React.SetStateAction<boolean[]>>; setIsOpen: React.Dispatch<React.SetStateAction<boolean[]>>;
onScroll: (e: React.UIEvent<HTMLElement>) => void;
} => { } => {
const virtuosoRef = useRef<GroupedVirtuosoHandle>(null) const virtuosoRef = useRef<GroupedVirtuosoHandle>(null)
@ -56,10 +55,7 @@ const useProxyState = (groups: IMihomoMixedGroup[]): {
return { return {
virtuosoRef, virtuosoRef,
isOpen, isOpen,
setIsOpen, setIsOpen
onScroll: useCallback((_e: React.UIEvent<HTMLElement>) => {
// 空实现,不再保存滚动位置
}, [])
} }
} }
@ -78,8 +74,9 @@ const Proxies: React.FC = () => {
} = appConfig || {} } = appConfig || {}
const [cols, setCols] = useState(1) const [cols, setCols] = useState(1)
const { virtuosoRef, isOpen, setIsOpen, onScroll } = useProxyState(groups) const { virtuosoRef, isOpen, setIsOpen } = useProxyState(groups)
const [delaying, setDelaying] = useState(Array(groups.length).fill(false)) const [delaying, setDelaying] = useState(Array(groups.length).fill(false))
const [proxyDelaying, setProxyDelaying] = useState<Record<string, boolean>>({})
const [searchValue, setSearchValue] = useState(Array(groups.length).fill('')) const [searchValue, setSearchValue] = useState(Array(groups.length).fill(''))
const { groupCounts, allProxies } = useMemo(() => { const { groupCounts, allProxies } = useMemo(() => {
const groupCounts: number[] = [] const groupCounts: number[] = []
@ -139,6 +136,16 @@ const Proxies: React.FC = () => {
return newDelaying return newDelaying
}) })
// 本组测试状态
const groupProxies = allProxies[index]
setProxyDelaying((prev) => {
const newProxyDelaying = { ...prev }
groupProxies.forEach(proxy => {
newProxyDelaying[proxy.name] = true
})
return newProxyDelaying
})
try { try {
// 限制并发数量 // 限制并发数量
const result: Promise<void>[] = [] const result: Promise<void>[] = []
@ -150,6 +157,12 @@ const Proxies: React.FC = () => {
} catch { } catch {
// ignore // ignore
} finally { } finally {
// 立即更新状态
setProxyDelaying((prev) => {
const newProxyDelaying = { ...prev }
delete newProxyDelaying[proxy.name]
return newProxyDelaying
})
mutate() mutate()
} }
}) })
@ -169,6 +182,14 @@ const Proxies: React.FC = () => {
newDelaying[index] = false newDelaying[index] = false
return newDelaying return newDelaying
}) })
// 状态清理
setProxyDelaying((prev) => {
const newProxyDelaying = { ...prev }
groupProxies.forEach(proxy => {
delete newProxyDelaying[proxy.name]
})
return newProxyDelaying
})
} }
}, [allProxies, groups, delayTestConcurrency, mutate]) }, [allProxies, groups, delayTestConcurrency, mutate])
@ -256,7 +277,6 @@ const Proxies: React.FC = () => {
<GroupedVirtuoso <GroupedVirtuoso
ref={virtuosoRef} ref={virtuosoRef}
groupCounts={groupCounts} groupCounts={groupCounts}
onScroll={onScroll}
defaultItemHeight={80} defaultItemHeight={80}
increaseViewportBy={{ top: 300, bottom: 300 }} increaseViewportBy={{ top: 300, bottom: 300 }}
overscan={500} overscan={500}
@ -434,6 +454,7 @@ const Proxies: React.FC = () => {
allProxies[groupIndex][innerIndex * cols + i]?.name === allProxies[groupIndex][innerIndex * cols + i]?.name ===
groups[groupIndex].now groups[groupIndex].now
} }
isGroupTesting={!!proxyDelaying[allProxies[groupIndex][innerIndex * cols + i].name]}
/> />
) )
})} })}