feat: drag photos onto a heap row to add them
Lightroom-style direct manipulation: pick up a photo (or a multi- selection) and drop it on a heap to add it. Complements the P shortcut without replacing it. PhotoThumbnail - Becomes draggable. onDragStart reads the current selection from the photo store at fire time: if the dragged photo is part of the selection, the payload is the whole selection; otherwise it's just that one photo. Matches Finder semantics. - Payload uses a custom MIME (application/x-mulita-photos) so the drop target can recognise our drags vs. arbitrary file drags from the OS. Also sets text/plain so dropping outside the app shows a sensible "N photos" string. HeapsPanel - Each heap row is now a drop target. onDragOver previews the drop effect and highlights the row with a primary ring and a faint background tint. onDragLeave only clears the highlight if the cursor actually left the row (not just moved over a child). - New dropMutation handles the drop: optimistic membership cache update so the basket affordance flips immediately, rollback on error from a captured `previous`, success toast naming the heap and the count of newly-added photos, onSettled invalidation of heaps + heap-photo-ids + photos so server truth re-syncs. PhotoThumbnail's title attribute now mentions the drag affordance alongside click/double-click/shift+click/ctrl+click hints. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@ import { Star, ShoppingBasket, Trash2, RefreshCw, Check } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { photos as photosApi } from '../../services/api'
|
||||
import type { Photo } from '../../types/photo'
|
||||
import { usePhotoStore } from '../../store/photoStore'
|
||||
|
||||
/** Custom MIME used by HeapsPanel to recognise our drag payload. */
|
||||
export const PHOTO_DRAG_MIME = 'application/x-mulita-photos'
|
||||
|
||||
// Auto-retry schedule (ms). Backend generates thumbs on-demand via Celery, so
|
||||
// first hit often 404s. Try a few times with backoff before giving up.
|
||||
@@ -94,6 +98,22 @@ export function PhotoThumbnail({
|
||||
return () => clearRetryTimer()
|
||||
}, [])
|
||||
|
||||
// Build the drag payload at fire time so multi-selection drags carry the
|
||||
// current selection. If the dragged photo isn't part of the selection,
|
||||
// drag just that one photo (matches Finder semantics).
|
||||
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
const state = usePhotoStore.getState()
|
||||
const ids =
|
||||
state.selectedPhotos.includes(photo.id) && state.selectedPhotos.length > 0
|
||||
? state.selectedPhotos
|
||||
: [photo.id]
|
||||
e.dataTransfer.effectAllowed = 'copy'
|
||||
e.dataTransfer.setData(PHOTO_DRAG_MIME, JSON.stringify(ids))
|
||||
// A plain text fallback so the OS shows something sensible if the user
|
||||
// drops outside the app.
|
||||
e.dataTransfer.setData('text/plain', `${ids.length} photo${ids.length > 1 ? 's' : ''}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -108,7 +128,9 @@ export function PhotoThumbnail({
|
||||
}}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
title="Click to select • Double-click to open • Shift+Click for range • Ctrl+Click to add"
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
title="Click to select • Double-click to open • Shift+Click for range • Ctrl+Click to add • Drag onto a heap to add"
|
||||
>
|
||||
{/* Thumbnail Image */}
|
||||
{!imageError ? (
|
||||
|
||||
Reference in New Issue
Block a user