MediaField is controlled: it reports every pointer move and draws what it is handed back. Wired straight to the block, that was a PATCH and a full reload of the preview per move, and the page went white while somebody dragged. The drag runs against local state now — instant — and the write is debounced by useAutosave, the same hook every other control here uses. The reset effect keys on the url and the shape, not on the crop. Keyed on the crop it would fight the drag: the parent's value is the last SAVED one, so every move would snap back until the save landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { MediaField } from '../MediaField';
|
|
import { useAutosave } from '../useAutosave';
|
|
|
|
/**
|
|
* A photograph block's picture and its framing.
|
|
*
|
|
* ── Why the crop is held here ──
|
|
*
|
|
* `MediaField` is controlled: it reports every pointer move and draws whatever it is handed back. Wired
|
|
* straight to the block, that meant a PATCH and a full reload of the preview PER MOVE — the page went
|
|
* white while somebody dragged, which is what this exists to stop.
|
|
*
|
|
* So the drag runs against local state, which is instant, and the write is debounced by `useAutosave`
|
|
* — one request when the hand stops, not sixty on the way there. The same trade every other control in
|
|
* this admin makes, and the same hook.
|
|
*
|
|
* ── Reset by IDENTITY, not by value ──
|
|
*
|
|
* The effect keys on the url and the shape rather than on the crop. Keyed on the crop it would fight
|
|
* the drag: the parent's value is the last SAVED one, so every move would be pulled back to it until
|
|
* the save landed.
|
|
*/
|
|
export const BlockMedia = ({
|
|
url,
|
|
crop,
|
|
aspect,
|
|
onUrl,
|
|
onCrop,
|
|
}: {
|
|
url: string | null;
|
|
crop: string | null;
|
|
/** Tailwind's ratio, as the page spells it — `21/9`, `1/1`. */
|
|
aspect: string;
|
|
onUrl: (url: string | null) => void;
|
|
onCrop: (crop: string | null) => void;
|
|
}) => {
|
|
const [draft, setDraft] = useState<string | null>(crop);
|
|
|
|
useEffect(() => setDraft(crop), [url, aspect]);
|
|
|
|
useAutosave(draft !== crop ? (draft ?? '') : null, false, () => onCrop(draft));
|
|
|
|
return (
|
|
<MediaField
|
|
value={url}
|
|
onChange={onUrl}
|
|
crop={draft}
|
|
onCropChange={setDraft}
|
|
aspect={aspect}
|
|
label="Choose a photograph"
|
|
/>
|
|
);
|
|
};
|