import { memo, useState } from 'react'; import { useCroppedThumbnail } from '../../hooks/useCroppedThumbnail'; import '../../styles/LayersPanel.css'; /** * Layers panel. * * Lists all elements on the canvas in render order (last in array = topmost). * Click selects, shift-click toggles a row's membership in a multi-selection * set (S3) — when more than one is selected, the panel shows a "Delete N * selected" affordance and bulk-deletes on click. * * Per-row controls (Change 4 in [[Refinements_2026-05-20_Part2]]): * • To the LEFT of each row's main button: nothing (the previous ledger-style * close-X delete affordance moved to the right side and became a proper * trash-can icon). * • To the RIGHT of each row's main button, as siblings (NOT nested inside * the main button), in order: duplicate, lock/unlock, trash-can delete, * drag grip. These three actions used to live in the floating * ElementToolbar; co-locating them with the layer they target makes * them discoverable from the same place the user reasons about layers, * and makes them reachable without having to click the layer first. * * Drag handles on each row let the user reorder items via HTML5 DnD (S3). * Reordering produces a single history entry (the parent's reorder callback * uses replaceElements internally). * * Accessibility (S24): * • Each row is a flex container holding multiple )} ); }); /** * Per-row thumbnail for image and sticker layers. * * Exists as its own component so it can call `useCroppedThumbnail` * (a React hook, can't live inside the .map() loop in the parent's * render body). The hook returns a data URL of the cropped sub-region * when the element has a crop applied, or the raw `element.src` * otherwise — see the hook's docblock for the rasterization details. * * Memoized so a re-render of the parent panel (e.g. when an unrelated * layer's selection state changes) doesn't redo the thumbnail's load * and crop computation. The hook itself also caches by (src, crop) * signature, so even without memo this would be fast — but skipping * the render path entirely for unchanged elements is faster still. * * The visual styling (size, object-fit, etc.) lives on the existing * `.layers-item-icon-img` CSS class, unchanged from when this was an * inline in renderIcon. The only thing this wrapper changes * about the rendered output is which URL ends up in the `src` attr. */ const LayerThumb = memo(function LayerThumb({ element }) { const src = useCroppedThumbnail(element); // The hook returns null in the rare initial-render-with-no-src case; // fall back to the fallback dot to avoid emitting an with an // empty src (which would trigger a broken-image icon in some browsers). if (!src) { return ; } return ( ); });