From 3802d978eda01672a0583ffc2598c709018cc0a2 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sat, 23 May 2026 07:59:52 -0700 Subject: [PATCH] Implement lasso selection, fix context menu positioning, fix video preview sizing --- frontend/src/components/FileExplorer.tsx | 103 ++++++++++++++++++++++- frontend/src/components/FilePreview.tsx | 2 +- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/FileExplorer.tsx b/frontend/src/components/FileExplorer.tsx index bf750e6..0a025d6 100644 --- a/frontend/src/components/FileExplorer.tsx +++ b/frontend/src/components/FileExplorer.tsx @@ -111,7 +111,10 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { const [dragOverFolder, setDragOverFolder] = useState(null); const [pinnedExpanded, setPinnedExpanded] = useState(false); const [pinnedOrder, setPinnedOrder] = useState([]); + const [selectionBox, setSelectionBox] = useState<{ startX: number; startY: number; currentX: number; currentY: number } | null>(null); + const [initialSelectedFiles, setInitialSelectedFiles] = useState>(new Set()); const searchTimeoutRef = useRef(undefined); + const mainContainerRef = useRef(null); const fileInputRef = useRef(null); const [gridSize, setGridSize] = useState('200'); @@ -357,17 +360,84 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { } } + function handlePointerDown(e: React.PointerEvent) { + if (e.button !== 0) return; + const target = e.target as HTMLElement; + if (target.closest('button') || target.closest('input') || target.closest('[data-file-item]')) { + return; + } + setSelectionBox({ + startX: e.clientX, + startY: e.clientY, + currentX: e.clientX, + currentY: e.clientY, + }); + if (e.shiftKey || e.ctrlKey || e.metaKey) { + setInitialSelectedFiles(new Set(selectedFiles)); + } else { + setInitialSelectedFiles(new Set()); + setSelectedFiles(new Set()); + } + } + + function handlePointerMove(e: React.PointerEvent) { + if (!selectionBox) return; + setSelectionBox(prev => prev ? { ...prev, currentX: e.clientX, currentY: e.clientY } : null); + + if (mainContainerRef.current) { + const boxLeft = Math.min(selectionBox.startX, e.clientX); + const boxRight = Math.max(selectionBox.startX, e.clientX); + const boxTop = Math.min(selectionBox.startY, e.clientY); + const boxBottom = Math.max(selectionBox.startY, e.clientY); + + const fileNodes = mainContainerRef.current.querySelectorAll('[data-file-item]'); + const newSelected = new Set(initialSelectedFiles); + + fileNodes.forEach(node => { + const rect = node.getBoundingClientRect(); + const intersects = !( + rect.right < boxLeft || + rect.left > boxRight || + rect.bottom < boxTop || + rect.top > boxBottom + ); + + const filePath = node.getAttribute('data-file-path'); + if (filePath) { + if (intersects) { + newSelected.add(filePath); + } else if (!initialSelectedFiles.has(filePath)) { + newSelected.delete(filePath); + } + } + }); + + setSelectedFiles(newSelected); + } + } + + function handlePointerUp() { + setSelectionBox(null); + } + function handleBgContextMenu(e: React.MouseEvent) { - // Only show if the click was on the container itself, not on a file item if ((e.target as HTMLElement).closest('[data-file-item]')) return; e.preventDefault(); setContextMenu(null); - setBgContextMenu({ x: e.clientX, y: e.clientY }); + setBgContextMenu({ + x: Math.min(e.clientX, typeof window !== 'undefined' ? window.innerWidth - 200 : e.clientX), + y: Math.min(e.clientY, typeof window !== 'undefined' ? window.innerHeight - 100 : e.clientY) + }); } function handleContextMenu(e: React.MouseEvent, file: FileItem) { e.preventDefault(); - setContextMenu({ x: e.clientX, y: e.clientY, file }); + const estimatedHeight = activeSection === 'trash' ? 100 : 320; + setContextMenu({ + x: Math.min(e.clientX, typeof window !== 'undefined' ? window.innerWidth - 200 : e.clientX), + y: Math.min(e.clientY, typeof window !== 'undefined' ? window.innerHeight - estimatedHeight : e.clientY), + file + }); } async function handleSearch(query: string) { @@ -1027,7 +1097,30 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { ) : (
{/* File Area */} -
+
+ {/* Lasso selection box */} + {selectionBox && ( +
+ )} + {/* Drag and drop overlay */} {dragOver && ( @@ -1122,6 +1215,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { handleItemDragStart(e, file.path)} onDragOver={(e: any) => handleItemDragOver(e, file)} @@ -1215,6 +1309,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { handleItemDragStart(e, file.path)} onDragOver={(e: any) => handleItemDragOver(e, file)} diff --git a/frontend/src/components/FilePreview.tsx b/frontend/src/components/FilePreview.tsx index c5ba0a8..bffa7d2 100644 --- a/frontend/src/components/FilePreview.tsx +++ b/frontend/src/components/FilePreview.tsx @@ -138,7 +138,7 @@ export default function FilePreview({ file, onClose }: FilePreviewProps) { src={downloadUrl} controls autoPlay - className="max-w-full max-h-full rounded-lg outline-none" + className="max-w-[calc(95vw-2rem)] max-h-[calc(95vh-6rem)] rounded-lg outline-none" /> )}