refactor(skill): use node.parent chain for ancestor traversal

Replace getAncestorIds(treeData) with node.parent chain traversal
for more efficient ancestor lookup. This avoids re-traversing the
tree data structure and uses react-arborist's built-in parent refs.

Also rename hook to useSyncTreeWithActiveTab for clarity.
This commit is contained in:
yyh
2026-01-16 14:27:21 +08:00
parent 76da178cc1
commit 0f5d3f38da
3 changed files with 56 additions and 50 deletions

View File

@ -16,8 +16,8 @@ import Toast from '@/app/components/base/toast'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useRenameAppAssetNode } from '@/service/use-app-asset'
import { cn } from '@/utils/classnames'
import { useRevealActiveTab } from '../hooks/use-reveal-active-tab'
import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree'
import { useSyncTreeWithActiveTab } from '../hooks/use-sync-tree-with-active-tab'
import TreeContextMenu from './tree-context-menu'
import TreeNode from './tree-node'
@ -85,10 +85,9 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
})
}, [appId, renameNode, t])
useRevealActiveTab({
useSyncTreeWithActiveTab({
treeRef,
activeTabId,
treeChildren: treeData?.children,
})
if (isLoading) {

View File

@ -1,47 +0,0 @@
'use client'
import type { TreeApi } from 'react-arborist'
import type { TreeNodeData } from '../type'
import type { AppAssetTreeView } from '@/types/app-asset'
import { useEffect } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { getAncestorIds } from '../utils/tree-utils'
type UseRevealActiveTabOptions = {
treeRef: React.RefObject<TreeApi<TreeNodeData> | null>
activeTabId: string | null
treeChildren: AppAssetTreeView[] | undefined
}
/**
* Hook that handles revealing the active tab in the file tree.
* Expands ancestor folders and scrolls to the active node.
*/
export function useRevealActiveTab({
treeRef,
activeTabId,
treeChildren,
}: UseRevealActiveTabOptions): void {
const storeApi = useWorkflowStore()
useEffect(() => {
if (!activeTabId || !treeChildren)
return
const tree = treeRef.current
if (!tree)
return
const ancestors = getAncestorIds(activeTabId, treeChildren)
if (ancestors.length > 0)
storeApi.getState().revealFile(ancestors)
requestAnimationFrame(() => {
const node = tree.get(activeTabId)
if (node) {
tree.openParents(node)
tree.scrollTo(activeTabId)
}
})
}, [activeTabId, treeChildren, storeApi, treeRef])
}

View File

@ -0,0 +1,54 @@
'use client'
import type { TreeApi } from 'react-arborist'
import type { TreeNodeData } from '../type'
import { useEffect } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
type UseSyncTreeWithActiveTabOptions = {
treeRef: React.RefObject<TreeApi<TreeNodeData> | null>
activeTabId: string | null
}
/**
* Hook that synchronizes the file tree with the active tab.
* Expands ancestor folders and scrolls to the active node.
*
* Uses node.parent chain for efficient ancestor traversal instead of
* re-traversing the tree data structure.
*/
export function useSyncTreeWithActiveTab({
treeRef,
activeTabId,
}: UseSyncTreeWithActiveTabOptions): void {
const storeApi = useWorkflowStore()
useEffect(() => {
if (!activeTabId)
return
const tree = treeRef.current
if (!tree)
return
requestAnimationFrame(() => {
const node = tree.get(activeTabId)
if (!node)
return
// Traverse parent chain to collect ancestor folder IDs
const ancestors: string[] = []
let current = node.parent
while (current && !current.isRoot) {
ancestors.push(current.id)
current = current.parent
}
if (ancestors.length > 0)
storeApi.getState().revealFile(ancestors)
tree.openParents(node)
tree.scrollTo(activeTabId)
})
}, [activeTabId, treeRef, storeApi])
}