diff --git a/web/app/components/workflow/skill/file-node-menu.tsx b/web/app/components/workflow/skill/file-tree/file-node-menu.tsx similarity index 96% rename from web/app/components/workflow/skill/file-node-menu.tsx rename to web/app/components/workflow/skill/file-tree/file-node-menu.tsx index d31be13a27..d0ce4fee3a 100644 --- a/web/app/components/workflow/skill/file-node-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/file-node-menu.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { NodeApi, TreeApi } from 'react-arborist' -import type { TreeNodeData } from './type' +import type { TreeNodeData } from '../type' import { RiDeleteBinLine, RiEdit2Line, @@ -11,7 +11,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' import { cn } from '@/utils/classnames' -import { useFileOperations } from './hooks/use-file-operations' +import { useFileOperations } from '../hooks/use-file-operations' type MenuItemProps = { icon: React.ElementType diff --git a/web/app/components/workflow/skill/folder-node-menu.tsx b/web/app/components/workflow/skill/file-tree/folder-node-menu.tsx similarity index 97% rename from web/app/components/workflow/skill/folder-node-menu.tsx rename to web/app/components/workflow/skill/file-tree/folder-node-menu.tsx index f702e5d06e..e6c5d076d5 100644 --- a/web/app/components/workflow/skill/folder-node-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/folder-node-menu.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { NodeApi, TreeApi } from 'react-arborist' -import type { TreeNodeData } from './type' +import type { TreeNodeData } from '../type' import { RiDeleteBinLine, RiEdit2Line, @@ -15,7 +15,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' import { cn } from '@/utils/classnames' -import { useFileOperations } from './hooks/use-file-operations' +import { useFileOperations } from '../hooks/use-file-operations' type MenuItemProps = { icon: React.ElementType diff --git a/web/app/components/workflow/skill/file-tree.tsx b/web/app/components/workflow/skill/file-tree/index.tsx similarity index 94% rename from web/app/components/workflow/skill/file-tree.tsx rename to web/app/components/workflow/skill/file-tree/index.tsx index dd3c48d76d..7c825d1fd0 100644 --- a/web/app/components/workflow/skill/file-tree.tsx +++ b/web/app/components/workflow/skill/file-tree/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { NodeApi, TreeApi } from 'react-arborist' -import type { OpensObject } from './store' -import type { TreeNodeData } from './type' +import type { OpensObject } from '../store' +import type { TreeNodeData } from '../type' import { RiDragDropLine } from '@remixicon/react' import { useIsMutating } from '@tanstack/react-query' import { useSize } from 'ahooks' @@ -15,11 +15,11 @@ import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' import { useRenameAppAssetNode } from '@/service/use-app-asset' import { cn } from '@/utils/classnames' -import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree' -import { useSkillEditorStore, useSkillEditorStoreApi } from './store' +import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree' +import { useSkillEditorStore, useSkillEditorStoreApi } from '../store' +import { getAncestorIds } from '../utils/tree-utils' import TreeContextMenu from './tree-context-menu' import TreeNode from './tree-node' -import { getAncestorIds } from './utils/tree-utils' type FileTreeProps = { className?: string diff --git a/web/app/components/workflow/skill/tree-context-menu.tsx b/web/app/components/workflow/skill/file-tree/tree-context-menu.tsx similarity index 87% rename from web/app/components/workflow/skill/tree-context-menu.tsx rename to web/app/components/workflow/skill/file-tree/tree-context-menu.tsx index e1b4913895..5d4233650e 100644 --- a/web/app/components/workflow/skill/tree-context-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/tree-context-menu.tsx @@ -2,15 +2,15 @@ import type { FC } from 'react' import type { TreeApi } from 'react-arborist' -import type { TreeNodeData } from './type' +import type { TreeNodeData } from '../type' import { useClickAway } from 'ahooks' import * as React from 'react' import { useCallback, useMemo, useRef } from 'react' +import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree' +import { useSkillEditorStore, useSkillEditorStoreApi } from '../store' +import { findNodeById } from '../utils/tree-utils' import FileNodeMenu from './file-node-menu' import FolderNodeMenu from './folder-node-menu' -import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree' -import { useSkillEditorStore, useSkillEditorStoreApi } from './store' -import { findNodeById } from './utils/tree-utils' type TreeContextMenuProps = { treeRef: React.RefObject | null> diff --git a/web/app/components/workflow/skill/file-tree/tree-edit-input.tsx b/web/app/components/workflow/skill/file-tree/tree-edit-input.tsx new file mode 100644 index 0000000000..1c8d3cacb3 --- /dev/null +++ b/web/app/components/workflow/skill/file-tree/tree-edit-input.tsx @@ -0,0 +1,48 @@ +'use client' + +import type { NodeApi } from 'react-arborist' +import type { TreeNodeData } from '../type' +import * as React from 'react' +import { useEffect, useRef } from 'react' + +type TreeEditInputProps = { + node: NodeApi +} + +const TreeEditInput: React.FC = ({ node }) => { + const inputRef = useRef(null) + + useEffect(() => { + inputRef.current?.focus() + inputRef.current?.select() + }, []) + + const handleKeyDown = (e: React.KeyboardEvent) => { + e.stopPropagation() + if (e.key === 'Escape') { + node.reset() + } + else if (e.key === 'Enter') { + e.preventDefault() + node.submit(inputRef.current?.value || '') + } + } + + const handleBlur = () => { + node.reset() + } + + return ( + e.stopPropagation()} + className="min-w-0 flex-1 rounded border border-components-input-border-active bg-transparent px-1 text-[13px] font-normal leading-4 text-text-primary outline-none" + /> + ) +} + +export default React.memo(TreeEditInput) diff --git a/web/app/components/workflow/skill/file-tree/tree-guide-lines.tsx b/web/app/components/workflow/skill/file-tree/tree-guide-lines.tsx new file mode 100644 index 0000000000..35da9ab509 --- /dev/null +++ b/web/app/components/workflow/skill/file-tree/tree-guide-lines.tsx @@ -0,0 +1,28 @@ +'use client' + +import * as React from 'react' + +const INDENT_SIZE = 20 + +type TreeGuideLinesProps = { + level: number +} + +const TreeGuideLines: React.FC = ({ level }) => { + if (level === 0) + return null + + return ( + <> + {Array.from({ length: level }).map((_, i) => ( +
+ ))} + + ) +} + +export default React.memo(TreeGuideLines) diff --git a/web/app/components/workflow/skill/tree-node.tsx b/web/app/components/workflow/skill/file-tree/tree-node.tsx similarity index 87% rename from web/app/components/workflow/skill/tree-node.tsx rename to web/app/components/workflow/skill/file-tree/tree-node.tsx index 48b64b5fc8..ddd52614bf 100644 --- a/web/app/components/workflow/skill/tree-node.tsx +++ b/web/app/components/workflow/skill/file-tree/tree-node.tsx @@ -1,7 +1,7 @@ 'use client' import type { NodeRendererProps } from 'react-arborist' -import type { TreeNodeData } from './type' +import type { TreeNodeData } from '../type' import type { FileAppearanceType } from '@/app/components/base/file-uploader/types' import { RiFolderLine, RiFolderOpenLine, RiMoreFill } from '@remixicon/react' import * as React from 'react' @@ -14,10 +14,12 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' +import { useSkillEditorStore, useSkillEditorStoreApi } from '../store' +import { getFileIconType } from '../utils/file-utils' import FileNodeMenu from './file-node-menu' import FolderNodeMenu from './folder-node-menu' -import { useSkillEditorStore, useSkillEditorStoreApi } from './store' -import { getFileIconType } from './utils/file-utils' +import TreeEditInput from './tree-edit-input' +import TreeGuideLines from './tree-guide-lines' const TreeNode = ({ node, style, dragHandle }: NodeRendererProps) => { const { t } = useTranslation('workflow') @@ -83,7 +85,7 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps) aria-selected={isSelected} aria-expanded={isFolder ? node.isOpen : undefined} className={cn( - 'group flex h-6 cursor-pointer items-center gap-2 rounded-md px-2', + 'group relative flex h-6 cursor-pointer items-center gap-2 rounded-md px-2', 'hover:bg-state-base-hover', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-components-input-border-active', isSelected && 'bg-state-base-active', @@ -94,6 +96,7 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps) onKeyDown={handleKeyDown} onContextMenu={handleContextMenu} > +
{isFolder ? ( @@ -122,16 +125,22 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps) )}
- - {node.data.name} - + {node.isEditing + ? ( + + ) + : ( + + {node.data.name} + + )}