Files
dify/web/app/components/workflow/skill/hooks/use-file-drop.ts
yyh 31a7db2657 refactor(skill): unify root/blank constants and eliminate magic strings
- Add constants.ts with ROOT_ID, CONTEXT_MENU_TYPE, NODE_MENU_TYPE
- Add root utilities to tree-utils.ts (isRootId, toApiParentId, etc.)
- Replace '__root__' with ROOT_ID for consistent root identifier
- Replace inline 'blank'/'root' strings with constants
- Use NodeMenuType for type-safe menu type props
- Remove duplicate ContextMenuType from types.ts, use from constants.ts
2026-01-19 23:07:49 +08:00

109 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'
import { ROOT_ID } from '../constants'
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_ID to indicate dragging over root (to distinguish from null = "not dragging")
storeApi.getState().setDragOverFolderId(target.folderId ?? ROOT_ID)
}, [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,
}
}