Files
dify/web/app/components/workflow/skill/hooks/use-tree-node-handlers.ts
yyh 021f055c36 feat(skill-editor): add blank area context menu and align search/add styles
Add right-click context menu for file tree blank area with New File,
New Folder, and Upload Files options. Also align search input and
add button styles to match Figma design specs (24px height, 6px radius).
2026-01-19 11:38:59 +08:00

102 lines
2.8 KiB
TypeScript

'use client'
import type { NodeApi } from 'react-arborist'
import type { TreeNodeData } from '../type'
import { throttle } from 'es-toolkit/function'
import { useCallback, useMemo } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useDelayedClick } from './use-delayed-click'
type UseTreeNodeHandlersOptions = {
node: NodeApi<TreeNodeData>
}
type UseTreeNodeHandlersReturn = {
handleClick: (e: React.MouseEvent) => void
handleDoubleClick: (e: React.MouseEvent) => void
handleToggle: (e: React.MouseEvent) => void
handleContextMenu: (e: React.MouseEvent) => void
handleKeyDown: (e: React.KeyboardEvent) => void
}
/**
* Hook that encapsulates all tree node interaction handlers.
* Handles click, double-click, toggle, context menu, and keyboard events.
*/
export function useTreeNodeHandlers({
node,
}: UseTreeNodeHandlersOptions): UseTreeNodeHandlersReturn {
const storeApi = useWorkflowStore()
const isFolder = node.data.node_type === 'folder'
const throttledToggle = useMemo(
() => throttle(() => node.toggle(), 300, { edges: ['leading'] }),
[node],
)
const openFilePreview = useCallback(() => {
storeApi.getState().openTab(node.data.id, { pinned: false })
}, [node.data.id, storeApi])
const openFilePinned = useCallback(() => {
storeApi.getState().openTab(node.data.id, { pinned: true })
}, [node.data.id, storeApi])
const { handleClick: handleFileClick, handleDoubleClick: handleFileDoubleClick } = useDelayedClick({
onSingleClick: openFilePreview,
onDoubleClick: openFilePinned,
})
const handleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
node.select()
if (isFolder)
throttledToggle()
else
handleFileClick()
}, [isFolder, node, throttledToggle, handleFileClick])
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
if (isFolder)
throttledToggle()
else
handleFileDoubleClick()
}, [isFolder, throttledToggle, handleFileDoubleClick])
const handleToggle = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
throttledToggle()
}, [throttledToggle])
const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
storeApi.getState().setContextMenu({
top: e.clientY,
left: e.clientX,
type: 'node',
nodeId: node.data.id,
})
}, [node.data.id, storeApi])
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
if (isFolder)
node.toggle()
else
storeApi.getState().openTab(node.data.id, { pinned: true })
}
}, [isFolder, node, storeApi])
return {
handleClick,
handleDoubleClick,
handleToggle,
handleContextMenu,
handleKeyDown,
}
}