diff --git a/frontend/src/components/FileExplorer.tsx b/frontend/src/components/FileExplorer.tsx index 0b85ca5..68b7c86 100644 --- a/frontend/src/components/FileExplorer.tsx +++ b/frontend/src/components/FileExplorer.tsx @@ -6,7 +6,7 @@ import { Folder, File, Search, Pin, Trash2, HardDrive, Settings, ChevronRight, Grid3X3, List, Plus, Upload, LogOut, MoreVertical, Star, Download, Pencil, Copy, Move, - FolderPlus, ArrowUp, X, Check, RefreshCw, Info, Link as LinkIcon + FolderPlus, ArrowUp, X, Check, RefreshCw, Info, Link as LinkIcon, Image as ImageIcon, Film as FilmIcon } from 'lucide-react'; import api from '@/lib/api'; import FilePreview from './FilePreview'; @@ -84,6 +84,11 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { const [sortAsc, setSortAsc] = useState(true); const [draggedFile, setDraggedFile] = useState(null); const [uploads, setUploads] = useState([]); + const [showTrashModal, setShowTrashModal] = useState(false); + const [showMoveModal, setShowMoveModal] = useState(false); + const [movingFiles, setMovingFiles] = useState([]); + const [moveModalPath, setMoveModalPath] = useState('.'); + const [moveFolders, setMoveFolders] = useState([]); const searchTimeoutRef = useRef(undefined); const fileInputRef = useRef(null); @@ -107,6 +112,21 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { } }, [currentPath, activeSection]); + const loadMoveFolders = useCallback(async (path: string) => { + try { + const data = await api.listDirectory(path); + setMoveFolders(data.files?.filter((f: FileItem) => f.is_dir).sort((a: FileItem, b: FileItem) => a.name.localeCompare(b.name)) || []); + } catch { + setMoveFolders([]); + } + }, []); + + useEffect(() => { + if (showMoveModal) { + loadMoveFolders(moveModalPath); + } + }, [showMoveModal, moveModalPath, loadMoveFolders]); + // Keyboard shortcuts useEffect(() => { function handleKeyDown(e: KeyboardEvent) { @@ -285,8 +305,23 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { loadTrash(); } + async function handleMoveExecute() { + if (movingFiles.length === 0) return; + try { + await Promise.all(movingFiles.map(src => api.move(src, moveModalPath === '.' ? '.' : moveModalPath))); + setShowMoveModal(false); + setMovingFiles([]); + loadFiles(); + setSelectedFiles(new Set()); + } catch (err) { + console.error(err); + } + } + // Drag and drop upload function handleDragOver(e: React.DragEvent) { + if (draggedFile) return; + if (!e.dataTransfer.types.includes('Files')) return; e.preventDefault(); setDragOver(true); } @@ -343,6 +378,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { async function handleDrop(e: React.DragEvent) { e.preventDefault(); setDragOver(false); + if (draggedFile) return; const droppedFiles = Array.from(e.dataTransfer.files); await Promise.all(droppedFiles.map(file => uploadFile(file))); loadFiles(); @@ -377,7 +413,9 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { if (file.is_dir) return ; const ext = file.name.split('.').pop()?.toLowerCase() || ''; - const isMedia = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'ogg'].includes(ext) || file.mime_type?.startsWith('image/') || file.mime_type?.startsWith('video/'); + const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext) || file.mime_type?.startsWith('image/'); + const isVideo = ['mp4', 'mkv', 'avi', 'mov', 'webm', 'ogg'].includes(ext) || file.mime_type?.startsWith('video/'); + const isMedia = isImage || isVideo; if (ext === 'pdf') { return ( @@ -386,6 +424,22 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) { ); } + + if (isImage) { + return ( +
+ +
+ ); + } + + if (isVideo) { + return ( +
+ +
+ ); + } const fallbackIcon = (() => { let color = 'var(--color-text-tertiary)'; @@ -584,11 +638,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
{activeSection === 'trash' && ( + +
+ + + + )} + + + + {showMoveModal && ( +
+ +
+

Move {movingFiles.length} item{movingFiles.length > 1 ? 's' : ''}

+ +
+
+ +
+ Home {moveModalPath !== '.' && '> ' + moveModalPath.replace(/\//g, ' > ')} +
+
+
+ {moveFolders.length === 0 ? ( +
+ No folders here +
+ ) : ( + moveFolders.map(folder => ( + + )) + )} +
+
+ + +
+
+
+ )} +
); }