a new section above Administration. it embeds THE PLATFORM'S OWN file browser rather than drawing a file list — upload, delete, rename, move, thumbnails, the context menu and every task hanging off it already exist and are maintained in one place. a plugin's own would be a second to keep in step, arriving without any of that. `basePath` IS A JAIL, not a starting folder: `useFileBrowserApp` resets currentPath whenever it leaves, so pointing it at `<root>/media/originals` means the breadcrumb stops there and `derived/` is not reachable by navigating. that is what was asked for — people work in originals, derived is made for them. IT IS A GUARDRAIL AND NOT A PERMISSION BOUNDARY, and the panel says so in its own docstring because the difference will matter to whoever reads this next. the file-browser API is unchanged and still addresses the whole home: anyone with file access reaches derived/ through the Files screen. this prevents an accident, not a determined person. confining it properly means scoping that API per user, which is a platform change and a much larger one. the panel also says, once, that derived/ exists and is made from these — otherwise somebody who knows about it would think it had been lost. `rootRelative` is new on the settings API because only the SERVER knows what home is. the file browser addresses everything relative to a root, and a browser deriving that prefix from the absolute path would be guessing. null when the root sits outside home — a legitimate setting the browser cannot address, which the panel explains rather than rendering an empty folder. NOTHING HERE IS FOFOS-SPECIFIC, which matters now that this plugin is the template for other site admins: the root is a setting, media/originals is the layout this plugin creates, and both are read from the API. another site's plugin changes the label. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
3.4 KiB
TypeScript
77 lines
3.4 KiB
TypeScript
import { useSearchParams } from 'react-router';
|
|
import { DEFAULT_SECTION, isSectionKey, sectionFor, type SectionKey } from './sections';
|
|
import { AdministrationPanel } from './AdministrationPanel';
|
|
import { MediaPanel } from './MediaPanel';
|
|
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=gallery` 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>> = {
|
|
messages: MessagesPanel,
|
|
administration: AdministrationPanel,
|
|
media: MediaPanel,
|
|
};
|
|
|
|
export const DetailPanel = () => {
|
|
const [params] = useSearchParams();
|
|
const raw = params.get('section');
|
|
const selected: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION;
|
|
|
|
const { ready, loading } = useReady();
|
|
|
|
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.
|
|
*/
|
|
const current = sectionFor(ready ? selected : 'administration');
|
|
|
|
const Panel = current.built ? PANELS[current.key] : undefined;
|
|
|
|
return (
|
|
<div className="flex size-full flex-col overflow-hidden">
|
|
<div className="flex items-center justify-between gap-3 border-b border-border px-4 py-2">
|
|
<h2 className="truncate text-sm font-medium">{current.label}</h2>
|
|
<span className="truncate text-xs text-muted-foreground">{current.hint}</span>
|
|
</div>
|
|
|
|
<div className="min-h-0 flex-1">{Panel ? <Panel /> : <PlannedSection section={current} />}</div>
|
|
</div>
|
|
);
|
|
};
|