summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYurenHao0426 <blackhao0426@gmail.com>2026-02-14 05:07:03 +0000
committerYurenHao0426 <blackhao0426@gmail.com>2026-02-14 05:07:03 +0000
commitdb1249889090ba6283dd92783776bf500f36d9e4 (patch)
treefdfbf5d5e41c161e73b8261910efeb8e875e0ea3
parentf119ab4f60ca40dcea993c46dd8ce47440b10c96 (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.tsx38
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);
}
}}
>