import {useState} from 'react'; import {useDropzone} from 'react-dropzone'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; import {ObjectsTable} from './ObjectsTable'; import {CreateDirectoryDialog} from './CreateDirectoryDialog'; import {DeleteObjectDialog} from './DeleteObjectDialog'; import {UploadProgress} from './UploadProgress'; import {ArrowLeft, ChevronRight, FolderPlus, Home, RotateCwIcon, Search, Trash, Upload} from 'lucide-react'; import {getBreadcrumbs} from '@/lib/file-utils'; import type {S3Object, UploadTask} from '@/types'; interface ObjectBrowserViewProps { bucketName: string; objects: S3Object[]; currentPath: string; searchQuery: string; isLoading?: boolean; isTruncated?: boolean; nextContinuationToken?: string; itemsPerPage: number; onSearchChange: (query: string) => void; onNavigateToFolder: (path: string) => void; onBackToBuckets: () => void; onUploadFiles: (files: File[]) => Promise; uploadTasks: UploadTask[]; onDeleteObject: (key: string) => Promise; onDeleteMultipleObjects: (keys: string[]) => Promise; onCreateDirectory: (name: string) => Promise; onRefresh: () => Promise; onPageChange: (token?: string) => void; onItemsPerPageChange: (count: number) => void; isRefreshing: boolean; isNavigating: boolean; initialPageToken?: string; initialItemsPerPage?: number; } export function ObjectBrowserView({ bucketName, objects, currentPath, searchQuery, isLoading = false, isTruncated = false, nextContinuationToken, itemsPerPage, onSearchChange, onNavigateToFolder, onBackToBuckets, onUploadFiles, uploadTasks, onDeleteObject, onDeleteMultipleObjects, onCreateDirectory, onRefresh, onPageChange, onItemsPerPageChange, isRefreshing, isNavigating, initialPageToken, initialItemsPerPage, }: ObjectBrowserViewProps) { const [showUploadZone, setShowUploadZone] = useState(false); const [deleteObjectDialogOpen, setDeleteObjectDialogOpen] = useState(false); const [selectedObject, setSelectedObject] = useState(null); const [createDirDialogOpen, setCreateDirDialogOpen] = useState(false); const [selectedFileKeys, setSelectedFileKeys] = useState>(new Set()); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: async (acceptedFiles, _fileRejections, event) => { // Get files with their full paths from DataTransferItems API const filesWithPaths: File[] = []; // Type cast event to DragEvent to access dataTransfer const dragEvent = event as DragEvent; if (dragEvent.dataTransfer?.items) { // Use DataTransferItemList API to preserve folder structure const items = Array.from(dragEvent.dataTransfer.items); await Promise.all(items.map(async (item: DataTransferItem) => { if (item.kind === 'file') { const entry = item.webkitGetAsEntry?.(); if (entry) { await traverseFileTree(entry, '', filesWithPaths); } } })); } else { // Fallback to standard files filesWithPaths.push(...acceptedFiles); } await onUploadFiles(filesWithPaths.length > 0 ? filesWithPaths : acceptedFiles); setShowUploadZone(false); }, noClick: true, }); // Helper function to traverse file/directory tree const traverseFileTree = async (item: any, path: string, files: File[]): Promise => { return new Promise((resolve) => { if (item.isFile) { item.file((file: File) => { const fullPath = path + file.name; Object.defineProperty(file, 'webkitRelativePath', { value: fullPath, writable: false }); files.push(file); resolve(); }); } else if (item.isDirectory) { const dirReader = item.createReader(); dirReader.readEntries(async (entries: any[]) => { for (const entry of entries) { await traverseFileTree(entry, path + item.name + '/', files); } resolve(); }); } else { resolve(); } }); }; const handleToggleFileSelection = (key: string) => { const newSelected = new Set(selectedFileKeys); if (newSelected.has(key)) { newSelected.delete(key); } else { newSelected.add(key); } setSelectedFileKeys(newSelected); }; const handleSelectAllFiles = () => { const fileKeys = objects .filter(obj => !obj.isFolder) .map(obj => obj.key); if (selectedFileKeys.size === fileKeys.length && fileKeys.length > 0) { setSelectedFileKeys(new Set()); } else { setSelectedFileKeys(new Set(fileKeys)); } }; const handleBulkDeleteFiles = async () => { if (selectedFileKeys.size === 0) return; await onDeleteMultipleObjects(Array.from(selectedFileKeys)); setSelectedFileKeys(new Set()); }; const handleDeleteObject = async (key: string): Promise => { const success = await onDeleteObject(key); if (success) { setDeleteObjectDialogOpen(false); setSelectedObject(null); } return success; }; const uploadFiles = async (files: File[]) => { await onUploadFiles(files); setShowUploadZone(false); }; return (
{/* Back Button */} {/* Breadcrumb Navigation */}
{getBreadcrumbs(currentPath).map((crumb, index) => (
{index > 0 && }
))}
{/* Toolbar */}
onSearchChange(e.target.value)} className="pl-8" />
{selectedFileKeys.size > 0 && ( )}
{/* Upload Zone */} {showUploadZone && uploadTasks.length === 0 && (

Drag and drop files/folders or{' '} {' / '}

{ if (e.target.files) { const files = Array.from(e.target.files); uploadFiles(files); e.target.value = ''; } }} style={{ display: 'none' }} /> { if (e.target.files) { const files = Array.from(e.target.files); uploadFiles(files); e.target.value = ''; } }} style={{ display: 'none' }} />
)} {/* Upload Progress */} {uploadTasks.length > 0 && } {/* Objects Table with Drag & Drop */}
{/* Drag & Drop Overlay */} {isDragActive && (

Drop files here to upload

Files will be uploaded to {currentPath || 'root'}

)} { setSelectedObject(obj); setDeleteObjectDialogOpen(true); }} onToggleFileSelection={handleToggleFileSelection} onSelectAllFiles={handleSelectAllFiles} onPageChange={onPageChange} onItemsPerPageChange={onItemsPerPageChange} initialPageToken={initialPageToken} initialItemsPerPage={initialItemsPerPage} />
{/* Create Directory Dialog */} {/* Delete Object Dialog */}
); }