diff options
| author | YurenHao0426 <blackhao0426@gmail.com> | 2026-02-14 05:07:03 +0000 |
|---|---|---|
| committer | YurenHao0426 <blackhao0426@gmail.com> | 2026-02-14 05:07:03 +0000 |
| commit | db1249889090ba6283dd92783776bf500f36d9e4 (patch) | |
| tree | fdfbf5d5e41c161e73b8261910efeb8e875e0ea3 | |
| parent | f119ab4f60ca40dcea993c46dd8ce47440b10c96 (diff) | |
Fix file drag-drop: allow dragging items out of folders and to empty area
- Drop on file moves item to file's parent directory
- Drop on empty area moves item to project root
- Container gets flex-1 height so empty droppable space exists
- Mark internal drags with dataTransfer to distinguish from OS file uploads
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | frontend/src/components/LeftSidebar.tsx | 38 |
1 files changed, 27 insertions, 11 deletions
diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx index 441b7e0..d802fde 100644 --- a/frontend/src/components/LeftSidebar.tsx +++ b/frontend/src/components/LeftSidebar.tsx @@ -399,25 +399,31 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { } }; - // Drag move blueprint into folder + // Drag move blueprint into/out of folder const onItemDragStart = (e: React.DragEvent, item: FSItem) => { setDragItem(item); e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', item.path); // mark as internal drag }; const onItemDragOver = (e: React.DragEvent, item: FSItem) => { - if (item.type === 'folder') { - e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - } + if (!dragItem || dragItem.path === item.path) return; + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; }; const onItemDrop = async (e: React.DragEvent, target: FSItem) => { e.preventDefault(); - if (!dragItem || target.type !== 'folder') return; - const newPath = joinPath(target.path, dragItem.name); + e.stopPropagation(); + if (!dragItem) return; + // Drop on folder → move into it; drop on file → move to file's parent + const destFolder = target.type === 'folder' + ? target.path + : target.path.split('/').slice(0, -1).join('/') || '.'; + const newPath = joinPath(destFolder, dragItem.name); if (newPath === dragItem.path) return; await renameProjectItem(dragItem.path, undefined, newPath); setDragItem(null); }; + const onItemDragEnd = () => { setDragItem(null); }; const toggleFolder = (path: string) => { setExpanded(prev => { @@ -462,6 +468,7 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { onDragStart={(e) => onItemDragStart(e, item)} onDragOver={(e) => onItemDragOver(e, item)} onDrop={(e) => onItemDrop(e, item)} + onDragEnd={onItemDragEnd} > <div className="flex items-center gap-2"> {item.type === 'folder' ? ( @@ -869,8 +876,8 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { {/* Content Area */} <div className={`flex-1 overflow-y-auto p-4 text-sm ${isDark ? 'text-gray-400' : 'text-gray-500'}`}> {activeTab === 'project' && ( - <div - className="space-y-2" + <div + className="space-y-2 flex flex-col h-full" > <div className="flex items-center gap-2 mb-2" @@ -940,18 +947,27 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { /> <div + className="flex-1" onContextMenu={(e) => { if (activeTab === 'project') { openContextMenu(e); } }} - onDragOver={(e) => { - if (e.dataTransfer.types.includes('Files')) { e.preventDefault(); } + onDragOver={(e) => { + if (e.dataTransfer.types.includes('Files') || e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); } }} onDrop={async (e) => { if (e.dataTransfer.files?.length) { e.preventDefault(); await handleUploadFiles(e.dataTransfer.files, currentFolder); + } else if (dragItem) { + // Internal drag: move item to root + e.preventDefault(); + const newPath = joinPath('.', dragItem.name); + if (newPath !== dragItem.path) { + await renameProjectItem(dragItem.path, undefined, newPath); + } + setDragItem(null); } }} > |
