- Playlists: a link over a playlist the account does not hold plays alone; the rail's own fallback no longer counts as a pick that gives the player up. - The linked opener opens or clears nothing once the address names another player, and stops looking (and fetching its row) once it settled. - Files: a list the server's cap cut — the sorted or searched one too — keeps the link, with a notice of its own (EN/HU). - History: an in-place view rewrite (search refine, count, channel to channel, sub-view) drops a reloaded entry's overlay marks (replaceView); a landing on a channel opened from a search keeps the search; the Feed press on a search opens the plain feed. - Files/Playlists: an emptied search is written at once. - Client log: plf/cf (column text filters) are typed keys; the shell has none of its own. - Contract: writers outside lib/ take a literal or a lib/ function's result; every URL key is sorted typed or not; the writer regexes are held by a fixture; sub-views named; the bookmark net reads page tags. - Feed defaults in one constant; one live-search queue helper; Scheduler writes through the Activity codec; notRun copy (EN/HU) names Forward and a plain "Search YouTube" button.
62 lines
4.0 KiB
TypeScript
62 lines
4.0 KiB
TypeScript
import type { ComponentType, ReactNode } from "react";
|
|
import type { LucideIcon } from "lucide-react";
|
|
import type { Me } from "./api";
|
|
import type { Page } from "./urlState";
|
|
import type { PreloadableComponent } from "./lazyWithPreload";
|
|
|
|
// What a module says about itself: one definition per page, in its own folder (`modules/<page>/module.ts`,
|
|
// handed out through its index.ts), collected in nav order by features/navigation/moduleRegistry. A new
|
|
// page is its folder, its id in lib/urlState, and one line in that registry.
|
|
export type ModuleDef = {
|
|
id: Page;
|
|
// i18n key for the SHORT display name — the label the nav rail and the palette show.
|
|
labelKey: string;
|
|
// The SHORT name under the icon when the user turns the rail labels on (B7): the rail is 3.75rem
|
|
// wide with labels or without, so it has to fit there ("Alerts", "Listák"). The full `labelKey`
|
|
// stays the tooltip and the accessible name.
|
|
railKey: string;
|
|
// i18n key for the LONGER, descriptive browser-tab title (e.g. "Channel manager" vs the rail's
|
|
// "Channels"). Absent → the tab title falls back to `labelKey`. Read via `pageTitleKey`.
|
|
titleKey?: string;
|
|
icon: LucideIcon;
|
|
// Admin/system modules render in their own section (below a divider) in the nav rail.
|
|
system?: boolean;
|
|
// Carries a live unread/active count in the rail (the number itself is computed in NavSidebar,
|
|
// which owns the polling queries; this only flags which modules have one).
|
|
hasBadge?: boolean;
|
|
// Availability for THIS account, derived from language-independent account state (plex toggle,
|
|
// admin role). Absent = always available. Drives the reachable set: the rail, the palette, the
|
|
// `g` chords, and which page a link may land on.
|
|
gate?: (me: Me) => boolean;
|
|
// The page itself: lazy, its own chunk, fetched on the first visit or by the idle preload. Prop-free:
|
|
// it reads `me` and its module's state from hooks.
|
|
Page: PreloadableComponent<object>;
|
|
// Its side rail, if it has one: lazy like the page, and prop-free (its layout and collapse flag are
|
|
// its own, lib/useRail).
|
|
Rail?: 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;
|
|
// The module's store, if it has one: the state its page, its rail and its readers elsewhere (the
|
|
// palette, the notifications, the header search) share. Mounted at the root for the whole session
|
|
// (features/navigation/ModuleStores), so what it holds in memory survives a visit to another page.
|
|
// Static, unlike the page: it is on the boot path by design. It reads no other module's store, so
|
|
// the order the stores nest in does not matter (moduleStores.contract.test).
|
|
Store?: ComponentType<{ children: ReactNode }>;
|
|
// Every address-bar key this page's view writes: its filters, sort, tab, the item it shows. The URL
|
|
// IS the view, so a bookmark reopens it. The shell clears them all on a page switch, and opens a page
|
|
// with no other page's keys in its address (NavigationProvider), so one page's state never rides
|
|
// along onto another's URL. Empty for a page with nothing of its own in the URL. Each key belongs to
|
|
// one module and none is a shell key (moduleUrl.contract.test).
|
|
urlKeys: readonly string[];
|
|
// Those of them that carry what the user TYPED (a search box's text, a column filter map holding a
|
|
// text filter): the client log keeps their values out below its most detailed level (lib/logHooks).
|
|
// moduleUrl.contract.test sorts every key of urlKeys as typed or not, so a new one is decided.
|
|
typedUrlKeys?: readonly string[];
|
|
// Its page plays YouTube videos in the player and reopens it from an address that names one (a
|
|
// reload, a bookmark — lib/playerUrlState), with its own list as the queue. A page without it gets
|
|
// the shell's player for such an address: the video alone.
|
|
playsVideos?: boolean;
|
|
};
|