diff --git a/frontend/src/components/buckets/ObjectDetailsView.tsx b/frontend/src/components/buckets/ObjectDetailsView.tsx index 47e2d9f..4bd330f 100644 --- a/frontend/src/components/buckets/ObjectDetailsView.tsx +++ b/frontend/src/components/buckets/ObjectDetailsView.tsx @@ -8,7 +8,7 @@ import { IconTile } from '@/components/ui/icon-tile'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { ArrowLeft, ChevronRight, Copy, Download, File, Loader2, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; -import { formatBytes } from '@/lib/file-utils'; +import { downloadObject, formatBytes } from '@/lib/file-utils'; import { formatDate } from '@/lib/utils'; function CardSection({ title, children }: { title: string; children: React.ReactNode }) { @@ -73,22 +73,9 @@ export function ObjectDetailsView() { toast.success(label); }; - const handleDownload = async () => { + const handleDownload = () => { if (!bucketName || !objectKey) return; - try { - const blob = await objectsApi.get(bucketName, objectKey); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = fileName || 'download'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Download started'); - } catch { - // error toast handled by axios interceptor - } + downloadObject(bucketName, objectKey); }; const handleDelete = async () => { diff --git a/frontend/src/components/buckets/ObjectsTable.tsx b/frontend/src/components/buckets/ObjectsTable.tsx index 4c5dea8..f063442 100644 --- a/frontend/src/components/buckets/ObjectsTable.tsx +++ b/frontend/src/components/buckets/ObjectsTable.tsx @@ -14,7 +14,7 @@ import { } from '@/components/ui/dropdown-menu'; import {ChevronLeft, ChevronRight, Download, Eye, FileIcon, FolderIcon, Loader2, MoreVertical, Trash2} from 'lucide-react'; import {Select, SelectOption} from '@/components/ui/select'; -import {formatBytes, formatRelativeTime} from '@/lib/file-utils'; +import {downloadObject, formatBytes, formatRelativeTime} from '@/lib/file-utils'; import type {S3Object} from '@/types'; interface ObjectsTableProps { @@ -353,7 +353,7 @@ export function ObjectsTable({ View Details - + downloadObject(bucketName, obj.key)}> Download diff --git a/frontend/src/lib/file-utils.ts b/frontend/src/lib/file-utils.ts index f9a015b..93d0f39 100644 --- a/frontend/src/lib/file-utils.ts +++ b/frontend/src/lib/file-utils.ts @@ -1,3 +1,27 @@ +import { objectsApi } from './api'; +import { toast } from 'sonner'; + +/** + * Download an object from a bucket by fetching it as a blob and clicking a + * temporary anchor element. Errors are surfaced by the axios interceptor. + */ +export async function downloadObject(bucket: string, key: string): Promise { + try { + const blob = await objectsApi.get(bucket, key); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = key.split('/').pop() || 'download'; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Download started'); + } catch { + // error toast handled by axios interceptor + } +} + /** * Get the file type based on file extension */