E2EE direct messages, the chat dock, the Messages module, the message WebSocket and its realtime registry, and the messages / message_keys tables (migration 0097; the downgrade restores the empty schema). Prod held two welcome notes, no message between people and one key — never used (owner decision, E34). WebSocket now has no owner module: the lint bans it everywhere. Privacy policy updated: it no longer promises E2EE for a feature that is gone.
102 lines
5.2 KiB
TypeScript
102 lines
5.2 KiB
TypeScript
import type { ComponentType } from "react";
|
|
import { lazyWithPreload, type PreloadableComponent } from "../lib/lazyWithPreload";
|
|
import type { Page } from "../lib/urlState";
|
|
import type { PanelId, PanelLayout } from "../lib/panelLayout";
|
|
import Sidebar from "./Sidebar";
|
|
import PlaylistsRail from "./PlaylistsRail";
|
|
import { Feed, FeedPage } from "./modulePages";
|
|
|
|
// The RENDER half of the module registry: which component (and side rail) each page renders. The
|
|
// metadata half — nav order, label, icon, gate — lives in lib/modules.ts (imported by NavSidebar /
|
|
// Header / App's gate check). Together they let App drive the whole shell from `page` alone, with no
|
|
// `page === X` chrome conditionals.
|
|
//
|
|
// Every content entry is a PROP-FREE component: it reads `me` from useMe() and its module-local state
|
|
// from the scoped providers, so App renders `<Content />` uniformly. The two that need per-render
|
|
// wiring get a thin wrapper in modulePages (FeedPage supplies the GLOBAL feed scope; the channel page
|
|
// threads its own isolated scope to <Feed> directly — see the scope note in FeedFiltersProvider).
|
|
|
|
// Route-level code splitting: each page is its own lazy chunk (admin-only pages never reach
|
|
// non-admins; the logged-out landing pulls only the shell). Rendered inside App's <Suspense>. Each
|
|
// one is preloadable: App fetches the chunks of every page the account can reach once the first page
|
|
// is on screen (preloadPages below), so switching modules never shows the chunk fallback.
|
|
const Home = lazyWithPreload(() => import("./Home"));
|
|
const Channels = lazyWithPreload(() => import("./Channels"));
|
|
const Playlists = lazyWithPreload(() => import("./Playlists"));
|
|
const PlexBrowse = lazyWithPreload(() => import("./PlexBrowse"));
|
|
const PlexSidebar = lazyWithPreload(() => import("./PlexSidebar"));
|
|
const NotificationsPanel = lazyWithPreload(() => import("./NotificationsPanel"));
|
|
const DownloadCenter = lazyWithPreload(() => import("./DownloadCenter"));
|
|
const Stats = lazyWithPreload(() => import("./Stats"));
|
|
const Scheduler = lazyWithPreload(() => import("./Scheduler"));
|
|
const ConfigPanel = lazyWithPreload(() => import("./ConfigPanel"));
|
|
const AdminUsers = lazyWithPreload(() => import("./AdminUsers"));
|
|
const AuditLog = lazyWithPreload(() => import("./AuditLog"));
|
|
const AdminLogs = lazyWithPreload(() => import("./AdminLogs"));
|
|
const SettingsPanel = lazyWithPreload(() => import("./SettingsPanel"));
|
|
|
|
export type PageDef = {
|
|
// Prop-free by construction: reads `me` + module state from hooks/providers. Preloadable by type, so
|
|
// a new page cannot silently opt out of preloadPages.
|
|
Component: PreloadableComponent<object>;
|
|
// The content column's max width. The header + toolbar centre on this same width (via --content-mw)
|
|
// so the floating chrome lines up with the centred content instead of spanning the whole column.
|
|
// MUST match the max-w the page's own content wrapper uses. Omit for a full-width page (feed).
|
|
maxWidth?: string;
|
|
};
|
|
|
|
// page id → the component App renders in the content column (+ its shell chrome config).
|
|
export const PAGE_CONTENT: Record<Page, PageDef> = {
|
|
home: { Component: Home, maxWidth: "80rem" },
|
|
// The two wrappers preload the lazy chunk they render.
|
|
feed: { Component: Object.assign(FeedPage, { preload: Feed.preload }) },
|
|
channels: { Component: Channels, maxWidth: "96rem" },
|
|
playlists: { Component: Playlists, maxWidth: "64rem" },
|
|
plex: { Component: PlexBrowse, maxWidth: "100rem" },
|
|
notifications: { Component: NotificationsPanel, maxWidth: "48rem" },
|
|
downloads: { Component: DownloadCenter, maxWidth: "56rem" },
|
|
stats: { Component: Stats },
|
|
scheduler: { Component: Scheduler },
|
|
config: { Component: ConfigPanel },
|
|
users: { Component: AdminUsers, maxWidth: "48rem" },
|
|
audit: { Component: AuditLog },
|
|
logs: { Component: AdminLogs },
|
|
settings: { Component: SettingsPanel },
|
|
};
|
|
|
|
// The uniform side-rail contract — shell layout/collapse only; the rail reads its module state from
|
|
// its provider and `me` from useMe().
|
|
type RailProps = {
|
|
layout: PanelLayout;
|
|
setLayout: (l: PanelLayout) => void;
|
|
collapsed: boolean;
|
|
onToggleCollapse: () => void;
|
|
};
|
|
|
|
// Which collapse flag a rail uses (feed + plex share the filter rail's flag; playlists has its own).
|
|
type RailCollapseKey = "filter" | "playlists";
|
|
|
|
export type RailDef = {
|
|
Rail: ComponentType<RailProps>;
|
|
// The persisted panel-layout record this rail edits (App owns the panelLayouts map).
|
|
panelId: PanelId;
|
|
collapse: RailCollapseKey;
|
|
};
|
|
|
|
// page id → its side rail (only the three pages that have one). App renders it in the rail slot.
|
|
export const PAGE_RAIL: Partial<Record<Page, RailDef>> = {
|
|
feed: { Rail: Sidebar, panelId: "feed", collapse: "filter" },
|
|
plex: { Rail: PlexSidebar, panelId: "plex", collapse: "filter" },
|
|
playlists: { Rail: PlaylistsRail, panelId: "playlists", collapse: "playlists" },
|
|
};
|
|
|
|
/** Fetch the code of these pages and of their side rails now, ahead of their first visit. Errors are
|
|
* swallowed: a chunk that fails to preload is simply fetched (and its failure surfaced) on visit. */
|
|
export function preloadPages(pages: Page[]): void {
|
|
for (const p of pages) {
|
|
PAGE_CONTENT[p].Component.preload().catch(() => {});
|
|
const rail = PAGE_RAIL[p]?.Rail as Partial<PreloadableComponent<RailProps>> | undefined;
|
|
rail?.preload?.().catch(() => {});
|
|
}
|
|
}
|