Skip to content

Media Clipboard

A persistent Zustand clipboard of media resources for side-by-side comparison. Survives page navigation and supports both server resources and local file uploads.


Setup

MediaClipboardFab is rendered globally in ClientProviders. It auto-shows when the store has items. No page-level setup is needed.


Adding Items Programmatically

import { useMediaClipboardStore } from "@/store/mediaClipboardStore";

const { addItem, addLocalItem, removeByResourceId, clearAll } =
  useMediaClipboardStore();

// Server resource from API
addItem(resource);

// Local file (image only, max 30 MB)
await addLocalItem(file);

// Remove by API resource ID
removeByResourceId(resource.id);

// Clear everything
clearAll();

addItem deduplicates by resource.id — adding the same resource twice is a no-op.


ClipboardItem Type

interface ClipboardItem {
  clipboardId: string;  // UUID assigned at add time
  resource: MediaResourceMinimal | MediaResourcePublic;
  addedAt: string;      // ISO timestamp
  isLocal?: boolean;    // true for local file uploads
  localBlobKey?: string; // key into local blob storage (IndexedDB)
}

Local Uploads

  • Max size: 30 MB
  • Allowed types: images only
  • Storage: blobs stored in IndexedDB via localMediaStorage
  • Image dimensions are probed automatically on add
  • isLocal: true marks the item; localBlobKey maps to the blob
  • removeByResourceId cleans up the blob from storage
  • clearAll purges all local blobs

Compare Mode

The clipboard content cycles through three states:

grid  →  pinned (one image fixed as "A")  →  comparing (side-by-side)
  • grid — default gallery of all items
  • pinned — one item is pinned; clicking another enters compare mode
  • comparing — two items displayed side-by-side; Escape exits back to grid

State is controlled internally by MediaClipboardContent.


FAB Behavior

MediaClipboardFab is a fixed-position button in the bottom-right corner:

  • Shows a badge with the current item count
  • Hidden when the store is empty
  • Draggable — drag threshold is 5 px; a move below the threshold is treated as a click
  • Click opens the clipboard as a floating window

Each clipboard item is assigned a unique gallery key so images open in an isolated lightbox:

galleryKey = "clipboard-{clipboardId}"

Store Persistence

The store uses Zustand persist middleware:

Property Value
localStorage key "media-clipboard"
Current schema version 2
Migration v1 → v2 adds isLocal and localBlobKey fields

Source Files

File Contents
src/store/mediaClipboardStore.ts Zustand store, ClipboardItem type, persistence config
src/components/MediaClipboard/MediaClipboardFab.tsx Draggable FAB
src/components/MediaClipboard/MediaClipboardContent.tsx Grid view + compare mode orchestration
src/components/MediaClipboard/MediaClipboardCompareView.tsx Side-by-side compare layout