summaryrefslogtreecommitdiff
path: root/frontend/components/drive/DriveDirectoryClient.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/components/drive/DriveDirectoryClient.tsx')
-rw-r--r--frontend/components/drive/DriveDirectoryClient.tsx88
1 files changed, 35 insertions, 53 deletions
diff --git a/frontend/components/drive/DriveDirectoryClient.tsx b/frontend/components/drive/DriveDirectoryClient.tsx
index f523e4f..d238065 100644
--- a/frontend/components/drive/DriveDirectoryClient.tsx
+++ b/frontend/components/drive/DriveDirectoryClient.tsx
@@ -32,18 +32,10 @@ import { Checkbox } from "@/components/ui/checkbox"
32import { toast } from "@/hooks/use-toast" 32import { toast } from "@/hooks/use-toast"
33import { DriveLsEntry } from "@/lib/drive_types" 33import { DriveLsEntry } from "@/lib/drive_types"
34import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants" 34import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants"
35import { formatFileSize } from "@/lib/utils"
35import { DriveMoveDialog } from "./DriveMoveDialog" 36import { DriveMoveDialog } from "./DriveMoveDialog"
36import { StorageUsage } from "./StorageUsage" 37import { StorageUsage } from "./StorageUsage"
37import type { StorageData } from "@/lib/storage" 38import type { StorageData } from "@/lib/storage"
38import type { UserAuth } from "@/lib/auth_types"
39
40function formatFileSize(bytes: number): string {
41 if (bytes === 0) return "0 Bytes"
42 const k = 1024
43 const sizes = ["Bytes", "KB", "MB", "GB"]
44 const i = Math.floor(Math.log(bytes) / Math.log(k))
45 return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
46}
47 39
48function formatDate(timestamp: number): string { 40function formatDate(timestamp: number): string {
49 return new Date(timestamp * 1000).toISOString().split('T')[0] 41 return new Date(timestamp * 1000).toISOString().split('T')[0]
@@ -52,9 +44,9 @@ function formatDate(timestamp: number): string {
52function formatDateTime(timestamp: number): string { 44function formatDateTime(timestamp: number): string {
53 const date = new Date(timestamp * 1000) 45 const date = new Date(timestamp * 1000)
54 const dateStr = date.toISOString().split('T')[0] 46 const dateStr = date.toISOString().split('T')[0]
55 const timeStr = date.toLocaleTimeString('en-US', { 47 const timeStr = date.toLocaleTimeString('en-US', {
56 hour12: false, 48 hour12: false,
57 hour: '2-digit', 49 hour: '2-digit',
58 minute: '2-digit', 50 minute: '2-digit',
59 second: '2-digit' 51 second: '2-digit'
60 }) 52 })
@@ -72,10 +64,9 @@ interface DriveDirectoryClientProps {
72 files: DriveLsEntry[] 64 files: DriveLsEntry[]
73 breadcrumbs: Breadcrumb[] 65 breadcrumbs: Breadcrumb[]
74 storageData: StorageData 66 storageData: StorageData
75 user: UserAuth
76} 67}
77 68
78export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, user }: DriveDirectoryClientProps) { 69export function DriveDirectoryClient({ path, files, breadcrumbs, storageData }: DriveDirectoryClientProps) {
79 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set()) 70 const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set())
80 const [renameDialogOpen, setRenameDialogOpen] = useState(false) 71 const [renameDialogOpen, setRenameDialogOpen] = useState(false)
81 const [infoDialogOpen, setInfoDialogOpen] = useState(false) 72 const [infoDialogOpen, setInfoDialogOpen] = useState(false)
@@ -98,10 +89,6 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
98 setSelectedFiles(newSelected) 89 setSelectedFiles(newSelected)
99 } 90 }
100 91
101 const selectAll = () => {
102 setSelectedFiles(new Set(files.map(file => file.path)))
103 }
104
105 const deselectAll = () => { 92 const deselectAll = () => {
106 setSelectedFiles(new Set()) 93 setSelectedFiles(new Set())
107 } 94 }
@@ -130,7 +117,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
130 }) 117 })
131 return 118 return
132 } 119 }
133 120
134 const filename = item.path.split('/').pop() || 'download' 121 const filename = item.path.split('/').pop() || 'download'
135 const permalink = `${window.location.origin}/blob/${item.blob}?filename=${encodeURIComponent(filename)}` 122 const permalink = `${window.location.origin}/blob/${item.blob}?filename=${encodeURIComponent(filename)}`
136 navigator.clipboard.writeText(permalink).then(() => { 123 navigator.clipboard.writeText(permalink).then(() => {
@@ -171,8 +158,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
171 title: "Renamed successfully", 158 title: "Renamed successfully",
172 description: result.message, 159 description: result.message,
173 }) 160 })
174 161
175 // Refresh page to show changes
176 window.location.reload() 162 window.location.reload()
177 } else { 163 } else {
178 throw new Error(result.error || `Rename failed with status ${response.status}`) 164 throw new Error(result.error || `Rename failed with status ${response.status}`)
@@ -222,7 +208,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
222 try { 208 try {
223 const formData = new FormData() 209 const formData = new FormData()
224 formData.append('file', file) 210 formData.append('file', file)
225 211
226 // Use the new simple upload endpoint with path as query parameter 212 // Use the new simple upload endpoint with path as query parameter
227 const response = await fetch(`/api/upload?path=${encodeURIComponent(path)}`, { 213 const response = await fetch(`/api/upload?path=${encodeURIComponent(path)}`, {
228 method: 'POST', 214 method: 'POST',
@@ -247,8 +233,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
247 title: "Upload successful", 233 title: "Upload successful",
248 description: `${successCount} file(s) uploaded successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}` 234 description: `${successCount} file(s) uploaded successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`
249 }) 235 })
250 236
251 // Refresh page to show changes
252 window.location.reload() 237 window.location.reload()
253 } 238 }
254 239
@@ -292,8 +277,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
292 title: "Deleted successfully", 277 title: "Deleted successfully",
293 description: result.message, 278 description: result.message,
294 }) 279 })
295 280
296 // Refresh page to show changes
297 window.location.reload() 281 window.location.reload()
298 } else { 282 } else {
299 throw new Error(result.error || `Delete failed with status ${response.status}`) 283 throw new Error(result.error || `Delete failed with status ${response.status}`)
@@ -322,7 +306,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
322 // Extract filename from the full path 306 // Extract filename from the full path
323 const fileName = filePath.split('/').pop() || '' 307 const fileName = filePath.split('/').pop() || ''
324 // Construct new path: destination + filename 308 // Construct new path: destination + filename
325 const newPath = destinationPath.endsWith('/') 309 const newPath = destinationPath.endsWith('/')
326 ? `${destinationPath}${fileName}` 310 ? `${destinationPath}${fileName}`
327 : `${destinationPath}/${fileName}` 311 : `${destinationPath}/${fileName}`
328 312
@@ -360,14 +344,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
360 title: "Move completed", 344 title: "Move completed",
361 description: `${successCount} item(s) moved successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`, 345 description: `${successCount} item(s) moved successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}`,
362 }) 346 })
363 347
364 // Refresh page to show changes
365 window.location.reload() 348 window.location.reload()
366 } 349 }
367 350
368 if (errorCount > 0 && successCount === 0) { 351 if (errorCount > 0 && successCount === 0) {
369 toast({ 352 toast({
370 title: "Move failed", 353 title: "Move failed",
371 description: `All ${errorCount} item(s) failed to move. ${errors[0] || ''}`, 354 description: `All ${errorCount} item(s) failed to move. ${errors[0] || ''}`,
372 variant: "destructive" 355 variant: "destructive"
373 }) 356 })
@@ -416,8 +399,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
416 title: "Folder created", 399 title: "Folder created",
417 description: result.message, 400 description: result.message,
418 }) 401 })
419 402
420 // Refresh page to show changes
421 window.location.reload() 403 window.location.reload()
422 } else { 404 } else {
423 throw new Error(result.error || `Failed to create folder`) 405 throw new Error(result.error || `Failed to create folder`)
@@ -447,7 +429,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
447 {index === breadcrumbs.length - 1 ? ( 429 {index === breadcrumbs.length - 1 ? (
448 <span className="text-foreground font-medium">{crumb.name}</span> 430 <span className="text-foreground font-medium">{crumb.name}</span>
449 ) : ( 431 ) : (
450 <Link 432 <Link
451 href={crumb.path} 433 href={crumb.path}
452 className="hover:text-foreground transition-colors" 434 className="hover:text-foreground transition-colors"
453 > 435 >
@@ -457,9 +439,9 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
457 </div> 439 </div>
458 ))} 440 ))}
459 </nav> 441 </nav>
460 442
461 <div className="flex items-center gap-2 sm:gap-2"> 443 <div className="flex items-center gap-2 sm:gap-2">
462 <Button 444 <Button
463 variant="secondary" 445 variant="secondary"
464 onClick={() => setCreateFolderDialogOpen(true)} 446 onClick={() => setCreateFolderDialogOpen(true)}
465 className="flex-1 sm:flex-none" 447 className="flex-1 sm:flex-none"
@@ -467,7 +449,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
467 <FolderPlus className="mr-2 h-4 w-4" /> 449 <FolderPlus className="mr-2 h-4 w-4" />
468 Create Folder 450 Create Folder
469 </Button> 451 </Button>
470 <Button 452 <Button
471 onClick={() => fileInputRef.current?.click()} 453 onClick={() => fileInputRef.current?.click()}
472 disabled={uploading} 454 disabled={uploading}
473 className="flex-1 sm:flex-none" 455 className="flex-1 sm:flex-none"
@@ -534,16 +516,16 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
534 files.map((file) => { 516 files.map((file) => {
535 const isSelected = selectedFiles.has(file.path) 517 const isSelected = selectedFiles.has(file.path)
536 const fileName = file.path.split('/').pop() || file.path 518 const fileName = file.path.split('/').pop() || file.path
537 519
538 return ( 520 return (
539 <TableRow 521 <TableRow
540 key={file.path} 522 key={file.path}
541 className={`hover:bg-muted/50 ${isSelected ? "bg-muted/30" : ""}`} 523 className={`hover:bg-muted/50 ${isSelected ? "bg-muted/30" : ""}`}
542 > 524 >
543 <TableCell className="w-[40px]" onClick={(e) => e.stopPropagation()}> 525 <TableCell className="w-[40px]" onClick={(e) => e.stopPropagation()}>
544 <Checkbox 526 <Checkbox
545 checked={isSelected} 527 checked={isSelected}
546 onCheckedChange={() => toggleFileSelection(file.path)} 528 onCheckedChange={() => toggleFileSelection(file.path)}
547 /> 529 />
548 </TableCell> 530 </TableCell>
549 <TableCell className="font-medium"> 531 <TableCell className="font-medium">
@@ -552,7 +534,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
552 <> 534 <>
553 <Folder className="h-4 w-4 text-blue-500 flex-shrink-0" /> 535 <Folder className="h-4 w-4 text-blue-500 flex-shrink-0" />
554 <div className="min-w-0 max-w-[60vw]"> 536 <div className="min-w-0 max-w-[60vw]">
555 <Link 537 <Link
556 href={`/drive${file.path}`} 538 href={`/drive${file.path}`}
557 className="text-blue-600 hover:text-blue-800 hover:underline cursor-pointer block truncate" 539 className="text-blue-600 hover:text-blue-800 hover:underline cursor-pointer block truncate"
558 title={fileName} 540 title={fileName}
@@ -606,8 +588,8 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
606 Info 588 Info
607 </DropdownMenuItem> 589 </DropdownMenuItem>
608 <DropdownMenuSeparator /> 590 <DropdownMenuSeparator />
609 <DropdownMenuItem 591 <DropdownMenuItem
610 onClick={() => handleDelete([file.path])} 592 onClick={() => handleDelete([file.path])}
611 className="text-red-600" 593 className="text-red-600"
612 > 594 >
613 <Trash2 className="mr-2 h-4 w-4" /> 595 <Trash2 className="mr-2 h-4 w-4" />
@@ -695,7 +677,7 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
695 </div> 677 </div>
696 <div> 678 <div>
697 <Label className="text-sm font-medium text-muted-foreground">Path</Label> 679 <Label className="text-sm font-medium text-muted-foreground">Path</Label>
698 <p className="text-sm font-mono text-xs">{currentItem.path}</p> 680 <p className="font-mono text-xs">{currentItem.path}</p>
699 </div> 681 </div>
700 </div> 682 </div>
701 <div className="flex justify-end"> 683 <div className="flex justify-end">
@@ -749,13 +731,13 @@ export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, us
749 </DialogContent> 731 </DialogContent>
750 </Dialog> 732 </Dialog>
751 733
752 <input 734 <input
753 ref={fileInputRef} 735 ref={fileInputRef}
754 type="file" 736 type="file"
755 multiple 737 multiple
756 className="hidden" 738 className="hidden"
757 onChange={handleFileUpload} 739 onChange={handleFileUpload}
758 /> 740 />
759 </div> 741 </div>
760 ) 742 )
761} \ No newline at end of file 743}