diff options
Diffstat (limited to 'frontend/components/drive/DriveDirectoryClient.tsx')
| -rw-r--r-- | frontend/components/drive/DriveDirectoryClient.tsx | 88 |
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" | |||
| 32 | import { toast } from "@/hooks/use-toast" | 32 | import { toast } from "@/hooks/use-toast" |
| 33 | import { DriveLsEntry } from "@/lib/drive_types" | 33 | import { DriveLsEntry } from "@/lib/drive_types" |
| 34 | import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants" | 34 | import { UPLOAD_MAX_FILE_SIZE, UPLOAD_MAX_FILES } from "@/lib/constants" |
| 35 | import { formatFileSize } from "@/lib/utils" | ||
| 35 | import { DriveMoveDialog } from "./DriveMoveDialog" | 36 | import { DriveMoveDialog } from "./DriveMoveDialog" |
| 36 | import { StorageUsage } from "./StorageUsage" | 37 | import { StorageUsage } from "./StorageUsage" |
| 37 | import type { StorageData } from "@/lib/storage" | 38 | import type { StorageData } from "@/lib/storage" |
| 38 | import type { UserAuth } from "@/lib/auth_types" | ||
| 39 | |||
| 40 | function 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 | ||
| 48 | function formatDate(timestamp: number): string { | 40 | function 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 { | |||
| 52 | function formatDateTime(timestamp: number): string { | 44 | function 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 | ||
| 78 | export function DriveDirectoryClient({ path, files, breadcrumbs, storageData, user }: DriveDirectoryClientProps) { | 69 | export 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 | } |
