feat(volume-browser): phase 3 - editor integration & polish

- Browse and Download buttons on each volume row in the editor table
- Download entire volume as ZIP directly from the editor (no browser needed)
- File type icons for common extensions in browser
- Ephemeral volumes excluded from browse/download actions
This commit is contained in:
2026-04-01 23:06:19 +03:00
parent 6b54a72ec9
commit aacdd255a9
@@ -4,9 +4,13 @@
import * as api from '$lib/api'; import * as api from '$lib/api';
import { toasts } from '$lib/stores/toast'; import { toasts } from '$lib/stores/toast';
import { t } from '$lib/i18n'; import { t } from '$lib/i18n';
import { IconChevronRight, IconPlus, IconEdit, IconTrash, IconCheck, IconX } from '$lib/components/icons'; import { IconChevronRight, IconPlus, IconEdit, IconTrash, IconCheck, IconX, IconSearch, IconExternalLink } from '$lib/components/icons';
import Skeleton from '$lib/components/Skeleton.svelte'; import Skeleton from '$lib/components/Skeleton.svelte';
function downloadUrl(volId: string): string {
return api.volumeDownloadUrl(projectId, volId);
}
let volumes = $state<Volume[]>([]); let volumes = $state<Volume[]>([]);
let scopeInfos = $state<VolumeScopeInfo[]>([]); let scopeInfos = $state<VolumeScopeInfo[]>([]);
let loading = $state(true); let loading = $state(true);
@@ -253,6 +257,14 @@
</td> </td>
<td class="whitespace-nowrap px-4 py-2.5 text-right"> <td class="whitespace-nowrap px-4 py-2.5 text-right">
<div class="flex items-center justify-end gap-1"> <div class="flex items-center justify-end gap-1">
{#if vol.scope !== 'ephemeral'}
<a href="/projects/{projectId}/volumes/{vol.id}/browse" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-link)] transition-colors" title={$t('volumeBrowser.browse')}>
<IconSearch size={16} />
</a>
<a href={downloadUrl(vol.id)} target="_blank" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-emerald-50 hover:text-emerald-600 transition-colors" title={$t('volumeBrowser.download')}>
<IconExternalLink size={16} />
</a>
{/if}
<button type="button" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-link)] transition-colors" onclick={() => startEdit(vol)}><IconEdit size={16} /></button> <button type="button" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-link)] transition-colors" onclick={() => startEdit(vol)}><IconEdit size={16} /></button>
<button type="button" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-red-50 hover:text-red-600 transition-colors" onclick={() => handleDelete(vol.id)}><IconTrash size={16} /></button> <button type="button" class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-red-50 hover:text-red-600 transition-colors" onclick={() => handleDelete(vol.id)}><IconTrash size={16} /></button>
</div> </div>