Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
4.8 kB · 144 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145/** * The frame around a screen: the masthead, the footer, and the page heading a * screen starts with. None of it is a screen, and every screen uses some of it. * * The destinations the masthead lists are in destinations.ts, which the * layout that renders it reads too. */
import { alphaStamp } from "./alpha-stamp";import { apiVersion as sharedVersion } from "./api";import { type AboutPageId, destinationForRoute } from "./destinations";import { el } from "./dom";import { PROJECT_LINKS } from "./project-links";import { DISCLAIMER_AFTER, DISCLAIMER_BEFORE, DISCLAIMER_LINK_HREF, DISCLAIMER_LINK_TEXT,} from "./site-disclaimer";
export type Route = | "home" | "matches" | "camo" | "hangar" | "about" | `about/${AboutPageId}` | "lobby" | "daily";
const navLinks = new Map<string, HTMLAnchorElement>();
/** * The masthead is in the page already: the layout renders it, because the * site has pages the app never runs on and a nav that waited for JavaScript * would be missing from all of them. All that is left here is to find the * links. * * It is chrome, not content, so it lives outside #app and is never torn down — * a screen change moves the current-page marker and nothing else. */export function collectNavLinks(): void { const links = document.querySelectorAll<HTMLAnchorElement>( "#masthead a[data-route]", ); for (const link of links) { const route = link.dataset.route; if (route) navLinks.set(route, link); }}
/** * Put the current-page marker on one masthead link, or on none. * * Null is the not-found screen: the address is not a destination, so no link * in the bar is the page the player is on, and marking one would tell a screen * reader they are somewhere the screen says does not exist. * * A page under a destination marks the destination: a reader in the FAQ is in * About, and About is the only one of the two the bar has a link for. */export function markCurrent(route: Route | null): void { const current = route === null ? null : destinationForRoute(route); for (const [candidate, link] of navLinks) { if (candidate === current) link.setAttribute("aria-current", "page"); else link.removeAttribute("aria-current"); }}
/** A page heading, and the one line under it that says what the page is for. */export function pageHead(title: string, lead?: string): HTMLElement { return el( "div", { className: "page-head" }, lead ? [ el("h1", { textContent: title }), el("p", { className: "lead", textContent: lead }), ] : [el("h1", { textContent: title })], );}
// The site's own build, stamped by scripts/build.sh. The dev server has no// build to name, and a stray production build without the stamp says so.const SITE_VERSION: string = import.meta.env.DEV ? "dev" : ((import.meta.env.VITE_BUILD_REF as string | undefined) ?? "unstamped");
// Fetched once per page load; every footer render shares the answer, and so// does anything else that asks - see `apiVersion` in api.ts.const apiVersion = sharedVersion();
export function footer(): HTMLElement { const debug = el("p", { className: "debug", textContent: `site ${SITE_VERSION}`, }); void apiVersion.then((info) => { const parts = [`site ${SITE_VERSION}`]; if (info) { parts.push(`api ${info.version}${info.build ? ` (${info.build})` : ""}`); if (info.arena) parts.push(`arena ${info.arena}`); } else { parts.push("api unreachable"); } debug.textContent = parts.join(" · "); });
return el("footer", {}, [ // The bottom of whatever screen the player stopped on. Same word as the // masthead's, so the page says it where a reader arrives and where they // run out of page; components/Footer.astro carries it on the pages the // app never runs on. alphaStamp("foot"), // The disclaimer, off site-disclaimer.ts so components/Footer.astro's copy // on the prerendered pages cannot drift from this one — the address behind // "our credits page" least of all. el("p", { className: "footer-disclaimer" }, [ DISCLAIMER_BEFORE, el("a", { href: DISCLAIMER_LINK_HREF, textContent: DISCLAIMER_LINK_TEXT, }), DISCLAIMER_AFTER, ]), projectLinks(), debug, ]);}
/** * The account and the repositories, on every page of the app. * * components/Footer.astro carries the same row on the pages the app never * runs on, off the same list - see project-links.ts. */function projectLinks(): HTMLElement { const row = el("p", { className: "footer-links" }); for (const link of PROJECT_LINKS) { row.append(el("a", { href: link.href, textContent: link.label })); } return row;}