From 943e18e8cafb121b6d7375d01237ee6c4379c896 Mon Sep 17 00:00:00 2001 From: wonfen Date: Tue, 3 Nov 2026 21:46:48 +0800 Subject: [PATCH] fix(proxies): list only GLOBAL members in global mode Reveal a group once GLOBAL selects it; hide DIRECT and REJECT unless selected. --- src/components/proxy/proxy-groups.tsx | 29 ++++++++++++- src/components/proxy/use-render-list.ts | 54 ++++++++++++++++--------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index a5e07c6e4..91460d49a 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -183,6 +183,7 @@ function useProxyRenderState( renderList, onProxies, onHeadState, + setHeadState: setListHeadState, handleCheckAll, saveScrollPosition, getScrollPosition, @@ -377,12 +378,17 @@ function NormalProxyGroups(props: { mode: string }) { renderList, onProxies, onHeadState, + setHeadState, handleCheckAll, getScrollPosition, saveScrollPosition, } = useProxyRenderState(mode, false, null) const emptyList = useEmptyRenderList() const { onDragEnd: onHeaderDragEnd } = useProxyGroupHeaderLayout() + const { proxyView } = useProxiesData() + const globalGroupName = proxyView?.global?.name + // The revealed group sits below GLOBAL's rows, so a click must bring it into view. + const pendingRevealRef = useRef(null) const renderFirstRef = useRef(true) // Do not persist intermediate positions produced while restoring virtual scroll. const isRestoringRef = useRef(false) @@ -477,9 +483,17 @@ function NormalProxyGroups(props: { mode: string }) { if (!['Selector', 'URLTest', 'Fallback'].includes(group.type)) return if (!isInteractableMember(member)) return + if (group.name === globalGroupName) { + pendingRevealRef.current = + member.kind === 'group' ? member.ref.name : null + // The selection refetches the view, so skip onHeadState's refetch. + if (member.kind === 'group') { + setHeadState(member.ref.name, { open: true }) + } + } select(group.name, member.ref.name, group.now!, group.fixed!) }, - [select], + [globalGroupName, select, setHeadState], ) const handleLocation = useStableCallback((group: ProxyGroupView) => { @@ -525,6 +539,17 @@ function NormalProxyGroups(props: { mode: string }) { return Array.from(new Set(names)) }, [renderList]) + useEffect(() => { + if (mode !== 'global') { + pendingRevealRef.current = null + return + } + const groupName = pendingRevealRef.current + if (!groupName || !proxyGroupNames.includes(groupName)) return + pendingRevealRef.current = null + handleGroupLocationByName(groupName) + }, [handleGroupLocationByName, mode, proxyGroupNames]) + const handleGroupToggle = useCallback( async (group: ProxyGroupView) => { const index = renderList.findIndex( @@ -608,7 +633,7 @@ function NormalProxyGroups(props: { mode: string }) { /> - {(mode === 'rule' || mode === 'global') && ( + {mode === 'rule' && ( member: resolveMember(view, member), })) +// mihomo selects DIRECT in GLOBAL by default, so the selected built-in stays visible. +const HIDDEN_GLOBAL_BUILTINS = new Set(['DIRECT', 'REJECT']) + +const isHiddenGlobalBuiltin = ( + group: ProxyGroupView, + { member }: ResolvedMemberOccurrence, +) => + member.kind === 'node' && + member.node.source.kind === 'core' && + HIDDEN_GLOBAL_BUILTINS.has(member.node.source.proxyName) && + member.ref.name !== group.now + const memberKey = ( group: ProxyGroupView, occurrence: ResolvedMemberOccurrence, @@ -209,8 +222,10 @@ export const useRenderList = ( const renderGroups = useMemo(() => { if (!proxyView) return [] if (mode === 'rule') return proxyView.groups - const global = proxyView.global ? [proxyView.global] : [] - return mode === 'global' ? [...global, ...proxyView.groups] : global + const { global } = proxyView + if (!global) return [] + const current = findCurrentGroupMember(proxyView, global)?.member + return current?.kind === 'group' ? [global, current.group] : [global] }, [mode, proxyView]) // Every group this list draws, so a test settling in any of them re-sorts that group. @@ -260,10 +275,10 @@ export const useRenderList = ( })) } - const useRule = mode === 'rule' || mode === 'global' const cache = groupCacheRef.current const retList = renderGroups.flatMap((group) => { + const isFlat = group === proxyView.global const headState = headStates[group.name] || DEFAULT_STATE const cached = cache.get(group.name) if ( @@ -279,19 +294,24 @@ export const useRenderList = ( } const ret: IRenderItem[] = [ - { - type: 0, - key: group.name, - group, - headState, - icon: group.icon!, - testUrl: group.testUrl!, - }, + isFlat + ? { type: 1, key: `head-${group.name}`, group, headState } + : { + type: 0, + key: group.name, + group, + headState, + icon: group.icon!, + testUrl: group.testUrl!, + }, ] - if (headState.open || !useRule) { + if (headState.open || isFlat) { + const resolved = resolveOccurrences(proxyView, group) const occurrences = filterSort( - resolveOccurrences(proxyView, group), + isFlat + ? resolved.filter((item) => !isHiddenGlobalBuiltin(group, item)) + : resolved, group.name, headState.filterText, headState.sortType, @@ -302,9 +322,6 @@ export const useRenderList = ( useRegularExpression: headState.filterUseRegularExpression, }, ) - if (!useRule) { - ret.push({ type: 1, key: `head-${group.name}`, group, headState }) - } if (occurrences.length === 0) { ret.push({ type: 3, key: `empty-${group.name}`, group, headState }) } else if (col > 1) { @@ -343,9 +360,8 @@ export const useRenderList = ( return ret }) - const filtered = !useRule - ? retList.slice(1) - : retList.filter((item) => !item.group.hidden) + const filtered = + mode === 'rule' ? retList.filter((item) => !item.group.hidden) : retList if ( prevListRef.current.length === filtered.length && filtered.every((item, index) => item === prevListRef.current[index])