mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-11 13:02:22 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c13c0370 |
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Install gh-aw extension
|
- name: Install gh-aw extension
|
||||||
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
|
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
|
||||||
with:
|
with:
|
||||||
version: v1.0.1
|
version: v1.2.2
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
|
|
||||||
- 优化连接、规则和日志筛选:切换页面后保留过滤条件
|
- 优化连接、规则和日志筛选:切换页面后保留过滤条件
|
||||||
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
|
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
|
||||||
- 优化代理页面和首页节点卡片,减少延迟更新时的卡顿
|
|
||||||
|
|
||||||
**🍎 macOS**
|
**🍎 macOS**
|
||||||
|
|
||||||
|
|||||||
@@ -605,14 +605,12 @@ export const CurrentProxyCard = () => {
|
|||||||
[getProfileStorageKey],
|
[getProfileStorageKey],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSelectionError = useCallback(
|
const { select } = useProxySelection({
|
||||||
(error: unknown) => {
|
onError: (error) => {
|
||||||
console.error('代理切换失败', error)
|
console.error('代理切换失败', error)
|
||||||
refreshProxy()
|
refreshProxy()
|
||||||
},
|
},
|
||||||
[refreshProxy],
|
})
|
||||||
)
|
|
||||||
const { select } = useProxySelection({ onError: onSelectionError })
|
|
||||||
|
|
||||||
const mode = clashConfig?.mode?.toLowerCase() || 'rule'
|
const mode = clashConfig?.mode?.toLowerCase() || 'rule'
|
||||||
const isGlobalMode = mode === 'global'
|
const isGlobalMode = mode === 'global'
|
||||||
@@ -675,6 +673,7 @@ export const CurrentProxyCard = () => {
|
|||||||
if (proxyView && lastGroupName !== selectedGroupName) {
|
if (proxyView && lastGroupName !== selectedGroupName) {
|
||||||
setLastGroupName(selectedGroupName)
|
setLastGroupName(selectedGroupName)
|
||||||
}
|
}
|
||||||
|
const delays = useGroupDelays(selectedGroupName || null)
|
||||||
|
|
||||||
const selectedGroup = useMemo<ProxyGroupView | null>(() => {
|
const selectedGroup = useMemo<ProxyGroupView | null>(() => {
|
||||||
if (!proxyView || isDirectMode) return null
|
if (!proxyView || isDirectMode) return null
|
||||||
@@ -768,17 +767,10 @@ export const CurrentProxyCard = () => {
|
|||||||
const currentProxy = currentMember ? memberDetails(currentMember) : undefined
|
const currentProxy = currentMember ? memberDetails(currentMember) : undefined
|
||||||
const selectedProxyName = currentMember?.ref.name ?? ''
|
const selectedProxyName = currentMember?.ref.name ?? ''
|
||||||
|
|
||||||
const delays = useGroupDelays(
|
|
||||||
selectedGroupName,
|
|
||||||
currentMember,
|
|
||||||
openSelect === 'proxy',
|
|
||||||
)
|
|
||||||
const currentDelay =
|
const currentDelay =
|
||||||
typeof delays === 'number'
|
currentMember && selectedGroupName
|
||||||
? delays
|
? delayManager.getDelayFix(currentMember, selectedGroupName)
|
||||||
: currentMember
|
: -1
|
||||||
? delays.of(currentMember)
|
|
||||||
: -1
|
|
||||||
|
|
||||||
const signalInfo =
|
const signalInfo =
|
||||||
currentProxy && selectedGroupName
|
currentProxy && selectedGroupName
|
||||||
@@ -900,7 +892,7 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
const proxyOptions = useMemo(
|
const proxyOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isDirectMode || openSelect !== 'proxy' || typeof delays === 'number'
|
isDirectMode || openSelect !== 'proxy'
|
||||||
? []
|
? []
|
||||||
: sortProxyOptions(
|
: sortProxyOptions(
|
||||||
unsortedProxyOptions,
|
unsortedProxyOptions,
|
||||||
|
|||||||
@@ -469,14 +469,12 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
}
|
}
|
||||||
}, [handleScroll])
|
}, [handleScroll])
|
||||||
|
|
||||||
const onSelectionError = useCallback(
|
const { select } = useProxySelection({
|
||||||
(error: unknown) => {
|
onError: (error) => {
|
||||||
console.error('代理切换失败', error)
|
console.error('代理切换失败', error)
|
||||||
onProxies()
|
onProxies()
|
||||||
},
|
},
|
||||||
[onProxies],
|
})
|
||||||
)
|
|
||||||
const { select } = useProxySelection({ onError: onSelectionError })
|
|
||||||
|
|
||||||
const handleChangeProxy = useCallback(
|
const handleChangeProxy = useCallback(
|
||||||
(group: ProxyGroupView, member: ResolvedProxyMember) => {
|
(group: ProxyGroupView, member: ResolvedProxyMember) => {
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ export function filterSort(
|
|||||||
const regex1 = /delay([=<>])(\d+|timeout|error)/i
|
const regex1 = /delay([=<>])(\d+|timeout|error)/i
|
||||||
const regex2 = /type=(.*)/i
|
const regex2 = /type=(.*)/i
|
||||||
|
|
||||||
export const isDelayFilter = (filterText: string) =>
|
|
||||||
regex1.test(filterText.trim())
|
|
||||||
|
|
||||||
function filterProxies(
|
function filterProxies(
|
||||||
proxies: ResolvedMemberOccurrence[],
|
proxies: ResolvedMemberOccurrence[],
|
||||||
groupName: string,
|
groupName: string,
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
import { expect, it, vi } from 'vitest'
|
|
||||||
|
|
||||||
import type { DelaySnapshot } from '@/services/delay'
|
|
||||||
import { resolveMember, type ProxyViewV1 } from '@/types/proxy-view'
|
|
||||||
|
|
||||||
import { filterSort } from './use-filter-sort'
|
|
||||||
import { useRenderList as readRenderList } from './use-render-list'
|
|
||||||
|
|
||||||
const hooks = vi.hoisted(() => {
|
|
||||||
const slots: { value: unknown; deps?: unknown[] }[] = []
|
|
||||||
let cursor = 0
|
|
||||||
return {
|
|
||||||
reset: () => {
|
|
||||||
cursor = 0
|
|
||||||
},
|
|
||||||
memo: (factory: () => unknown, deps: unknown[]) => {
|
|
||||||
const index = cursor++
|
|
||||||
const old = slots[index]
|
|
||||||
if (!old || deps.some((dep, i) => !Object.is(dep, old.deps?.[i]))) {
|
|
||||||
slots[index] = { value: factory(), deps }
|
|
||||||
}
|
|
||||||
return slots[index].value
|
|
||||||
},
|
|
||||||
ref: (current: unknown) => {
|
|
||||||
const index = cursor++
|
|
||||||
slots[index] ??= { value: { current } }
|
|
||||||
return slots[index].value
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const state = vi.hoisted(() => ({
|
|
||||||
view: null as ProxyViewV1 | null,
|
|
||||||
delays: new Map<string, DelaySnapshot>(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('react', () => ({
|
|
||||||
useMemo: hooks.memo,
|
|
||||||
useRef: hooks.ref,
|
|
||||||
useEffect: () => {},
|
|
||||||
}))
|
|
||||||
vi.mock('@/hooks/use-profiles', () => ({}))
|
|
||||||
vi.mock('@/hooks/use-clash', () => ({
|
|
||||||
useRuntimeConfig: () => ({ data: null }),
|
|
||||||
}))
|
|
||||||
vi.mock('@/hooks/use-verge', () => ({
|
|
||||||
useVerge: () => ({ verge: { proxy_layout_column: 1 } }),
|
|
||||||
}))
|
|
||||||
vi.mock('@/providers/app-data-context', () => ({
|
|
||||||
useProxiesData: () => ({ proxyView: state.view }),
|
|
||||||
useAppRefreshers: () => ({ refreshProxy: () => {} }),
|
|
||||||
}))
|
|
||||||
vi.mock('@/hooks/use-group-delays', () => ({
|
|
||||||
useGroupsDelays: (names: string[]) =>
|
|
||||||
new Map(names.map((name) => [name, state.delays.get(name)])),
|
|
||||||
}))
|
|
||||||
vi.mock('./use-window-width', () => ({
|
|
||||||
useWindowWidth: () => ({ width: 800 }),
|
|
||||||
}))
|
|
||||||
vi.mock('./use-head-state', async (original) => {
|
|
||||||
const actual = await original<typeof import('./use-head-state')>()
|
|
||||||
const heads = {
|
|
||||||
plain: { ...actual.DEFAULT_STATE, open: true },
|
|
||||||
sorted: { ...actual.DEFAULT_STATE, open: true, sortType: 1 },
|
|
||||||
}
|
|
||||||
return { ...actual, useHeadStateNew: () => [heads, () => {}] }
|
|
||||||
})
|
|
||||||
vi.mock('./use-filter-sort', async (original) => {
|
|
||||||
const actual = await original<typeof import('./use-filter-sort')>()
|
|
||||||
return { ...actual, filterSort: vi.fn(actual.filterSort) }
|
|
||||||
})
|
|
||||||
vi.mock('@/types/proxy-view', async (original) => {
|
|
||||||
const actual = await original<typeof import('@/types/proxy-view')>()
|
|
||||||
return { ...actual, resolveMember: vi.fn(actual.resolveMember) }
|
|
||||||
})
|
|
||||||
|
|
||||||
it('skips delay-independent projections and reuses members for delay sorting', () => {
|
|
||||||
const group = (name: string) => ({
|
|
||||||
name,
|
|
||||||
type: 'Selector',
|
|
||||||
history: [],
|
|
||||||
udp: false,
|
|
||||||
xudp: false,
|
|
||||||
tfo: false,
|
|
||||||
mptcp: false,
|
|
||||||
smux: false,
|
|
||||||
members: Array.from({ length: 500 }, (_, i) => ({
|
|
||||||
kind: 'unresolved' as const,
|
|
||||||
name: `${i}`,
|
|
||||||
reason: 'missing' as const,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
state.view = {
|
|
||||||
schemaVersion: 1,
|
|
||||||
orderSource: 'runtime',
|
|
||||||
providerState: 'ready',
|
|
||||||
global: null,
|
|
||||||
direct: null,
|
|
||||||
groups: [group('plain'), group('sorted')],
|
|
||||||
records: {},
|
|
||||||
standalone: [],
|
|
||||||
providers: [],
|
|
||||||
}
|
|
||||||
const render = () => {
|
|
||||||
hooks.reset()
|
|
||||||
return readRenderList('rule')
|
|
||||||
}
|
|
||||||
state.delays.set('plain', { of: () => 42 })
|
|
||||||
state.delays.set('sorted', { of: () => 42 })
|
|
||||||
render()
|
|
||||||
vi.mocked(filterSort).mockClear()
|
|
||||||
vi.mocked(resolveMember).mockClear()
|
|
||||||
state.delays.set('plain', { of: () => 43 })
|
|
||||||
render()
|
|
||||||
expect(filterSort).toHaveBeenCalledTimes(0)
|
|
||||||
state.delays.set('sorted', { of: () => 43 })
|
|
||||||
render()
|
|
||||||
expect(filterSort).toHaveBeenCalledTimes(1)
|
|
||||||
expect(resolveMember).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from '@/types/proxy-view'
|
} from '@/types/proxy-view'
|
||||||
import { debugLog } from '@/utils/debug'
|
import { debugLog } from '@/utils/debug'
|
||||||
|
|
||||||
import { filterSort, isDelayFilter } from './use-filter-sort'
|
import { filterSort } from './use-filter-sort'
|
||||||
import {
|
import {
|
||||||
DEFAULT_STATE,
|
DEFAULT_STATE,
|
||||||
useHeadStateNew,
|
useHeadStateNew,
|
||||||
@@ -60,14 +60,14 @@ export const hasRenderableItems = (
|
|||||||
): boolean => renderList.some((item) => item.type !== 0 || !item.group.hidden)
|
): boolean => renderList.some((item) => item.type !== 0 || !item.group.hidden)
|
||||||
|
|
||||||
type GroupCache = {
|
type GroupCache = {
|
||||||
group: ProxyGroupView
|
now: string | undefined
|
||||||
|
members: ProxyGroupView['members']
|
||||||
headState: HeadState
|
headState: HeadState
|
||||||
col: number
|
col: number
|
||||||
latencyTimeout: number | undefined
|
latencyTimeout: number | undefined
|
||||||
/// This group's own delays. Compared by identity so that a test settling in one group
|
/// This group's own delays. Compared by identity so that a test settling in one group
|
||||||
/// does not throw away every other group's sorted order.
|
/// does not throw away every other group's sorted order.
|
||||||
delays: DelaySnapshot | undefined
|
delays: DelaySnapshot | undefined
|
||||||
occurrences: ResolvedMemberOccurrence[] | undefined
|
|
||||||
items: IRenderItem[]
|
items: IRenderItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,51 +222,19 @@ export const useRenderList = (
|
|||||||
return current?.kind === 'group' ? [global, current.group] : [global]
|
return current?.kind === 'group' ? [global, current.group] : [global]
|
||||||
}, [mode, proxyView])
|
}, [mode, proxyView])
|
||||||
|
|
||||||
const repeatedNameGroups = useMemo(() => {
|
// Every group this list draws, so a test settling in any of them re-sorts that group.
|
||||||
const repeated = new Set<string>()
|
|
||||||
if (isChainMode) return repeated
|
|
||||||
for (const group of renderGroups) {
|
|
||||||
const names = new Set<string>()
|
|
||||||
if (
|
|
||||||
group.members.some(({ name }) => {
|
|
||||||
if (names.has(name)) return true
|
|
||||||
names.add(name)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
repeated.add(group.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return repeated
|
|
||||||
}, [isChainMode, renderGroups])
|
|
||||||
|
|
||||||
// Rows own delay updates unless repeated names share the current item listener.
|
|
||||||
const renderedGroupNames = useMemo(() => {
|
const renderedGroupNames = useMemo(() => {
|
||||||
if (!proxyView || isChainMode) return []
|
if (!proxyView) return []
|
||||||
return renderGroups
|
if (isChainMode)
|
||||||
.filter((group) => {
|
return selectedGroup ? [selectedGroup] : [CHAIN_DELAY_GROUP]
|
||||||
const state = headStates[group.name] || DEFAULT_STATE
|
return renderGroups.map(({ name }) => name)
|
||||||
return (
|
}, [isChainMode, proxyView, renderGroups, selectedGroup])
|
||||||
(state.open || group === proxyView.global) &&
|
|
||||||
(state.sortType === 1 ||
|
|
||||||
isDelayFilter(state.filterText) ||
|
|
||||||
repeatedNameGroups.has(group.name))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(({ name }) => name)
|
|
||||||
}, [headStates, isChainMode, proxyView, renderGroups, repeatedNameGroups])
|
|
||||||
const groupDelays = useGroupsDelays(renderedGroupNames)
|
const groupDelays = useGroupsDelays(renderedGroupNames)
|
||||||
|
|
||||||
const groupCacheRef = useRef<Map<string, GroupCache> | null>(null)
|
const groupCacheRef = useRef<Map<string, GroupCache>>(new Map())
|
||||||
const cachedViewRef = useRef(proxyView)
|
|
||||||
const prevListRef = useRef<IRenderItem[]>([])
|
const prevListRef = useRef<IRenderItem[]>([])
|
||||||
|
|
||||||
const renderList = useMemo<IRenderItem[]>(() => {
|
const renderList = useMemo<IRenderItem[]>(() => {
|
||||||
const cache = (groupCacheRef.current ??= new Map())
|
|
||||||
if (cachedViewRef.current !== proxyView) {
|
|
||||||
cache.clear()
|
|
||||||
cachedViewRef.current = proxyView
|
|
||||||
}
|
|
||||||
if (!proxyView) return []
|
if (!proxyView) return []
|
||||||
|
|
||||||
if (isChainMode) {
|
if (isChainMode) {
|
||||||
@@ -301,13 +269,16 @@ export const useRenderList = (
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cache = groupCacheRef.current
|
||||||
|
|
||||||
const retList = renderGroups.flatMap((group) => {
|
const retList = renderGroups.flatMap((group) => {
|
||||||
const isFlat = group === proxyView.global
|
const isFlat = group === proxyView.global
|
||||||
const headState = headStates[group.name] || DEFAULT_STATE
|
const headState = headStates[group.name] || DEFAULT_STATE
|
||||||
const cached = cache.get(group.name)
|
const cached = cache.get(group.name)
|
||||||
if (
|
if (
|
||||||
cached &&
|
cached &&
|
||||||
cached.group === group &&
|
cached.now === group.now &&
|
||||||
|
cached.members === group.members &&
|
||||||
cached.headState === headState &&
|
cached.headState === headState &&
|
||||||
cached.col === col &&
|
cached.col === col &&
|
||||||
cached.latencyTimeout === latencyTimeout &&
|
cached.latencyTimeout === latencyTimeout &&
|
||||||
@@ -329,18 +300,9 @@ export const useRenderList = (
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Duplicate names share one item listener until the delay-store fix lands.
|
|
||||||
const refreshRepeated =
|
|
||||||
repeatedNameGroups.has(group.name) &&
|
|
||||||
cached?.delays !== groupDelays.get(group.name)
|
|
||||||
let resolved =
|
|
||||||
cached?.group === group && !refreshRepeated
|
|
||||||
? cached.occurrences
|
|
||||||
: undefined
|
|
||||||
if (headState.open || isFlat) {
|
if (headState.open || isFlat) {
|
||||||
resolved ??= resolveOccurrences(proxyView, group)
|
|
||||||
const occurrences = filterSort(
|
const occurrences = filterSort(
|
||||||
resolved,
|
resolveOccurrences(proxyView, group),
|
||||||
group.name,
|
group.name,
|
||||||
headState.filterText,
|
headState.filterText,
|
||||||
headState.sortType,
|
headState.sortType,
|
||||||
@@ -375,17 +337,15 @@ export const useRenderList = (
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
resolved = undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.set(group.name, {
|
cache.set(group.name, {
|
||||||
group,
|
now: group.now!,
|
||||||
|
members: group.members,
|
||||||
headState,
|
headState,
|
||||||
col,
|
col,
|
||||||
latencyTimeout: latencyTimeout!,
|
latencyTimeout: latencyTimeout!,
|
||||||
delays: groupDelays.get(group.name),
|
delays: groupDelays.get(group.name),
|
||||||
occurrences: resolved,
|
|
||||||
items: ret,
|
items: ret,
|
||||||
})
|
})
|
||||||
return ret
|
return ret
|
||||||
@@ -411,7 +371,6 @@ export const useRenderList = (
|
|||||||
mode,
|
mode,
|
||||||
proxyView,
|
proxyView,
|
||||||
renderGroups,
|
renderGroups,
|
||||||
repeatedNameGroups,
|
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import { useSyncExternalStore } from 'react'
|
|
||||||
import { expect, it, vi } from 'vitest'
|
|
||||||
|
|
||||||
import delayManager from '@/services/delay'
|
|
||||||
import type { ResolvedProxyMember } from '@/types/proxy-view'
|
|
||||||
|
|
||||||
import { useGroupDelays } from './use-group-delays'
|
|
||||||
|
|
||||||
vi.mock('react', () => ({
|
|
||||||
useCallback: (callback: unknown) => callback,
|
|
||||||
useSyncExternalStore: vi.fn((_subscribe, read) => read()),
|
|
||||||
}))
|
|
||||||
|
|
||||||
it('keeps the closed-menu snapshot unchanged when another member settles', () => {
|
|
||||||
const member: ResolvedProxyMember = {
|
|
||||||
kind: 'node',
|
|
||||||
ref: { kind: 'node', name: 'current', recordId: 'current' },
|
|
||||||
node: {
|
|
||||||
recordId: 'current',
|
|
||||||
name: 'current',
|
|
||||||
type: 'Shadowsocks',
|
|
||||||
source: { kind: 'core', proxyName: 'current' },
|
|
||||||
history: [],
|
|
||||||
udp: false,
|
|
||||||
xudp: false,
|
|
||||||
tfo: false,
|
|
||||||
mptcp: false,
|
|
||||||
smux: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
vi.spyOn(delayManager, 'getDelayFix').mockReturnValue(42)
|
|
||||||
const snapshot = vi.spyOn(delayManager, 'groupDelays')
|
|
||||||
snapshot.mockReturnValue({ of: () => 42 })
|
|
||||||
useGroupDelays('group', member, false)
|
|
||||||
const [, read] = vi.mocked(useSyncExternalStore).mock.calls.at(-1)!
|
|
||||||
let previous = read()
|
|
||||||
let changes = 0
|
|
||||||
for (let i = 0; i < 120; i++) {
|
|
||||||
snapshot.mockReturnValue({ of: () => 42 })
|
|
||||||
const next = read()
|
|
||||||
if (!Object.is(next, previous)) changes++
|
|
||||||
previous = next
|
|
||||||
}
|
|
||||||
expect(changes).toBe(0)
|
|
||||||
})
|
|
||||||
@@ -1,27 +1,19 @@
|
|||||||
import { useCallback, useSyncExternalStore } from 'react'
|
import { useCallback, useSyncExternalStore } from 'react'
|
||||||
|
|
||||||
import delayManager, { type DelaySnapshot } from '@/services/delay'
|
import delayManager, { type DelaySnapshot } from '@/services/delay'
|
||||||
import type { ResolvedProxyMember } from '@/types/proxy-view'
|
|
||||||
|
|
||||||
/** A closed menu observes only its current member, not every settled group delay. */
|
const NO_DELAYS: DelaySnapshot = { of: () => -1 }
|
||||||
export const useGroupDelays = (
|
|
||||||
group: string | null,
|
/** Exposes the external delay store to React, updating sort order only after a test settles. */
|
||||||
member: ResolvedProxyMember | undefined,
|
export const useGroupDelays = (group: string | null): DelaySnapshot => {
|
||||||
menuOpen: boolean,
|
|
||||||
): DelaySnapshot | number => {
|
|
||||||
const subscribe = useCallback(
|
const subscribe = useCallback(
|
||||||
(onSettle: () => void) =>
|
(onSettle: () => void) =>
|
||||||
group ? delayManager.addGroupListener(group, onSettle) : () => {},
|
group ? delayManager.addGroupListener(group, onSettle) : () => {},
|
||||||
[group],
|
[group],
|
||||||
)
|
)
|
||||||
const read = useCallback(
|
const read = useCallback(
|
||||||
() =>
|
() => (group ? delayManager.groupDelays(group) : NO_DELAYS),
|
||||||
group && menuOpen
|
[group],
|
||||||
? delayManager.groupDelays(group)
|
|
||||||
: group && member
|
|
||||||
? delayManager.getDelayFix(member, group)
|
|
||||||
: -1,
|
|
||||||
[group, member, menuOpen],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return useSyncExternalStore(subscribe, read, read)
|
return useSyncExternalStore(subscribe, read, read)
|
||||||
|
|||||||
Reference in New Issue
Block a user