The client

v3 · Preview

Framework v3 is an opt-in preview available to everyone. New games use the stable framework by default.

Display match history

API

Hook Contract
useHistory(options?) options is { ownPanel?: boolean }. Returns { entries: HistoryItem[]; more: boolean; open: boolean; setOpen: (open: boolean) => void }. entries is the match's log, oldest first, read from the opening to now and then each new row. more is true while later rows are still to be read. open is always false while no mounted component passes ownPanel: true. What ownPanel does: The log in the game.
useLogLinks(handler) void. While the component is mounted, handler gets a LogLinkEvent, { type: "enter" | "leave" | "click"; key: string }, each time a player's pointer or focus first reaches a link in Boardweaver's game log (enter), both have left it (leave) or the player clicks it (click). key is the link's key from link(key, text). See Links in the log.

A log line's link (link(key, text), Changing the state) is a control in the game log: Boardweaver's panel, the replay page and <History />. useLogLinks hears its enter, leave and click events, each with the link's key. Each enter has one leave with the same key. A click on a link does not move the board. Show what the key names however the game likes, such as the card in a side panel. Clear it only on the leave of the key it shows:

const [shownCard, setShownCard] = useState<string | null>(null);
useLogLinks((event) => {
  if (event.type === "enter") setShownCard(event.key);
  if (event.type === "leave") setShownCard((shown) => (shown === event.key ? null : shown));
});

Every viewer reads every key, so make a key name only what every viewer may see.

The game log lists moves and links to earlier positions in the match.
The game log lists moves and links to earlier positions in the match. Example data from the local development UI. Select the image to enlarge.

The log in the game

Boardweaver draws the match's log in its own panel. To draw the log in the game instead, such as in a side panel, call useHistory({ ownPanel: true }) in the component that draws it. While that component is mounted, Boardweaver hides its panel. Its history button keeps its count of the moves the player has not seen, and opens and closes the game's log instead (open, setOpen).

import { FormattedMessage } from "react-intl";
import { History, useHistory } from "boardweaver/react";

export function LogSidebar() {
  const { open, setOpen } = useHistory({ ownPanel: true });
  if (!open) return null;
  return (
    <aside>
      <button onClick={() => setOpen(false)}>
        <FormattedMessage id="action.close" />
      </button>
      <History kindStyles={{ capture: { color: "crimson" } }} />
    </aside>
  );
}

<History /> draws the log as Boardweaver's panel does: each decision under its player, the rows the player missed marked, and the row on the board marked with a bar in the theme's colors.accent.500 along its leading edge, over a tint of that color. It takes the text color of where you place it. A tap on a row moves the board there, and its links reach useLogLinks. Boardweaver writes its own words in the log (a row for a move or an opening your game wrote no line for, an undo, the divider above the missed rows, and each row's age) in the language the frame shows your game in.

How <History /> scrolls:

  • It is its own scrolling element (overflowY: "auto" unless style sets overflowY or overflow), so it never scrolls the game's page.
  • To make it scroll, give it a maxHeight in style, or another bound on its height.
  • It opens with its newest row at the bottom. While the player stays at the bottom, it keeps each new row in view.
  • Each time another row becomes the row on the board, it scrolls that row into view. Rows that arrive while the board stays on its row never make it scroll.
  • It lays its rows out from the bottom up with display: "flex" and flexDirection: "column-reverse", so its style takes every CSS property but those two.
  • A wrapper that scrolls instead keeps the browser's own scrollbar. <History /> never scrolls the wrapper.

The log's own scrollbar is thin and in the log's text color. To change it, set scrollbarColor (the thumb's color, then the track's) or scrollbarWidth ("auto", "thin" or "none") in style. Each one replaces only its own default, so this scrollbar stays thin:

<History style={{ maxHeight: 320, scrollbarColor: "#b8862b transparent" }} />

Chrome and Edge ignore ::-webkit-scrollbar rules on an element whose scrollbarColor or scrollbarWidth is not "auto", and the elements inside <History /> take its scrollbarColor. To style the log's scrollbar with those rules in a <style> element, set both to "auto" in style.

To draw the log yourself, read useHistory().entries. Each line is a FormattedLine, as in What changed. A decision's steps hold every step, also a step that writes no line. endsLogRow is true on the last step of each of Boardweaver's rows, so a log that groups steps by it shows the same rows. While more is true, the last step read of the latest decision can have endsLogRow false: its row goes on in steps not read yet, so draw it as an open row.

Need a hand with this?

Join our Discord