Two of these were live: PageBuilderPanel still compared kind === 'gallery' in two places, so a carousel block's editor and its Choose button were both dead branches. The type checker could not see them — both compare against string. The rest is copy somebody reads: "Change gallery", "Name a new gallery", "Reading the gallery", "Add to this gallery", Home's Gallery heading, and the "Create Gallery" file action, now "Carousel from folder". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
142 lines
6.6 KiB
TypeScript
142 lines
6.6 KiB
TypeScript
import { Navigate } from 'react-router';
|
|
import { href, useNav } from './routes';
|
|
import { pageParam, usePages } from './usePages';
|
|
import { DEFAULT_SECTION } from './sections';
|
|
import { sectionFor, sectionOpen, type SectionKey } from './sections';
|
|
import { AdministrationPanel } from './AdministrationPanel';
|
|
import { MediaPanel } from './MediaPanel';
|
|
import { CollectionsPanel } from './sections/CollectionsPanel';
|
|
import { PiecesPanel } from './sections/PiecesPanel';
|
|
import { CarouselsPanel } from './sections/CarouselsPanel';
|
|
import { LayoutPanel } from './sections/LayoutPanel';
|
|
import { PageBuilderPanel } from './sections/PageBuilderPanel';
|
|
import { ViewModeProvider } from './ViewMode';
|
|
import { MessagesPanel } from './MessagesPanel';
|
|
import { PlannedSection } from './PlannedSection';
|
|
import { useReady } from './useReady';
|
|
|
|
/**
|
|
* The right column: whichever section the nav has selected.
|
|
*
|
|
* It reads `?section=` from the URL rather than being handed it — the two panels are siblings in a
|
|
* workspace and neither owns the other.
|
|
*
|
|
* ── There used to be a `?config=1` ──
|
|
*
|
|
* The connection form was an overlay reached from a link in this header, because it was not one of the
|
|
* sections. Now it IS one — Administration — so the overlay is gone and so is the link. A second route
|
|
* to the same screen is a second thing to keep correct, and the two disagreed the moment a section was
|
|
* selected underneath a form that covered it.
|
|
*
|
|
* ── Not ready means Administration, whatever the URL says ──
|
|
*
|
|
* The gate is `useReady`, shared with the nav so the two cannot disagree. Enforced HERE and not only
|
|
* there, because the nav can only decline to offer a link — `?section=carousels` typed into the address
|
|
* bar, or a bookmark from before the connection was forgotten, arrives without going through it. A
|
|
* nav that hides a door is a courtesy; this is the lock.
|
|
*/
|
|
|
|
/**
|
|
* Which component renders which section.
|
|
*
|
|
* A map rather than a conditional on `built`. The version of this that read
|
|
* `built ? <MessagesPanel/> : <Planned/>` was correct only while exactly one section existed — the
|
|
* second one to be finished would have rendered Messages under its own name, which is the kind of bug
|
|
* that looks like a data problem for an hour. A key with no entry here falls through to Planned, so
|
|
* marking a section `built` in sections.ts without writing its panel fails visibly rather than
|
|
* silently showing somebody else's.
|
|
*/
|
|
const PANELS: Partial<Record<SectionKey, () => React.JSX.Element>> = {
|
|
layout: LayoutPanel,
|
|
collections: CollectionsPanel,
|
|
pieces: PiecesPanel,
|
|
carousels: CarouselsPanel,
|
|
messages: MessagesPanel,
|
|
administration: AdministrationPanel,
|
|
media: MediaPanel,
|
|
};
|
|
|
|
export const DetailPanel = () => {
|
|
/*
|
|
A page selected inside Pages opens the BUILDER, not the list with a row lit up. By SLUG, so the URL
|
|
says which page rather than which row — see pageParam in usePages.
|
|
|
|
The nav says the same thing — Pages is a section and the pages are rows under it — so the detail
|
|
panel follows it. The second path segment is what distinguishes the two, which keeps both
|
|
addressable: `/fofos-admin/pages` is the list, `/fofos-admin/pages/home` is that page.
|
|
*/
|
|
const { section: selected, item: selectedPage, atIndex } = useNav();
|
|
const { pages } = usePages();
|
|
|
|
const { ready, rootSet, loading } = useReady();
|
|
|
|
/*
|
|
The bare `/fofos-admin` redirected to its first section, once, replacing.
|
|
|
|
The screen has to render SOMETHING there, and rendering Layout while the address bar says nothing
|
|
means the URL stops describing what is on screen — which is the whole reason the sections moved out
|
|
of the query string. `replace`, so Back leaves the admin rather than bouncing off the redirect.
|
|
*/
|
|
if (atIndex) return <Navigate to={href(DEFAULT_SECTION)} replace />;
|
|
|
|
if (loading) {
|
|
return <div className="flex size-full items-center justify-center p-6 text-sm text-muted-foreground">Loading…</div>;
|
|
}
|
|
|
|
/*
|
|
Not ready, so Administration regardless of what the URL asked for. The header then NAMES it, which
|
|
is the part the old overlay could not do: the form used to appear under whichever row happened to
|
|
be highlighted, so it read as that section being broken rather than as one step not done yet.
|
|
*/
|
|
/*
|
|
A section that cannot be opened sends you to Administration, because that is where every requirement
|
|
is satisfied. Asked per section rather than by one flag: Media is reachable the moment a root
|
|
location exists, long before fofos is connected.
|
|
*/
|
|
const wanted = sectionFor(selected);
|
|
const current = sectionOpen(wanted, { ready, rootSet }) ? wanted : sectionFor('administration');
|
|
|
|
/*
|
|
Pages has no panel of its own any more.
|
|
|
|
It was a list of cards — a title, a slug, a menu label, per page — beside a nav that already lists
|
|
the pages. Two lists of the same thing, and the one you were looking at was never the one you had
|
|
clicked. The nav IS the list now, ordered by dragging it, so the section itself is only a way in.
|
|
|
|
`/fofos-admin/pages` therefore means "the first one", which is what somebody pressing Pages wants
|
|
and what the nav's own row already implied.
|
|
*/
|
|
if (current.key === 'pages' && !selectedPage) {
|
|
const first = pages?.[0];
|
|
if (first) return <Navigate to={href('pages', pageParam(first))} replace />;
|
|
return (
|
|
<div className="flex size-full items-center justify-center p-6 text-xs text-muted-foreground">
|
|
{pages ? 'This site has no pages yet.' : 'Loading…'}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Panel =
|
|
current.key === 'pages' && selectedPage ? PageBuilderPanel : current.built ? PANELS[current.key] : undefined;
|
|
|
|
return (
|
|
<div className="flex size-full flex-col overflow-hidden">
|
|
{/*
|
|
No bar.
|
|
|
|
It held the device toggle and Sync / Build / Publish across the top of this panel. The toggle
|
|
moved to the top of the EDITOR, which is where the settings it scopes already are; the three
|
|
actions are out until they have a place that is theirs rather than a corner of the panel that
|
|
happens to be pinned. `SyncButton` and `PublishButton` still exist and are still mounted
|
|
nowhere — see git history for what the bar looked like.
|
|
|
|
`ViewModeProvider` stays. It is not the bar's: the preview and the two panels with one read
|
|
`device` from it, and `usePreviewable` is how a section says it has a page to show.
|
|
*/}
|
|
<ViewModeProvider>
|
|
<div className="min-h-0 flex-1">{Panel ? <Panel /> : <PlannedSection section={current} />}</div>
|
|
</ViewModeProvider>
|
|
</div>
|
|
);
|
|
};
|