import { useRef, useState, useEffect, useCallback } from "react"; import { useLockFn } from "ahooks"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; import { getConnections, providerHealthCheck, updateProxy, deleteConnection, getGroupProxyDelays, } from "@/services/api"; import { useProfiles } from "@/hooks/use-profiles"; import { useVerge } from "@/hooks/use-verge"; import { BaseEmpty } from "../base"; import { useRenderList } from "./use-render-list"; import { ProxyRender } from "./proxy-render"; import delayManager from "@/services/delay"; import { useTranslation } from "react-i18next"; import { ScrollTopButton } from "../layout/scroll-top-button"; interface Props { mode: string; } export const ProxyGroups = (props: Props) => { const { t } = useTranslation(); const { mode } = props; const { renderList, onProxies, onHeadState } = useRenderList(mode); const { verge } = useVerge(); const { current, patchCurrent } = useProfiles(); const timeout = verge?.default_latency_timeout || 10000; const virtuosoRef = useRef(null); const scrollPositionRef = useRef>({}); const [showScrollTop, setShowScrollTop] = useState(false); const scrollerRef = useRef(null); // 从 localStorage 恢复滚动位置 useEffect(() => { if (renderList.length === 0) return; try { const savedPositions = localStorage.getItem("proxy-scroll-positions"); if (savedPositions) { const positions = JSON.parse(savedPositions); scrollPositionRef.current = positions; const savedPosition = positions[mode]; if (savedPosition !== undefined) { setTimeout(() => { virtuosoRef.current?.scrollTo({ top: savedPosition, behavior: "auto", }); }, 100); } } } catch (e) { console.error("Error restoring scroll position:", e); } }, [mode, renderList]); // 使用防抖保存滚动位置 const saveScrollPosition = useCallback( (scrollTop: number) => { try { scrollPositionRef.current[mode] = scrollTop; localStorage.setItem( "proxy-scroll-positions", JSON.stringify(scrollPositionRef.current), ); } catch (e) { console.error("Error saving scroll position:", e); } }, [mode], ); // 优化滚动处理函数 const handleScroll = useCallback( (e: any) => { const scrollTop = e.target.scrollTop; setShowScrollTop(scrollTop > 100); saveScrollPosition(scrollTop); }, [saveScrollPosition], ); // 添加和清理滚动事件监听器 useEffect(() => { const currentScroller = scrollerRef.current; if (currentScroller) { currentScroller.addEventListener("scroll", handleScroll, { passive: true, }); return () => { currentScroller.removeEventListener("scroll", handleScroll); }; } }, [handleScroll]); // 滚动到顶部 const scrollToTop = useCallback(() => { virtuosoRef.current?.scrollTo?.({ top: 0, behavior: "smooth", }); saveScrollPosition(0); }, [saveScrollPosition]); // 切换分组的节点代理 const handleChangeProxy = useLockFn( async (group: IProxyGroupItem, proxy: IProxyItem) => { if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return; const { name, now } = group; await updateProxy(name, proxy.name); onProxies(); // 断开连接 if (verge?.auto_close_connection) { getConnections().then(({ connections }) => { connections.forEach((conn) => { if (conn.chains.includes(now!)) { deleteConnection(conn.id); } }); }); } // 保存到selected中 if (!current) return; if (!current.selected) current.selected = []; const index = current.selected.findIndex( (item) => item.name === group.name, ); if (index < 0) { current.selected.push({ name, now: proxy.name }); } else { current.selected[index] = { name, now: proxy.name }; } await patchCurrent({ selected: current.selected }); }, ); // 测全部延迟 const handleCheckAll = useLockFn(async (groupName: string) => { const proxies = renderList .filter( (e) => e.group?.name === groupName && (e.type === 2 || e.type === 4), ) .flatMap((e) => e.proxyCol || e.proxy!) .filter(Boolean); const providers = new Set(proxies.map((p) => p!.provider!).filter(Boolean)); if (providers.size) { Promise.allSettled( [...providers].map((p) => providerHealthCheck(p)), ).then(() => onProxies()); } const names = proxies.filter((p) => !p!.provider).map((p) => p!.name); await Promise.race([ delayManager.checkListDelay(names, groupName, timeout), getGroupProxyDelays(groupName, delayManager.getUrl(groupName), timeout), // 查询group delays 将清除fixed(不关注调用结果) ]); onProxies(); }); // 滚到对应的节点 const handleLocation = (group: IProxyGroupItem) => { if (!group) return; const { name, now } = group; const index = renderList.findIndex( (e) => e.group?.name === name && ((e.type === 2 && e.proxy?.name === now) || (e.type === 4 && e.proxyCol?.some((p) => p.name === now))), ); if (index >= 0) { virtuosoRef.current?.scrollToIndex?.({ index, align: "center", behavior: "smooth", }); } }; if (mode === "direct") { return ; } return (
{ scrollerRef.current = ref; }} itemContent={(index) => ( )} />
); };