diff --git a/src/App.tsx b/src/App.tsx index fe2e634..8c01409 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,12 @@ /** - * Explore: pick a mode and a layer, then read what the panel lights up. + * The shell. It owns the panel — there is only ever one — and each tab supplies + * what is lit, what the screen says, and what fills the strip and the card box. * - * Angular: useState is roughly a component field plus change detection. Setting - * it re-runs this function, so derived values are recomputed each render; - * useMemo is the explicit opt-out when that stops being cheap. + * Angular: useReducer is the closest thing to a service holding state with + * methods, except dispatch returns a new state rather than mutating one. */ -import { useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; +import { DECKS, boxDistribution, deckPool, knownCount, pickCard } from "./domain/drill"; import { ENTRIES } from "./domain/entries"; import { controlName } from "./domain/format"; import { CTX, CTXHINT } from "./domain/labels"; @@ -16,13 +17,16 @@ import { orderLayerKeys, } from "./domain/layers"; import type { ContextCode, ControlId, Entry } from "./domain/types"; +import { DrillCard } from "./ui/DrillCard"; +import { DrillStrip } from "./ui/DrillStrip"; import { InfoCard } from "./ui/InfoCard"; import { Panel } from "./ui/Panel"; +import { drillReducer, initialDrillState } from "./ui/drillReducer"; import styles from "./App.module.css"; const TABS = ["Explore", "Drill", "Find", "Rules"] as const; +type Tab = (typeof TABS)[number]; -/** Drawn, not typed: caret glyphs sit outside every Latin font subset. */ function Caret() { return (
-
- - - -
+ {isDrill ? ( + undefined} + /> + ) : ( +
+ + +
+ )} e.short), + lit={isDrill ? drillLit : exploreLit} + held={isDrill ? drill.chord : activeLayer === "" ? [] : activeLayer.split("+")} + selected={isDrill ? null : (selected?.tset[0] ?? null)} + onPress={(id) => { + if (isDrill) { + if (drill.answered) deal(); + else dispatch({ type: "press", id }); + return; + } + const here = exploreLit.get(id); + if (!here || here.length === 0) { + setSelected(null); + return; + } + const at = selected ? here.indexOf(selected) : -1; + setSelected(here[(at + 1) % here.length]!); }} + screen={ + isDrill + ? { + top: DECKS[drill.deck].name.toUpperCase(), + right: drill.answered ? (drill.lastCorrect ? "CORRECT" : "MISS") : "", + main: drill.card ? drill.card.name : "—", + chips: [], + } + : { + top: CTX[ctx].toUpperCase(), + right: layerLabel(activeLayer), + main: selected ? selected.short : `${onLayer.length} functions`, + chips: selected ? [] : onLayer.slice(0, 3).map((e) => e.short), + } + } /> - + {isDrill ? ( + dispatch({ type: "check" })} + onClear={() => dispatch({ type: "clear" })} + onReveal={() => dispatch({ type: "reveal" })} + onNext={deal} + onPickOption={(name) => dispatch({ type: "pickOption", name })} + /> + ) : ( + + )}