From 1ceaa8185f3190184a4025ea1bfd75136a89fa08 Mon Sep 17 00:00:00 2001 From: titanium_machine <78664175+titaniummachine1@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:05:00 +0200 Subject: [PATCH 1/2] button added --- src/sections/analysis/panelToolbar/index.tsx | 7 ++ .../analysis/panelToolbar/playButton.tsx | 109 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/sections/analysis/panelToolbar/playButton.tsx diff --git a/src/sections/analysis/panelToolbar/index.tsx b/src/sections/analysis/panelToolbar/index.tsx index 827d64ce..e57f04bf 100644 --- a/src/sections/analysis/panelToolbar/index.tsx +++ b/src/sections/analysis/panelToolbar/index.tsx @@ -5,6 +5,7 @@ import { boardAtom, gameAtom } from "../states"; import { useChessActions } from "@/hooks/useChessActions"; import FlipBoardButton from "./flipBoardButton"; import NextMoveButton from "./nextMoveButton"; +import PlayButton from "./playButton"; import GoToLastPositionButton from "./goToLastPositionButton"; import SaveButton from "./saveButton"; import { useEffect } from "react"; @@ -24,6 +25,10 @@ export default function PanelToolBar() { undoBoardMove(); } else if (e.key === "ArrowDown") { resetBoard(); + } else if (e.key === " " || e.key === "Spacebar") { + // Space bar will be handled by PlayButton component + // We prevent default here to avoid page scrolling + e.preventDefault(); } }; @@ -62,6 +67,8 @@ export default function PanelToolBar() { + + diff --git a/src/sections/analysis/panelToolbar/playButton.tsx b/src/sections/analysis/panelToolbar/playButton.tsx new file mode 100644 index 00000000..ebf8a0b3 --- /dev/null +++ b/src/sections/analysis/panelToolbar/playButton.tsx @@ -0,0 +1,109 @@ +import { Icon } from "@iconify/react"; +import { Grid2 as Grid, IconButton, Tooltip } from "@mui/material"; +import { useAtomValue } from "jotai"; +import { boardAtom, gameAtom } from "../states"; +import { useChessActions } from "@/hooks/useChessActions"; +import { useCallback, useEffect, useRef, useState } from "react"; + +const PLAY_SPEED = 1000; // 1 second between moves + +export default function PlayButton() { + const { playMove: playBoardMove } = useChessActions(boardAtom); + const game = useAtomValue(gameAtom); + const board = useAtomValue(boardAtom); + + const [isPlaying, setIsPlaying] = useState(false); + const intervalRef = useRef(null); + + const gameHistory = game.history(); + const boardHistory = board.history(); + + const isButtonEnabled = + boardHistory.length < gameHistory.length && + gameHistory.slice(0, boardHistory.length).join() === boardHistory.join(); + + const playNextMove = useCallback(() => { + if (!isButtonEnabled) { + setIsPlaying(false); + return; + } + + const nextMoveIndex = boardHistory.length; + const nextMove = game.history({ verbose: true })[nextMoveIndex]; + const comment = game + .getComments() + .find((c) => c.fen === nextMove.after)?.comment; + + if (nextMove) { + playBoardMove({ + from: nextMove.from, + to: nextMove.to, + promotion: nextMove.promotion, + comment, + }); + } + }, [isButtonEnabled, boardHistory, game, playBoardMove]); + + const togglePlay = useCallback(() => { + if (isPlaying) { + setIsPlaying(false); + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + } else { + setIsPlaying(true); + intervalRef.current = setInterval(playNextMove, PLAY_SPEED); + } + }, [isPlaying, playNextMove]); + + // Cleanup interval on unmount or when playing stops + useEffect(() => { + if (!isPlaying && intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [isPlaying]); + + // Spacebar shortcut + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === " " || e.key === "Spacebar") { + e.preventDefault(); + if (isButtonEnabled || isPlaying) { + togglePlay(); + } + } + }; + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [togglePlay, isButtonEnabled, isPlaying]); + + return ( + + + + + + + + ); +} \ No newline at end of file From f574a8b9f1cf2b63007b89f1767c7c5129616fb9 Mon Sep 17 00:00:00 2001 From: titanium_machine <78664175+titaniummachine1@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:29:25 +0200 Subject: [PATCH 2/2] src: added working play button --- src/lib/engine/shared.ts | 8 ++- src/lib/engine/worker.ts | 5 ++ src/sections/analysis/panelToolbar/index.tsx | 5 +- .../analysis/panelToolbar/playButton.tsx | 62 +++++++++++-------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/lib/engine/shared.ts b/src/lib/engine/shared.ts index 328366a6..8b2ddc31 100644 --- a/src/lib/engine/shared.ts +++ b/src/lib/engine/shared.ts @@ -18,10 +18,14 @@ export const isMultiThreadSupported = () => { } }; -export const isIosDevice = () => /iPhone|iPad|iPod/i.test(navigator.userAgent); +export const isIosDevice = () => + typeof navigator !== "undefined" && + /iPhone|iPad|iPod/i.test(navigator.userAgent); export const isMobileDevice = () => - isIosDevice() || /Android|Opera Mini/i.test(navigator.userAgent); + isIosDevice() || + (typeof navigator !== "undefined" && + /Android|Opera Mini/i.test(navigator.userAgent)); export const isEngineSupported = (name: EngineName): boolean => { switch (name) { diff --git a/src/lib/engine/worker.ts b/src/lib/engine/worker.ts index 224e0d9c..4b98c61a 100644 --- a/src/lib/engine/worker.ts +++ b/src/lib/engine/worker.ts @@ -45,6 +45,11 @@ export const sendCommandsToWorker = ( }; export const getRecommendedWorkersNb = (): number => { + // Return default value during SSR + if (typeof navigator === "undefined") { + return 4; + } + const maxWorkersNbFromThreads = Math.max( 1, Math.round(navigator.hardwareConcurrency - 4), diff --git a/src/sections/analysis/panelToolbar/index.tsx b/src/sections/analysis/panelToolbar/index.tsx index e57f04bf..69156ac7 100644 --- a/src/sections/analysis/panelToolbar/index.tsx +++ b/src/sections/analysis/panelToolbar/index.tsx @@ -20,10 +20,9 @@ export default function PanelToolBar() { useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { - if (boardHistory.length === 0) return; - if (e.key === "ArrowLeft") { + if (e.key === "ArrowLeft" && boardHistory.length > 0) { undoBoardMove(); - } else if (e.key === "ArrowDown") { + } else if (e.key === "ArrowDown" && boardHistory.length > 0) { resetBoard(); } else if (e.key === " " || e.key === "Spacebar") { // Space bar will be handled by PlayButton component diff --git a/src/sections/analysis/panelToolbar/playButton.tsx b/src/sections/analysis/panelToolbar/playButton.tsx index ebf8a0b3..5f20c4af 100644 --- a/src/sections/analysis/panelToolbar/playButton.tsx +++ b/src/sections/analysis/panelToolbar/playButton.tsx @@ -11,13 +11,13 @@ export default function PlayButton() { const { playMove: playBoardMove } = useChessActions(boardAtom); const game = useAtomValue(gameAtom); const board = useAtomValue(boardAtom); - + const [isPlaying, setIsPlaying] = useState(false); const intervalRef = useRef(null); - + const gameHistory = game.history(); const boardHistory = board.history(); - + const isButtonEnabled = boardHistory.length < gameHistory.length && gameHistory.slice(0, boardHistory.length).join() === boardHistory.join(); @@ -30,46 +30,56 @@ export default function PlayButton() { const nextMoveIndex = boardHistory.length; const nextMove = game.history({ verbose: true })[nextMoveIndex]; - const comment = game - .getComments() - .find((c) => c.fen === nextMove.after)?.comment; if (nextMove) { + const comment = game + .getComments() + .find((c) => c.fen === nextMove.after)?.comment; + playBoardMove({ from: nextMove.from, to: nextMove.to, promotion: nextMove.promotion, comment, }); + } else { + setIsPlaying(false); } - }, [isButtonEnabled, boardHistory, game, playBoardMove]); + }, [isButtonEnabled, boardHistory.length, gameHistory, game, playBoardMove]); const togglePlay = useCallback(() => { if (isPlaying) { setIsPlaying(false); - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } } else { setIsPlaying(true); - intervalRef.current = setInterval(playNextMove, PLAY_SPEED); } - }, [isPlaying, playNextMove]); + }, [isPlaying]); - // Cleanup interval on unmount or when playing stops + // Handle interval management useEffect(() => { - if (!isPlaying && intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; + if (isPlaying) { + intervalRef.current = setInterval(playNextMove, PLAY_SPEED); + } else { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } } - + return () => { if (intervalRef.current) { clearInterval(intervalRef.current); + intervalRef.current = null; } }; - }, [isPlaying]); + }, [isPlaying, playNextMove]); + + // Stop playing when no more moves available + useEffect(() => { + if (isPlaying && !isButtonEnabled) { + setIsPlaying(false); + } + }, [isPlaying, isButtonEnabled]); // Spacebar shortcut useEffect(() => { @@ -92,18 +102,18 @@ export default function PlayButton() { - ); -} \ No newline at end of file +}