Add section asks what shape, and creates one
Pressing the button drawn inside the page opens a dialog: a name, and a choice between one column and two, then Create section. The row lands in the database and the frame re-renders to show it. The layouts are DRAWN rather than named. "One column" is words for a shape, and a shape is faster to recognise than to read; the words stay underneath for anybody the picture does not reach. The name is asked for and not generated. It never appears on the site — it exists so a page of eight bands is navigable, and "Section 4" tells nobody anything, which is exactly the state a generated name would leave every page in. Asked at the only moment somebody knows what they are about to build. The frame is remounted after a write rather than the admin drawing the new band itself. The site renders from the database; the admin does not know how to render a page, and if it learnt it would be a second implementation of the very thing this preview exists to avoid. The canvas now finds the empty-page placeholder by its `data-page-empty` marker instead of taking `firstElementChild`. That was right for exactly as long as an empty page was the only kind — the moment a page has a section, the first child IS that section, and it would have hidden the thing being edited. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useClient } from 'hooks/useClient';
|
||||
import { readableError } from '../errors';
|
||||
import { SitePreview, useDevPort } from '../SitePreview';
|
||||
import { paintCanvas } from '../canvas';
|
||||
import { AddSectionDialog, type SectionLayout } from './AddSectionDialog';
|
||||
import { usePages, type Page } from '../usePages';
|
||||
|
||||
/**
|
||||
@@ -33,8 +34,16 @@ export const PageBuilderPanel = () => {
|
||||
const { pages, announce } = usePages();
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
/** Proof the frame is talking back. Replaced by the section picker once there are sections to pick. */
|
||||
const [asked, setAsked] = useState(0);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
/*
|
||||
Bumped after a write, and passed to SitePreview to remount the frame.
|
||||
|
||||
The page is rendered by the SITE, from the database, so a new section only appears when the document
|
||||
is fetched again. Nothing else can show it: the admin does not know how to draw a section, and if it
|
||||
did it would be drawing a second implementation of the page.
|
||||
*/
|
||||
const [rendered, setRendered] = useState(0);
|
||||
|
||||
/*
|
||||
The builder's chrome, painted into the rendered page.
|
||||
@@ -43,10 +52,26 @@ export const PageBuilderPanel = () => {
|
||||
be a new injection each render.
|
||||
*/
|
||||
const onDocument = useCallback(
|
||||
(frameDocument: Document) => paintCanvas(frameDocument, { onAddSection: () => setAsked((n) => n + 1) }),
|
||||
(frameDocument: Document) => paintCanvas(frameDocument, { onAddSection: () => setAdding(true) }),
|
||||
[],
|
||||
);
|
||||
|
||||
const createSection = async (values: { name: string; layout: SectionLayout }) => {
|
||||
if (!page) return;
|
||||
setCreating(true);
|
||||
setError(null);
|
||||
try {
|
||||
await clientRef.current.post(`/fofos-admin/pages/${page.id}/sections`, values);
|
||||
setAdding(false);
|
||||
// The site has to re-render for the new band to exist. See `rendered` above.
|
||||
setRendered((n) => n + 1);
|
||||
} catch (failure) {
|
||||
setError(readableError(failure));
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
The list, if nobody has announced it yet.
|
||||
|
||||
@@ -82,22 +107,14 @@ export const PageBuilderPanel = () => {
|
||||
|
||||
{/* The real page, at its own address, with the builder's own controls drawn inside it. `path`
|
||||
has no leading slash — Home's is the empty string, which the proxy resolves to the root. */}
|
||||
<SitePreview port={devPort} path={page.slug} onDocument={onDocument} />
|
||||
<SitePreview port={devPort} path={page.slug} onDocument={onDocument} reloadToken={rendered} />
|
||||
|
||||
{/*
|
||||
The other end of the wire.
|
||||
|
||||
Temporary, and it earns its place until the section picker exists: it is the only visible proof
|
||||
that a button drawn inside the rendered page reaches React out here. When there are sections to
|
||||
choose, this becomes the chooser.
|
||||
*/}
|
||||
<div className="shrink-0 rounded-lg border border-dashed border-border p-3 text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{asked === 0
|
||||
? 'Press Add section on the page.'
|
||||
: `Add section pressed ${asked} time${asked === 1 ? '' : 's'} — the page is talking to the panel.`}
|
||||
</p>
|
||||
</div>
|
||||
<AddSectionDialog
|
||||
open={adding}
|
||||
busy={creating}
|
||||
onCancel={() => setAdding(false)}
|
||||
onCreate={(values) => void createSection(values)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user