import type { DashboardDestination, DiscoverView, SettingsPageSection, SettingsSection, } from "../dashboard-routes.js"; import type { DashboardSettings, DashboardSettingsModule } from "../host-protocol.js"; import type { ShellIconName } from "./shell-icons.js"; /** * How each platform Settings section is named and described to a person (ADR 0061). One table, so * the navigation, the Settings landing page, and each section's own page say the same thing. Plain * words — never "Core", "platform module", or another implementation term (DESIGN.md §2.3). */ export const SETTINGS_SECTION_COPY: Readonly< Record< SettingsSection, { readonly label: string; readonly description: string; readonly icon: ShellIconName } > > = Object.freeze({ branding: { description: "Your church's name, logo, and colours.", icon: "settingsBranding", label: "Branding", }, displays: { description: "Screens paired with this installation.", icon: "settingsDisplays", label: "Displays", }, general: { description: "How this installation works for everyone.", icon: "settingsGeneral", label: "General", }, discover: { description: "Find, install, and manage the modules your church uses.", icon: "settingsDiscover", label: "Discover", }, system: { description: "Version, health, and diagnostics for this installation.", icon: "settingsSystem", label: "System", }, }); /** * Discover's views (ADR 0061). "Discover" is where module lifecycle lives in the navigation; inside * it the pages speak of modules — "Discover modules", "Installed modules", "Module updates" — * because Modules is the product's word for them. */ export const DISCOVER_VIEW_COPY: Readonly< Record< DiscoverView, { readonly label: string; readonly title: string; readonly description: string; readonly notYet: string; readonly icon: ShellIconName; } > > = Object.freeze({ browse: { description: "Modules add capabilities to your church's Dashboard. Install and manage the modules your church uses.", icon: "discoverBrowse", label: "Browse", notYet: "Finding and installing modules will happen here.", title: "Discover modules", }, installed: { description: "The modules installed for your church.", icon: "discoverInstalled", label: "Installed", notYet: "Seeing your installed modules, turning them on or off, inspecting them, and removing them will happen here.", title: "Installed modules", }, updates: { description: "Updates for your modules and for this installation.", icon: "discoverUpdates", label: "Updates", notYet: "Checking for and applying module and platform updates will happen here.", title: "Module updates", }, }); /** Where a platform section's navigation item leads: its page, or — for Discover — Browse. */ export function settingsSectionDestination(section: SettingsSection): DashboardDestination { return section === "discover" ? { kind: "discover", view: "browse" } : { kind: "settings-section", section }; } /** The module settings a person can open from navigation: running modules only. A disabled * module's settings are not offered, though its address still says it is unavailable. */ export function navigableModuleSettings( settings: DashboardSettings | "unavailable", ): readonly DashboardSettingsModule[] { return settings === "unavailable" ? [] : settings.modules.filter((module) => module.available); } /** Whether Settings has anything to offer this person. With nothing, the Settings link is not * shown at all, so nobody is shown administration they cannot carry out (DESIGN.md §9.2). */ export function hasVisibleSettings(settings: DashboardSettings | "unavailable"): boolean { return ( settings !== "unavailable" && (settings.sections.length > 0 || navigableModuleSettings(settings).length > 0) ); } export function isSettingsRoute(kind: string): boolean { return ( kind === "settings" || kind === "settings-section" || kind === "discover" || kind === "module-settings" ); } /** What a Settings address shows this person. */ export type SettingsPage = | { readonly kind: "home"; readonly settings: DashboardSettings } | { readonly kind: "section"; readonly section: SettingsPageSection } | { readonly kind: "discover"; readonly view: DiscoverView } | { readonly kind: "module"; readonly module: DashboardSettingsModule } | { readonly kind: "unavailable"; readonly reason: "no-access" | "module-disabled" } | { readonly kind: "load-failed" } | { readonly kind: "not-found" }; /** * Decides what a Settings address shows (ADR 0061), from what the server said this person may see: * * - a section or module they may see shows its page; * - a disabled module's settings say it is turned off — never a silent redirect; * - a section they may not see, or a module they know of but may not configure (or that has no * settings), says the settings aren't available, without saying which; * - a name that is no module they know of is not found. */ export function resolveSettingsPage( route: | { readonly kind: "settings" } | { readonly kind: "settings-section"; readonly section: SettingsPageSection } | { readonly kind: "discover"; readonly view: DiscoverView } | { readonly kind: "module-settings"; readonly moduleId: string }, settings: DashboardSettings | "unavailable", knownModuleIds: ReadonlySet, ): SettingsPage { if (settings === "unavailable") { return { kind: "load-failed" }; } if (route.kind === "settings") { return { kind: "home", settings }; } if (route.kind === "discover") { return settings.sections.includes("discover") ? { kind: "discover", view: route.view } : { kind: "unavailable", reason: "no-access" }; } if (route.kind === "settings-section") { return settings.sections.includes(route.section) ? { kind: "section", section: route.section } : { kind: "unavailable", reason: "no-access" }; } const module = settings.modules.find((candidate) => candidate.id === route.moduleId); if (module) { return module.available ? { kind: "module", module } : { kind: "unavailable", reason: "module-disabled" }; } return knownModuleIds.has(route.moduleId) ? { kind: "unavailable", reason: "no-access" } : { kind: "not-found" }; } /** The browser tab's name for a Settings page. */ export function settingsPageTitle(page: SettingsPage): string { switch (page.kind) { case "home": return "Settings"; case "section": return `${SETTINGS_SECTION_COPY[page.section].label} settings`; case "discover": return DISCOVER_VIEW_COPY[page.view].title; case "module": return `${page.module.name} settings`; case "unavailable": return page.reason === "module-disabled" ? "This module is turned off" : "These settings aren't available"; case "load-failed": return "Settings could not be loaded"; case "not-found": return "Page not found"; } }