From 0f5d3f38da8ca12f90cb92ee57a33af55fd197a8 Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 16 Jan 2026 14:27:21 +0800 Subject: [PATCH] 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. --- .../workflow/skill/file-tree/index.tsx | 5 +- .../skill/hooks/use-reveal-active-tab.ts | 47 ---------------- .../hooks/use-sync-tree-with-active-tab.ts | 54 +++++++++++++++++++ 3 files changed, 56 insertions(+), 50 deletions(-) delete mode 100644 web/app/components/workflow/skill/hooks/use-reveal-active-tab.ts create mode 100644 web/app/components/workflow/skill/hooks/use-sync-tree-with-active-tab.ts diff --git a/web/app/components/workflow/skill/file-tree/index.tsx b/web/app/components/workflow/skill/file-tree/index.tsx index 3eb4a7f022..d21c1adb43 100644 --- a/web/app/components/workflow/skill/file-tree/index.tsx +++ b/web/app/components/workflow/skill/file-tree/index.tsx @@ -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 = ({ className }) => { }) }, [appId, renameNode, t]) - useRevealActiveTab({ + useSyncTreeWithActiveTab({ treeRef, activeTabId, - treeChildren: treeData?.children, }) if (isLoading) { diff --git a/web/app/components/workflow/skill/hooks/use-reveal-active-tab.ts b/web/app/components/workflow/skill/hooks/use-reveal-active-tab.ts deleted file mode 100644 index d1349067c2..0000000000 --- a/web/app/components/workflow/skill/hooks/use-reveal-active-tab.ts +++ /dev/null @@ -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 | 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]) -} diff --git a/web/app/components/workflow/skill/hooks/use-sync-tree-with-active-tab.ts b/web/app/components/workflow/skill/hooks/use-sync-tree-with-active-tab.ts new file mode 100644 index 0000000000..73887302f2 --- /dev/null +++ b/web/app/components/workflow/skill/hooks/use-sync-tree-with-active-tab.ts @@ -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 | 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]) +}