dify/web/app/components/workflow/skill/hooks/use-file-drop.ts
yyh 9f444f1f6a
refactor(skill): split file operations hook and extract TreeNodeIcon component
Split use-file-operations.ts (248 lines) into smaller focused hooks:
- use-create-operations.ts for file/folder creation and upload
- use-modify-operations.ts for rename and delete operations
- use-file-operations.ts now serves as orchestrator maintaining backward compatibility

Extract TreeNodeIcon component from tree-node.tsx for cleaner separation of concerns.

Add brief comments to drag hooks explaining their purpose and relationships.
2026-01-19 19:13:09 +08:00

108 lines
2.9 KiB
TypeScript

'use client'
// Base drag-and-drop handler for file uploads
// Used by use-root-file-drop and use-folder-file-drop
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import Toast from '@/app/components/base/toast'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useCreateAppAssetFile } from '@/service/use-app-asset'
type FileDropTarget = {
folderId: string | null
isFolder: boolean
}
export function useFileDrop() {
const { t } = useTranslation('workflow')
const appDetail = useAppStore(s => s.appDetail)
const appId = appDetail?.id || ''
const storeApi = useWorkflowStore()
const createFile = useCreateAppAssetFile()
const handleDragOver = useCallback((e: React.DragEvent, target: FileDropTarget) => {
e.preventDefault()
e.stopPropagation()
// Only handle file drops from the system (not internal tree drags)
if (!e.dataTransfer.types.includes('Files'))
return
e.dataTransfer.dropEffect = 'copy'
// Use '__root__' to indicate dragging over root (to distinguish from "not dragging")
storeApi.getState().setDragOverFolderId(target.folderId ?? '__root__')
}, [storeApi])
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
storeApi.getState().setDragOverFolderId(null)
}, [storeApi])
const handleDrop = useCallback(async (e: React.DragEvent, targetFolderId: string | null) => {
e.preventDefault()
e.stopPropagation()
storeApi.getState().setDragOverFolderId(null)
// Get files from dataTransfer, filter out directories (which have no type)
const items = Array.from(e.dataTransfer.items || [])
const files: File[] = []
for (const item of items) {
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry?.()
// Skip directories - they have isDirectory = true
if (entry?.isDirectory) {
Toast.notify({
type: 'error',
message: t('skillSidebar.menu.folderDropNotSupported'),
})
continue
}
const file = item.getAsFile()
if (file)
files.push(file)
}
}
if (files.length === 0)
return
try {
await Promise.all(
files.map(file =>
createFile.mutateAsync({
appId,
name: file.name,
file,
parentId: targetFolderId,
}),
),
)
Toast.notify({
type: 'success',
message: t('skillSidebar.menu.filesUploaded', { count: files.length }),
})
}
catch {
Toast.notify({
type: 'error',
message: t('skillSidebar.menu.uploadError'),
})
}
}, [appId, createFile, t, storeApi])
return {
handleDragOver,
handleDragLeave,
handleDrop,
isUploading: createFile.isPending,
}
}