From db1249889090ba6283dd92783776bf500f36d9e4 Mon Sep 17 00:00:00 2001
From: YurenHao0426 <blackhao0426@gmail.com>
Date: Sat, 14 Feb 2026 05:07:03 +0000
Subject: 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>
---
 frontend/src/components/LeftSidebar.tsx | 38 +++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 11 deletions(-)

(limited to 'frontend/src/components/LeftSidebar.tsx')

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);
                 }
               }}
             >
-- 
cgit v1.2.3

