import type { Hono } from "hono"; import { knownObjects, languageLines } from "../mirror/queries.ts"; import { setting } from "../server/db.ts"; import { type Html, html } from "./html.ts"; import { cacheFor, type PageContext, page, plural } from "./layout.ts"; import { type LibraryRepo, publicLibrary, recentSaves, repoCards } from "./library.ts"; const TICKER_ROWS = 7; const TICKER_ITEMS = 10; const LATEST_OFFERED = 4; /** §21.1's front page and §21.3's browse. */ export function registerFrontPages(app: Hono, ctx: PageContext): void { app.get("/", (c) => { const library = publicLibrary(ctx); const tagline = setting(ctx.kepr, "tagline") ?? ""; // §21.1: distinct objects across every public repo's real saves. const known = new Set(library.flatMap((repo) => knownObjects(repo.mirror))).size; const latest = library .filter((repo) => repo.stats.updated !== null) .sort((a, b) => (b.stats.updated ?? 0) - (a.stats.updated ?? 0)) .slice(0, LATEST_OFFERED); cacheFor(c, false); return c.html( page(ctx, { title: "", description: tagline, path: "/", body: html`${ticker(library)}

The library knows
${plural(known, "thing")}.

${tagline && html`

${tagline}

`}
${wordCloud(library)}
${ latest.length > 0 && html`` }

run your own library →

`, }), ); }); app.get("/browse", (c) => { const library = publicLibrary(ctx); cacheFor(c, false); return c.html( page(ctx, { title: "browse", description: `Every public repository, ${plural(library.length, "repo")}.`, path: "/browse", body: html`

browse

${plural(library.length, "repo")}

${ library.length > 0 ? repoCards(library) : html`

nothing public yet

` }`, }), ); }); } // Rows of recent save messages drifting at different depths; the motion is CSS, and stops under // reduced motion. Each row's messages are written twice so sliding by half its width loops. function ticker(library: LibraryRepo[]): Html { const messages = recentSaves(library, TICKER_ROWS * TICKER_ITEMS).map( ({ save }) => save.message.split("\n")[0] as string, ); if (messages.length === 0) return html``; const rows = Array.from({ length: TICKER_ROWS }, (_, row) => { const items = Array.from( { length: TICKER_ITEMS }, (_, i) => messages[(row * 3 + i) % messages.length], ); const spans = items.map((message) => html`${message}`); return html`
${spans}${spans}
`; }); return html``; } // §21.1: file types (the header's languages) mixed with categories, each weighed by how many // public repos have it, as Kepr 2 weighed its cloud. function wordCloud(library: LibraryRepo[]): Html { const weights = new Map(); for (const repo of library) { const labels = languageLines(repo.mirror).map(([language]) => language); if (repo.settings.category) labels.push(repo.settings.category); for (const label of new Set(labels)) weights.set(label, (weights.get(label) ?? 0) + 1); } if (weights.size === 0) return html``; const max = Math.max(...weights.values()); // Ordered by a hash of the label, so the big words scatter instead of leading the cloud. const tags = [...weights].sort((a, b) => scatter(a[0]) - scatter(b[0])); return html`
${tags.map( ([label, weight]) => html`${label}`, )}
`; } function scatter(label: string): number { let hash = 0x9e3779b9; for (const char of label) hash = (Math.imul(31, hash) + (char.codePointAt(0) ?? 0)) | 0; return hash; }