mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 17:08:03 +08:00
feat: multi select for file tree & clipboard support
This commit is contained in:
@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
|
||||
import type { RefObject } from 'react'
|
||||
import type { TreeApi } from 'react-arborist'
|
||||
import type { TreeNodeData } from '../type'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
getKeyboardKeyCodeBySystem,
|
||||
isEventTargetInputArea,
|
||||
} from '@/app/components/workflow/utils/common'
|
||||
|
||||
type UseSkillShortcutsOptions = {
|
||||
treeRef: RefObject<TreeApi<TreeNodeData> | null>
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export function useSkillShortcuts({
|
||||
treeRef,
|
||||
enabled = true,
|
||||
}: UseSkillShortcutsOptions): void {
|
||||
const storeApi = useWorkflowStore()
|
||||
const enabledRef = useRef(enabled)
|
||||
useEffect(() => { enabledRef.current = enabled }, [enabled])
|
||||
|
||||
const shouldHandle = useCallback((e: KeyboardEvent) => {
|
||||
if (!enabledRef.current)
|
||||
return false
|
||||
return !isEventTargetInputArea(e.target as HTMLElement)
|
||||
}, [])
|
||||
|
||||
const getSelectedNodeIds = useCallback(() => {
|
||||
const tree = treeRef.current
|
||||
if (!tree)
|
||||
return []
|
||||
return tree.selectedNodes.map(n => n.id)
|
||||
}, [treeRef])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.c`, (e) => {
|
||||
if (shouldHandle(e)) {
|
||||
const nodeIds = getSelectedNodeIds()
|
||||
if (nodeIds.length > 0) {
|
||||
e.preventDefault()
|
||||
storeApi.getState().copyNodes(nodeIds)
|
||||
}
|
||||
}
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.x`, (e) => {
|
||||
if (shouldHandle(e)) {
|
||||
const nodeIds = getSelectedNodeIds()
|
||||
if (nodeIds.length > 0) {
|
||||
e.preventDefault()
|
||||
storeApi.getState().cutNodes(nodeIds)
|
||||
}
|
||||
}
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.v`, (e) => {
|
||||
if (shouldHandle(e) && storeApi.getState().hasClipboard()) {
|
||||
e.preventDefault()
|
||||
window.dispatchEvent(new CustomEvent('skill:paste'))
|
||||
}
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
}
|
||||
@ -27,8 +27,6 @@ export function useSyncTreeWithActiveTab({
|
||||
if (!activeTabId)
|
||||
return
|
||||
|
||||
storeApi.getState().setSelectedTreeNodeId(activeTabId)
|
||||
|
||||
const tree = treeRef.current
|
||||
if (!tree)
|
||||
return
|
||||
@ -38,7 +36,6 @@ export function useSyncTreeWithActiveTab({
|
||||
if (!node)
|
||||
return
|
||||
|
||||
// Traverse parent chain to collect ancestor folder IDs
|
||||
const ancestors: string[] = []
|
||||
let current = node.parent
|
||||
while (current && !current.isRoot) {
|
||||
@ -50,6 +47,7 @@ export function useSyncTreeWithActiveTab({
|
||||
storeApi.getState().revealFile(ancestors)
|
||||
|
||||
tree.openParents(node)
|
||||
tree.select(activeTabId)
|
||||
tree.scrollTo(activeTabId)
|
||||
})
|
||||
}, [activeTabId, treeRef, storeApi])
|
||||
|
||||
@ -19,10 +19,6 @@ type UseTreeNodeHandlersReturn = {
|
||||
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 {
|
||||
@ -49,10 +45,16 @@ export function useTreeNodeHandlers({
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
node.select() // This triggers Tree's onSelect → setSelectedTreeNodeId
|
||||
if (e.shiftKey)
|
||||
node.selectContiguous()
|
||||
else if (e.metaKey || e.ctrlKey)
|
||||
node.selectMulti()
|
||||
else
|
||||
node.select()
|
||||
|
||||
if (isFolder)
|
||||
throttledToggle()
|
||||
else
|
||||
else if (!e.metaKey && !e.ctrlKey && !e.shiftKey)
|
||||
handleFileClick()
|
||||
}, [handleFileClick, isFolder, node, throttledToggle])
|
||||
|
||||
@ -72,9 +74,7 @@ export function useTreeNodeHandlers({
|
||||
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// Select the node for highlight + creation target
|
||||
storeApi.getState().setSelectedTreeNodeId(node.data.id)
|
||||
node.select()
|
||||
storeApi.getState().setContextMenu({
|
||||
top: e.clientY,
|
||||
left: e.clientX,
|
||||
@ -82,7 +82,7 @@ export function useTreeNodeHandlers({
|
||||
nodeId: node.data.id,
|
||||
isFolder,
|
||||
})
|
||||
}, [isFolder, node.data.id, storeApi])
|
||||
}, [isFolder, node, storeApi])
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
|
||||
Reference in New Issue
Block a user