feat(frontend): show the clocks in replays

Each player's clock after the shown move, from the saved move times; games saved before the game clock show none.
This commit is contained in:
Halit Aksoy 2026-10-16 01:40:05 +03:00
parent 20359b7a8f
commit 8671bb2553
2 changed files with 14 additions and 3 deletions

View File

@ -7,6 +7,7 @@ import {
DEFAULT_ANIMATION_INTERVAL,
GameRecord,
getBoardAfterMove,
getClockAtMove,
getColorByBrightness,
getPlayerDisplayName,
PitAnimator,
@ -28,6 +29,7 @@ import HeaderbarIcon from "../headerbar/HeaderbarIcon";
import HeaderbarTitle from "../headerbar/HeaderbarTitle";
import ThemeSwitchMenu from "../headerbar/ThemeSwitchMenu";
import PageContainer from "../PageContainer";
import GameClockView from "../GameClockView";
import Row from "../Row";
import MoveList from "./MoveList";
@ -149,6 +151,12 @@ const ReplayView: FunctionComponent<{ context: Context, theme: Theme, record: Ga
? [record.player1, stones[PLAYER1_STORE], record.player2, stones[PLAYER2_STORE]]
: [record.player2, stones[PLAYER2_STORE], record.player1, stones[PLAYER1_STORE]];
const atEnd = moveNumber >= total;
// Each player's clock after the shown move; games saved before the game clock have none.
const clockTimes = getClockAtMove(record, moveNumber);
const renderClock = (player: PlayerSummary) => clockTimes && (
<GameClockView context={context} running={false}
timeMs={player === record.player1 ? clockTimes.player1Ms : clockTimes.player2Ms} />
);
const controlButton = (label: string, title: string, onClick: () => void, disabled = false) => (
<button className="control" title={title} aria-label={title} disabled={disabled} onClick={onClick}
@ -183,7 +191,7 @@ const ReplayView: FunctionComponent<{ context: Context, theme: Theme, record: Ga
)}
</div>
<div className="player" style={{ color: textColor }}>{`${name(topPlayer)} · ${topStore}`}</div>
<div className="player" style={{ color: textColor }}>{`${name(topPlayer)} · ${topStore}`}{renderClock(topPlayer)}</div>
<BoardView
game={createGameAtMove(baseGame, moves, moveNumber)}
context={context}
@ -191,7 +199,7 @@ const ReplayView: FunctionComponent<{ context: Context, theme: Theme, record: Ga
boardViewModel={boardViewModel}
revert={revert}
onPitSelect={() => undefined} />
<div className="player" style={{ color: textColor }}>{`${name(bottomPlayer)} · ${bottomStore}`}</div>
<div className="player" style={{ color: textColor }}>{`${name(bottomPlayer)} · ${bottomStore}`}{renderClock(bottomPlayer)}</div>
<div className="controls" style={{ color: textColor }}>
<div className="buttons">
@ -248,6 +256,9 @@ const ReplayView: FunctionComponent<{ context: Context, theme: Theme, record: Ga
.player {
margin: 6px 0;
font-weight: bold;
display: flex;
align-items: center;
gap: 8px;
}
.controls {
flex-shrink: 0;

View File

@ -15,7 +15,7 @@ Work through the tasks in order; each ends with its tests passing and a commit.
- [x] 5. **Web app, live game**: `useGameClock`, `GameClockView`, clocks in `GamePage` on desktop and mobile
layouts; remove the offline countdown, and with it `TURN_TIMEOUT_MS`, `INACTIVE_TIMEOUT_MS`,
`UserConnectionInfo.offlineSince` and `OpponentOfflineCountdown` from core (the web app uses them until then).
- [ ] 6. **Web app, replay**: clocks in `ReplayView` from `getClockAtMove`.
- [x] 6. **Web app, replay**: clocks in `ReplayView` from `getClockAtMove`.
- [ ] 7. **Mobile app**: clocks in `GameScreen`; type check and bundle.
- [ ] 8. **End to end and docs**: Chrome tests from `design.md`; README, `AGENTS.md` and `TODO.md` (release the mobile
update before or with the backend; the out of scope items); mark `game-history` R3.3 and R3.4 as replaced.