mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
use component hooks
This commit is contained in:
parent
515dff6d7b
commit
3aa8c654cc
81 changed files with 5203 additions and 3173 deletions
|
|
@ -1,92 +1,24 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useWebClient } from '@app/hooks';
|
||||
import { GameSelectors, useAppSelector } from '@app/store';
|
||||
import type { Enriched } from '@app/types';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { formatElapsed, useGameLog } from './useGameLog';
|
||||
|
||||
import './GameLog.css';
|
||||
|
||||
const EMPTY_MESSAGES: Enriched.GameMessage[] = [];
|
||||
|
||||
function formatElapsed(totalSeconds: number): string {
|
||||
const s = Math.max(0, Math.floor(totalSeconds));
|
||||
const hh = String(Math.floor(s / 3600)).padStart(2, '0');
|
||||
const mm = String(Math.floor((s % 3600) / 60)).padStart(2, '0');
|
||||
const ss = String(s % 60).padStart(2, '0');
|
||||
return `${hh}:${mm}:${ss}`;
|
||||
}
|
||||
|
||||
export interface GameLogProps {
|
||||
gameId: number | undefined;
|
||||
}
|
||||
|
||||
function GameLog({ gameId }: GameLogProps) {
|
||||
const webClient = useWebClient();
|
||||
const messages = useAppSelector((state) =>
|
||||
gameId != null ? GameSelectors.getMessages(state, gameId) : EMPTY_MESSAGES,
|
||||
);
|
||||
const players = useAppSelector((state) =>
|
||||
gameId != null ? GameSelectors.getPlayers(state, gameId) : undefined,
|
||||
);
|
||||
const secondsElapsed = useAppSelector((state) =>
|
||||
gameId != null ? GameSelectors.getSecondsElapsed(state, gameId) : 0,
|
||||
);
|
||||
|
||||
// Local 1Hz ticker, resynced from Redux whenever a server event delivers a
|
||||
// fresh `secondsElapsed`. Mirrors desktop's QTimer(1000) +
|
||||
// setGameTime(event.seconds_elapsed()) pattern in game_state.cpp.
|
||||
const [displaySeconds, setDisplaySeconds] = useState(secondsElapsed);
|
||||
|
||||
useEffect(() => {
|
||||
setDisplaySeconds(secondsElapsed);
|
||||
}, [secondsElapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (gameId == null) {
|
||||
return undefined;
|
||||
}
|
||||
const id = window.setInterval(() => {
|
||||
setDisplaySeconds((prev) => prev + 1);
|
||||
}, 1000);
|
||||
return () => window.clearInterval(id);
|
||||
}, [gameId]);
|
||||
|
||||
const [draft, setDraft] = useState('');
|
||||
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
// Desktop pins the log to the bottom unless the user has scrolled up to read backlog.
|
||||
// Capture pin state before the new line renders so auto-scroll only fires when the
|
||||
// user was already following the tail.
|
||||
const wasPinnedRef = useRef(true);
|
||||
useEffect(() => {
|
||||
const el = listRef.current;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (wasPinnedRef.current) {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, [messages.length]);
|
||||
|
||||
const handleMessagesScroll = () => {
|
||||
const el = listRef.current;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
wasPinnedRef.current = el.scrollTop + el.clientHeight >= el.scrollHeight - 2;
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (gameId == null) {
|
||||
return;
|
||||
}
|
||||
const trimmed = draft.trim();
|
||||
if (trimmed.length === 0) {
|
||||
return;
|
||||
}
|
||||
webClient.request.game.gameSay(gameId, { message: trimmed });
|
||||
setDraft('');
|
||||
};
|
||||
const {
|
||||
messages,
|
||||
players,
|
||||
displaySeconds,
|
||||
draft,
|
||||
setDraft,
|
||||
handleMessagesScroll,
|
||||
handleSubmit,
|
||||
} = useGameLog({ gameId, listRef });
|
||||
|
||||
return (
|
||||
<div className="game-log" data-testid="game-log">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue