Implement lasso selection, fix context menu positioning, fix video preview sizing
All checks were successful
Automated Container Build / build-and-push (push) Successful in 32s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 32s
This commit is contained in:
parent
99e0cd5f0c
commit
3802d978ed
2 changed files with 100 additions and 5 deletions
|
|
@ -111,7 +111,10 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
const [dragOverFolder, setDragOverFolder] = useState<string | null>(null);
|
||||
const [pinnedExpanded, setPinnedExpanded] = useState(false);
|
||||
const [pinnedOrder, setPinnedOrder] = useState<string[]>([]);
|
||||
const [selectionBox, setSelectionBox] = useState<{ startX: number; startY: number; currentX: number; currentY: number } | null>(null);
|
||||
const [initialSelectedFiles, setInitialSelectedFiles] = useState<Set<string>>(new Set());
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const mainContainerRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(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) {
|
|||
) : (
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* File Area */}
|
||||
<div className="flex-1 overflow-y-auto p-6 relative" onContextMenu={activeSection === 'files' ? handleBgContextMenu : undefined}>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto p-6 relative"
|
||||
onContextMenu={activeSection === 'files' ? handleBgContextMenu : undefined}
|
||||
ref={mainContainerRef}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPointerLeave={handlePointerUp}
|
||||
>
|
||||
{/* Lasso selection box */}
|
||||
{selectionBox && (
|
||||
<div
|
||||
className="fixed z-50 pointer-events-none"
|
||||
style={{
|
||||
left: Math.min(selectionBox.startX, selectionBox.currentX),
|
||||
top: Math.min(selectionBox.startY, selectionBox.currentY),
|
||||
width: Math.abs(selectionBox.currentX - selectionBox.startX),
|
||||
height: Math.abs(selectionBox.currentY - selectionBox.startY),
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.2)',
|
||||
border: '1px solid rgba(59, 130, 246, 0.5)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Drag and drop overlay */}
|
||||
<AnimatePresence>
|
||||
{dragOver && (
|
||||
|
|
@ -1122,6 +1215,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
<motion.div
|
||||
key={file.path}
|
||||
data-file-item
|
||||
data-file-path={file.path}
|
||||
draggable
|
||||
onDragStart={(e: any) => handleItemDragStart(e, file.path)}
|
||||
onDragOver={(e: any) => handleItemDragOver(e, file)}
|
||||
|
|
@ -1215,6 +1309,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
|
|||
<motion.div
|
||||
key={file.path}
|
||||
data-file-item
|
||||
data-file-path={file.path}
|
||||
draggable
|
||||
onDragStart={(e: any) => handleItemDragStart(e, file.path)}
|
||||
onDragOver={(e: any) => handleItemDragOver(e, file)}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Reference in a new issue