feat: unified drag-and-drop for skill file tree

Implement unified drag system that supports both internal node moves
and external file uploads with consistent UI feedback. Uses native
HTML5 drag API with shared visual states (isDragOver, isBlinking,
DragActionTooltip showing 'Move to' or 'Upload to').
This commit is contained in:
yyh
2026-01-20 16:27:05 +08:00
parent 0e66b51ca0
commit b527921f3f
14 changed files with 297 additions and 34 deletions

View File

@ -1,5 +1,27 @@
import type * as React from 'react'
import { INTERNAL_NODE_DRAG_TYPE } from '../constants'
// Check if dragging external files from OS
export const isFileDrag = (e: React.DragEvent): boolean => {
return e.dataTransfer.types.includes('Files')
}
// Check if dragging internal tree node
export const isNodeDrag = (e: React.DragEvent): boolean => {
return e.dataTransfer.types.includes(INTERNAL_NODE_DRAG_TYPE)
}
// Check if any supported drag type
export const isDragEvent = (e: React.DragEvent): boolean => {
return isFileDrag(e) || isNodeDrag(e)
}
// Get drag action type for tooltip display
export type DragActionType = 'upload' | 'move'
export const getDragActionType = (e: React.DragEvent): DragActionType | null => {
if (isFileDrag(e))
return 'upload'
if (isNodeDrag(e))
return 'move'
return null
}

View File

@ -117,6 +117,19 @@ export function getAllDescendantFileIds(
return fileIds
}
export function isDescendantOf(
potentialDescendantId: string | null | undefined,
ancestorId: string | null | undefined,
nodes: AppAssetTreeView[],
): boolean {
if (!potentialDescendantId || !ancestorId)
return false
if (potentialDescendantId === ancestorId)
return true
const ancestors = getAncestorIds(potentialDescendantId, nodes)
return ancestors.includes(ancestorId)
}
export function getTargetFolderIdFromSelection(
selectedId: string | null,
nodes: AppAssetTreeView[],