/** * App shell: pickers, the panel, and the tab bar. * * Angular: useState is roughly a component field plus change detection. Setting * it re-runs this function, so derived values below are recomputed each render; * useMemo is the explicit opt-out when that stops being cheap. */ import { useMemo, useState } from "react"; import { ENTRIES } from "./domain/entries"; import { controlName } from "./domain/format"; import { CTX } from "./domain/labels"; import { entriesByTarget, entriesForLayer, layerCounts, orderLayerKeys } from "./domain/layers"; import type { ContextCode } from "./domain/types"; import { Panel } from "./ui/Panel"; import styles from "./App.module.css"; const TABS = ["Explore", "Drill", "Find", "Rules"] as const; /** Drawn, not typed: caret glyphs sit outside every Latin font subset. */ function Caret() { return ( ); } const layerLabel = (key: string): string => key === "" ? "NO MODIFIER" : key.split("+").map(controlName).join(" + "); export function App() { const [ctx, setCtx] = useState("S"); const [layer, setLayer] = useState(""); const [tab, setTab] = useState<(typeof TABS)[number]>("Explore"); const counts = useMemo(() => layerCounts(ENTRIES, ctx), [ctx]); const layers = useMemo(() => orderLayerKeys(counts.keys()), [counts]); // Changing mode can strip the held layer out from under us. const activeLayer = counts.has(layer) ? layer : ""; const onLayer = useMemo( () => entriesForLayer(ENTRIES, ctx, activeLayer), [ctx, activeLayer], ); const lit = useMemo(() => entriesByTarget(onLayer), [onLayer]); const held = activeLayer === "" ? [] : activeLayer.split("+"); return ( { const order = Object.keys(CTX) as ContextCode[]; setCtx(order[(order.indexOf(ctx) + 1) % order.length]!); }} > {CTX[ctx].toUpperCase()} { const i = layers.indexOf(activeLayer); setLayer(layers[(i + 1) % layers.length]!); }} > {layerLabel(activeLayer)} {onLayer.length} e.short), }} /> {TABS.map((name) => ( setTab(name)} > {name.toUpperCase()} ))} ); }