HistoryPage
is an async component
The history: a branch's entries by day, each with what it changed by name and count (an entry that re-hashed
hundreds of definitions but edited four reads as small), under one namespace if asked.
import { historyList } from "#codebase/tcmts/cli/changes"; import type { HistoryItem } from "#codebase/tcmts/cli/changes"; import { ago, at, codeHref, entriesIn, EntryBadges, entrySummary, Frame, HistoryFilter, inSlices, shareNow, } from "#codebase/tcmts/share"; import type { EntrySummary } from "#codebase/tcmts/share"; import { entryTitle } from "#codebase/tcmts/store"; import { Link } from "react-aria-components"; export async function HistoryPage({ branch, under, page, }: { branch: string; under: string; page: number; }) { const s = shareNow(); if (!s.signedIn) return <Frame tab="history" title="Sign in" children={null} />; const b = branch && s.cb.store.hasBranch(branch) ? branch : s.cb.store.branch().name; const per = 30; const all = historyList(s.cb, under ? 400 : (page + 1) * per + 1, b); const inside = (n: string) => n === under || n.startsWith(`${under}.`); const touches = (e: EntrySummary) => [...e.edited, ...e.added, ...e.removed, ...e.renamed.map((r) => r.to)].some(inside); // (an entry whose names under it are all as they were can't have changed one: no need to tell how) const trees = s.cache.trees(s.cb); const near = (h: HistoryItem) => trees .delta(s.cb.store.causal(h.parents[0]!)?.namespace, s.cb.store.causal(h.hash)!.namespace) .some((d) => inside(d.path)); const matching = under ? [] : all; if (under) await inSlices( all.map((h) => () => near(h) && touches(entrySummary(s, h.hash)) && matching.push(h)), ); const listed = matching.slice(page * per, page * per + per); await inSlices(listed.map((h) => () => entrySummary(s, h.hash))); const more = matching.length > (page + 1) * per; const days = new Map<string, typeof listed>(); for (const h of listed) { const day = new Date(h.ts).toISOString().slice(0, 10); if (!days.has(day)) days.set(day, []); days.get(day)!.push(h); } const q = (p: number) => at( `/history?${new URLSearchParams({ branch: b, ...(under ? { under } : {}), page: String(p) })}`, ); return ( <Frame tab="history" title="History"> <div className="page cols-2"> <div> <HistoryFilter branches={s.cb.store.branches()} branch={b} under={under} /> <div className="muted" style={{ margin: "0 0 14px" }}> {under ? ( <> Entries on <code>{b}</code> that change <code>{under}</code> </> ) : ( <> {entriesIn(s, s.cb.store.head(b)).toLocaleString("en")} entries on <code>{b}</code> </> )} </div> {[...days].map(([day, entries]) => ( <div className="box" key={day}> <div className="boxhead"> <b>{day}</b> </div> {entries.map((h) => { const e = entrySummary(s, h.hash); const names = [...e.edited, ...e.added, ...e.removed]; return ( <div className="hrow" key={h.hash}> <a className="hash" href={at(`/history/${h.hash}`)}> {h.short} </a> <div> <a href={at(`/history/${h.hash}`)} style={{ color: "var(--fg)", overflowWrap: "anywhere" }} > <b>{entryTitle(h)}</b> </a> {h.parents.length > 1 && <span className="pill">merge</span>} {h.message && <div className="muted">{h.command}</div>} <div className="badges"> <EntryBadges e={e} /> </div> {names.length > 0 && ( <div className="names"> {names.slice(0, 6).map((n, i) => ( <span key={n}> {i > 0 && " · "} <a href={codeHref(n)}>{n}</a> </span> ))} {names.length > 6 && ` +${names.length - 6}`} </div> )} </div> <span className="when">{ago(h.ts)}</span> </div> ); })} </div> ))} {!listed.length && ( <div className="empty"> No entries{under ? ` change ${under} in the latest 400` : ""}. </div> )} <div className="filters"> {page > 0 && ( <Link className="btn" href={q(page - 1)}> ← Newer </Link> )} {more && ( <Link className="btn" href={q(page + 1)}> Older → </Link> )} </div> </div> <aside className="side"> <div className="sect"> <h4>Reading history</h4>Each entry is one command that changed names: an update, a merge, a rename, called by its message where it was given one. A rename is a rename, never a delete and an add; reformatting is no change at all. </div> <div className="sect"> <h4>Re-hashed</h4> <span className="badge b-prop">re-hashed</span> counts definitions that changed only because something they use did: there's nothing in them to read. </div> <div className="sect"> <h4>Undo</h4> <code>tcm revert <entry></code> takes one entry back, three-way, and nothing after it. </div> </aside> </div> </Frame> ); }
uses
- tcmts.cli.changes.HistoryItem
- tcmts.cli.changes.historyList
- tcmts.share.EntryBadges
- tcmts.share.EntrySummary
- tcmts.share.Frame
- tcmts.share.HistoryFilter
- tcmts.share.ago
- tcmts.share.at
- tcmts.share.codeHref
- tcmts.share.entriesIn
- tcmts.share.entrySummary
- tcmts.share.inSlices
- tcmts.share.shareNow
- tcmts.store.entryTitle