feat: photo multiselect, move/copy, trash and compact chrome

- backend: soft delete + trash (photos.deleted_at, migration 000005), GET /trash, restore, permanent delete with storage-key refcount (S3 objects removed only when no other album references them); batch endpoints photo-batch/{delete,move,copy} - metadata only, copies clone rows sharing storage keys
- frontend: Ctrl/Shift multiselect; right-panel 'Действия' as icon text rows (Открыть (просмотр), Переместить, Копировать, Удалить); drag selected photos onto rail album -> TransferDialog; /trash route; trash entry as last item in the left rail; empty right panel hidden on overview
- frontend: compact top bars (app header and viewer), compact theme switcher
- openspec: sync lightroom-workspace-ui artifacts (photos/ui-workspace specs, design #20-24, tasks 9.11-9.17)
This commit is contained in:
iu
2026-10-10 22:13:17 +03:00
parent 78996bc053
commit 2d6a3fea1a
27 changed files with 1097 additions and 85 deletions
+9 -10
View File
@@ -99,8 +99,8 @@
const topClass = $derived(
isFullscreen
? `border-border bg-chrome absolute inset-x-0 top-0 z-30 flex items-center justify-between gap-2 border-b px-3 py-2 transition-transform duration-200 ${showTop ? 'translate-y-0' : '-translate-y-full'}`
: 'border-border bg-chrome flex shrink-0 items-center justify-between gap-2 border-b px-3 py-2'
? `border-border bg-chrome absolute inset-x-0 top-0 z-30 flex items-center justify-between gap-1 border-b px-2 py-0.5 transition-transform duration-200 ${showTop ? 'translate-y-0' : '-translate-y-full'}`
: 'border-border bg-chrome flex shrink-0 items-center justify-between gap-1 border-b px-2 py-0.5'
);
const bottomClass = $derived(
isFullscreen
@@ -289,22 +289,21 @@
}
const btn =
'border-border text-text hover:bg-bg-muted inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm';
const dangerBtn =
'border-border text-danger hover:bg-bg-muted inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm';
'text-text-muted hover:bg-bg-muted hover:text-text inline-flex items-center justify-center rounded-md p-1';
const dangerBtn = 'text-danger hover:bg-bg-muted inline-flex items-center justify-center rounded-md p-1';
</script>
<svelte:window onkeydown={onKey} onmousemove={onWinMouseMove} />
<div bind:this={viewerEl} class="bg-canvas relative flex h-full flex-col overflow-hidden">
<div bind:this={topEl} class={topClass}>
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
<button type="button" class={btn} aria-label="Назад" title="Назад" onclick={onClose}>
<Icon name="arrow_back" />
<Icon name="arrow_back" class="size-4" />
</button>
<span class="text-text-muted text-sm">{index + 1} / {photos.length}</span>
<span class="text-text-muted text-xs">{index + 1} / {photos.length}</span>
</div>
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
<button
type="button"
class={btn}
@@ -315,7 +314,7 @@
aria-pressed={isFullscreen}
onclick={toggleViewerFullscreen}
>
<Icon name={isFullscreen ? 'fullscreen_exit' : 'fullscreen'} />
<Icon name={isFullscreen ? 'fullscreen_exit' : 'fullscreen'} class="size-4" />
</button>
{#if fresh?.download_url}
<a