diff --git a/web/app/components/workflow/skill/files.tsx b/web/app/components/workflow/skill/files.tsx index 85504d6a68..936da07c1c 100644 --- a/web/app/components/workflow/skill/files.tsx +++ b/web/app/components/workflow/skill/files.tsx @@ -1,6 +1,7 @@ 'use client' import type { NodeApi, TreeApi } from 'react-arborist' +import type { OpensObject } from './store' import type { TreeNodeData } from './type' import { RiDragDropLine } from '@remixicon/react' import * as React from 'react' @@ -15,7 +16,7 @@ import { cn } from '@/utils/classnames' import FileTreeContextMenu from './file-tree-context-menu' import FileTreeNode from './file-tree-node' import { useSkillEditorStore, useSkillEditorStoreApi } from './store' -import { getAncestorIds, toOpensObject } from './utils/tree-utils' +import { getAncestorIds } from './utils/tree-utils' type FilesProps = { className?: string @@ -48,7 +49,11 @@ const Files: React.FC = ({ className }) => { const renameNode = useRenameAppAssetNode() - const initialOpenState = useMemo(() => toOpensObject(expandedFolderIds), [expandedFolderIds]) + const initialOpensObject = useMemo(() => { + return Object.fromEntries( + [...expandedFolderIds].map(id => [id, true]), + ) + }, [expandedFolderIds]) const handleToggle = useCallback((id: string) => { storeApi.getState().toggleFolder(id) @@ -75,31 +80,23 @@ const Files: React.FC = ({ className }) => { }, [appId, renameNode, t]) useEffect(() => { - if (!activeTabId || !treeData?.children || !treeRef.current) + if (!activeTabId || !treeData?.children) + return + + const tree = treeRef.current + if (!tree) return const ancestors = getAncestorIds(activeTabId, treeData.children) - if (ancestors.length > 0) storeApi.getState().revealFile(ancestors) - - const timeoutId = setTimeout(() => { - const tree = treeRef.current - if (!tree) - return - - for (const ancestorId of ancestors) { - const ancestorNode = tree.get(ancestorId) - if (ancestorNode && !ancestorNode.isOpen) - ancestorNode.open() - } - + requestAnimationFrame(() => { const node = tree.get(activeTabId) - if (node) - node.select() - }, 0) - - return () => clearTimeout(timeoutId) + if (node) { + tree.openParents(node) + tree.scrollTo(activeTabId) + } + }) }, [activeTabId, treeData?.children, storeApi]) if (isLoading) { @@ -146,8 +143,8 @@ const Files: React.FC = ({ className }) => { rowHeight={24} indent={20} overscanCount={5} - initialOpenState={initialOpenState} selection={activeTabId ?? undefined} + initialOpenState={initialOpensObject} onToggle={handleToggle} onActivate={handleActivate} onRename={handleRename} diff --git a/web/app/components/workflow/skill/store/index.ts b/web/app/components/workflow/skill/store/index.ts index 8daea51961..50d52d8ffd 100644 --- a/web/app/components/workflow/skill/store/index.ts +++ b/web/app/components/workflow/skill/store/index.ts @@ -65,11 +65,15 @@ export const createTabSlice: StateCreator = (set, get) => ({ }, }) +export type OpensObject = Record + export type FileTreeSliceShape = { expandedFolderIds: Set setExpandedFolderIds: (ids: Set) => void toggleFolder: (folderId: string) => void revealFile: (ancestorFolderIds: string[]) => void + setExpandedFromOpens: (opens: OpensObject) => void + getOpensObject: () => OpensObject } export const createFileTreeSlice: StateCreator = (set, get) => ({ @@ -96,6 +100,22 @@ export const createFileTreeSlice: StateCreator = (set, get) ancestorFolderIds.forEach(id => newSet.add(id)) set({ expandedFolderIds: newSet }) }, + + setExpandedFromOpens: (opens: OpensObject) => { + const newSet = new Set( + Object.entries(opens) + .filter(([_, isOpen]) => isOpen) + .map(([id]) => id), + ) + set({ expandedFolderIds: newSet }) + }, + + getOpensObject: () => { + const { expandedFolderIds } = get() + return Object.fromEntries( + [...expandedFolderIds].map(id => [id, true]), + ) + }, }) export type DirtySliceShape = {