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>
179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
import type { LucideIcon } from 'lucide-react';
|
|
import { Home, Info, Layers, Images, Handshake, Sparkles, Wrench, Settings, Mail, Cog, FolderOpen } from 'lucide-react';
|
|
|
|
/**
|
|
* The sections of the fofos admin, in nav order.
|
|
*
|
|
* ── The order is the client's site, not the database's ──
|
|
*
|
|
* It follows the sitemap: the pages someone edits most, first. Site settings, Messages and
|
|
* Administration sit at the bottom because they are not pages — the site's settings, an inbox, and the
|
|
* plugin's own machinery.
|
|
*
|
|
* ── `site` and `administration` are different things ──
|
|
*
|
|
* "Site settings" is the CLIENT's — partners, social links, contact details, all of it public copy that
|
|
* whoever edits the site edits. "Administration" is OFFICER's end: where fofos is, how to reach it, and
|
|
* whatever else this plugin needs to know to do its job. One is content, the other is plumbing, and the
|
|
* people who touch them are not the same people. Nothing in Administration ever appears on the site.
|
|
*
|
|
* ── `built` ──
|
|
*
|
|
* The panels arrive one section at a time, each verified against the live site before the next is
|
|
* started. A section that is not built yet says so, plainly, with what it will do — which is a better
|
|
* answer than a nav that hides most of itself and gives no sense of what is coming.
|
|
*
|
|
* It mirrors `built` in fofos's own `src/apps/web/site-map.ts`, and for the same reason: one list, so
|
|
* the nav and what actually renders cannot disagree.
|
|
*/
|
|
|
|
export type SectionKey =
|
|
| 'administration'
|
|
| 'media'
|
|
| 'messages'
|
|
| 'site'
|
|
| 'about'
|
|
| 'home'
|
|
| 'gallery'
|
|
| 'drops'
|
|
| 'collabs'
|
|
| 'collections'
|
|
| 'services';
|
|
|
|
export type Section = {
|
|
key: SectionKey;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
/** One line under the label in the nav — what this section is, for somebody who has not opened it. */
|
|
hint: string;
|
|
/** The fofos path this reads, relative to the plugin's mount. Null when nothing is built. */
|
|
path: string | null;
|
|
built: boolean;
|
|
/**
|
|
* Pinned to the FOOT of the nav rather than sitting in the list.
|
|
*
|
|
* A flag rather than the nav hard-coding a key, because "which sections are the site's and which are
|
|
* the plugin's own" is a fact about the section. It is also why this stays in one array: `sectionFor`
|
|
* and `isSectionKey` have to resolve every key, and a second exported list would be a second place to
|
|
* forget one.
|
|
*/
|
|
pinned?: boolean;
|
|
};
|
|
|
|
export const SECTIONS: Section[] = [
|
|
{
|
|
key: 'home',
|
|
label: 'Home',
|
|
icon: Home,
|
|
hint: 'The hero, the intro, and the gallery',
|
|
path: '/fofos-admin/home',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'about',
|
|
label: 'About Us',
|
|
icon: Info,
|
|
hint: 'The header photograph and the copy',
|
|
path: '/fofos-admin/about',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'collections',
|
|
label: 'Collections',
|
|
icon: Layers,
|
|
hint: 'Collections, and the items in them',
|
|
path: '/fofos-admin/collections',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'gallery',
|
|
label: 'Gallery',
|
|
icon: Images,
|
|
hint: 'Standalone pieces',
|
|
path: '/fofos-admin/gallery',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'collabs',
|
|
label: 'Collabs',
|
|
icon: Handshake,
|
|
hint: 'Projects with other people',
|
|
path: '/fofos-admin/collabs',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'drops',
|
|
label: 'Drops',
|
|
icon: Sparkles,
|
|
hint: 'Releases',
|
|
path: '/fofos-admin/drops',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'services',
|
|
label: 'Services',
|
|
icon: Wrench,
|
|
hint: 'A fixed tree — the words, not the shape',
|
|
path: '/fofos-admin/services',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'site',
|
|
label: 'Site settings',
|
|
icon: Settings,
|
|
hint: 'Partners, social links, contact details',
|
|
path: '/fofos-admin/site',
|
|
built: false,
|
|
},
|
|
{
|
|
key: 'messages',
|
|
label: 'Messages',
|
|
icon: Mail,
|
|
hint: 'What people sent through the contact form',
|
|
path: '/fofos-admin/messages',
|
|
built: true,
|
|
},
|
|
{
|
|
/*
|
|
The site's own media, browsed in place.
|
|
|
|
Above Administration and below the pages, because it is neither: the pages are the site's CONTENT
|
|
and Administration is this plugin's machinery, while this is the raw material both draw on. It is
|
|
also the section somebody uses most often once the site exists, which is the other reason it sits
|
|
where a hand falls rather than at the bottom.
|
|
|
|
`path: null` — it reads no fofos API. It embeds the platform's own file browser, confined to the
|
|
originals folder under the root location.
|
|
*/
|
|
key: 'media',
|
|
label: 'Media',
|
|
icon: FolderOpen,
|
|
hint: 'Photos and video the site is built from',
|
|
path: null,
|
|
built: true,
|
|
},
|
|
{
|
|
/*
|
|
Last, and `path: null` — it reads nothing from fofos. Every other section is a window onto the
|
|
client's site; this one is the window onto the connection itself, which is why it keeps working
|
|
when fofos is unreachable and the others do not.
|
|
*/
|
|
key: 'administration',
|
|
label: 'Administration',
|
|
icon: Cog,
|
|
hint: 'Where fofos is, and the token to reach it',
|
|
path: null,
|
|
built: true,
|
|
pinned: true,
|
|
},
|
|
];
|
|
|
|
/** Where the detail panel goes when the URL says nothing, or says something it does not recognise. */
|
|
export const DEFAULT_SECTION: SectionKey = 'messages';
|
|
|
|
export const isSectionKey = (value: string | null): value is SectionKey =>
|
|
SECTIONS.some((section) => section.key === value);
|
|
|
|
export const sectionFor = (key: SectionKey): Section =>
|
|
SECTIONS.find((section) => section.key === key) ?? SECTIONS[SECTIONS.length - 1]!;
|