draft the crop instead of saving every pointer move
Panning went berserk. Wired straight to MediaField, `onCropChange` fired on every move, each move saved, each save reloaded the row, and the reload handed the crop back as the last SAVED value — yanking the picture out from under the drag. BlockMedia exists for exactly this and I went around it. It keeps the crop in a local draft, saves once when the drag stops, and resets on the url and the shape rather than on the crop — its own note says why, and the failure it describes is the one I reproduced. Also: an empty picture slot looked the same whether the carousel was still being read or genuinely had nothing in it. `undefined` now means loading and `null` means empty, and the line underneath says which. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { RichText } from '../RichText';
|
import { RichText } from '../RichText';
|
||||||
import { useAutosave } from '../useAutosave';
|
import { useAutosave } from '../useAutosave';
|
||||||
import { MediaField } from '../MediaField';
|
import { BlockMedia } from './BlockMedia';
|
||||||
import { SHAPES } from './shapes';
|
import { SHAPES } from './shapes';
|
||||||
import type { SubjectRecord, SubjectSection } from './subjects';
|
import type { SubjectRecord, SubjectSection } from './subjects';
|
||||||
|
|
||||||
@@ -106,18 +106,29 @@ export const SubjectFields = <T extends SubjectRecord>({
|
|||||||
*/}
|
*/}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs text-muted-foreground">Main image</label>
|
<label className="text-xs text-muted-foreground">Main image</label>
|
||||||
<MediaField
|
{/*
|
||||||
value={cover}
|
`BlockMedia`, not `MediaField` directly.
|
||||||
onChange={(path) => save({ cover_url: path, ...(path ? {} : { cover_crop: null }) })}
|
|
||||||
|
It holds the crop in a local draft and saves once, when the drag stops. Wired straight to
|
||||||
|
`MediaField` this saved on every pointer move — and each save reloaded the row, which handed
|
||||||
|
the crop back as the last SAVED value and yanked the picture out from under the drag. That is
|
||||||
|
the exact failure the note at the top of BlockMedia describes, and the reason it exists.
|
||||||
|
*/}
|
||||||
|
<BlockMedia
|
||||||
|
url={cover}
|
||||||
crop={record.cover_crop}
|
crop={record.cover_crop}
|
||||||
onCropChange={(crop) => save({ cover_crop: crop })}
|
|
||||||
aspect={aspect}
|
aspect={aspect}
|
||||||
label="Choose the main image"
|
onUrl={(path) => save({ cover_url: path, ...(path ? {} : { cover_crop: null }) })}
|
||||||
|
onCrop={(crop) => save({ cover_crop: crop })}
|
||||||
/>
|
/>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{record.cover_url
|
{record.cover_url
|
||||||
? 'Chosen for this one. Remove it to go back to the first picture in its carousel.'
|
? 'Chosen for this one. Remove it to go back to the first picture in its carousel.'
|
||||||
: 'From the first picture in its carousel. Choose one to override that.'}
|
: firstPhoto === undefined
|
||||||
|
? 'Reading its carousel…'
|
||||||
|
: firstPhoto
|
||||||
|
? 'From the first picture in its carousel. Choose one to override that.'
|
||||||
|
: 'Its carousel is empty. Choose a picture, or add some in Carousels.'}
|
||||||
{record.cover_shape === null && ' No shape is set on the page, so it draws at its own.'}
|
{record.cover_shape === null && ' No shape is set on the page, so it draws at its own.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ export const SubjectPanel = <T extends SubjectRecord>({ section }: { section: Su
|
|||||||
record={selected}
|
record={selected}
|
||||||
// What the cover falls back to when the row has not chosen one — the same first item the
|
// What the cover falls back to when the row has not chosen one — the same first item the
|
||||||
// grid below is already showing, so the two cannot disagree about which picture that is.
|
// grid below is already showing, so the two cannot disagree about which picture that is.
|
||||||
firstPhoto={photos?.[0]?.path ?? null}
|
// `undefined` while the carousel is still being read, `null` when it genuinely has none.
|
||||||
|
// The two look identical in an empty picture slot, and only one of them is a problem.
|
||||||
|
firstPhoto={photos === null ? undefined : (photos[0]?.path ?? null)}
|
||||||
onSaved={() => void load()}
|
onSaved={() => void load()}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
|
|||||||
Reference in New Issue
Block a user