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:
@@ -5,12 +5,15 @@
|
||||
|
||||
interface Props {
|
||||
photos: Photo[];
|
||||
selectedId?: string | null;
|
||||
onSelect?: (index: number) => void;
|
||||
selectedIds?: string[];
|
||||
onSelect?: (index: number, event: MouseEvent) => void;
|
||||
onOpen?: (index: number) => void;
|
||||
onDragStart?: (index: number, event: DragEvent) => void;
|
||||
}
|
||||
|
||||
let { photos, selectedId = null, onSelect, onOpen }: Props = $props();
|
||||
let { photos, selectedIds = [], onSelect, onOpen, onDragStart }: Props = $props();
|
||||
|
||||
const isSelected = (id: string) => selectedIds.includes(id);
|
||||
</script>
|
||||
|
||||
{#if photos.length === 0}
|
||||
@@ -24,13 +27,16 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="border-border bg-bg-muted group relative block aspect-square w-full overflow-hidden rounded-lg border {photo.id ===
|
||||
selectedId
|
||||
draggable={isSelected(photo.id)}
|
||||
class="border-border bg-bg-muted group relative block aspect-square w-full overflow-hidden rounded-lg border {isSelected(
|
||||
photo.id
|
||||
)
|
||||
? 'ring-primary ring-2'
|
||||
: ''}"
|
||||
aria-pressed={photo.id === selectedId}
|
||||
onclick={() => onSelect?.(i)}
|
||||
aria-pressed={isSelected(photo.id)}
|
||||
onclick={(e) => onSelect?.(i, e)}
|
||||
ondblclick={() => onOpen?.(i)}
|
||||
ondragstart={(e) => onDragStart?.(i, e)}
|
||||
>
|
||||
{#if photo.thumb_url}
|
||||
<img
|
||||
@@ -38,7 +44,7 @@
|
||||
alt={photo.original_filename}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="h-full w-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||
class="pointer-events-none h-full w-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||
/>
|
||||
{:else}
|
||||
<span class="text-text-subtle flex h-full w-full items-center justify-center text-xs">
|
||||
|
||||
Reference in New Issue
Block a user