From 38abf30eccae9363dddc9f591b75a500c0158617 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 1 Jun 2026 11:22:56 +0200 Subject: [PATCH] set and clear game state using reducers - removed the need for global state management --- src/components/LichessPgnViewerComponent.tsx | 31 -------- src/components/PgnViewer.tsx | 32 +++++++++ src/pages/Chessboard.container.tsx | 76 +++++++++++++++++++- src/pages/Chessboard.tsx | 57 +++++++++++++-- src/reducers/gameReducer.ts | 38 ++++++++++ 5 files changed, 198 insertions(+), 36 deletions(-) delete mode 100644 src/components/LichessPgnViewerComponent.tsx create mode 100644 src/components/PgnViewer.tsx create mode 100644 src/reducers/gameReducer.ts diff --git a/src/components/LichessPgnViewerComponent.tsx b/src/components/LichessPgnViewerComponent.tsx deleted file mode 100644 index aa29bf9..0000000 --- a/src/components/LichessPgnViewerComponent.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import LichessPgnViewer from "lichess-pgn-viewer"; -import type PgnViewer from "lichess-pgn-viewer/pgnViewer"; -import { useEffect, useRef } from "react"; - -interface IProps { - pgn: string; -} - -const PgnViewerComponent = ({ pgn }: IProps) => { - const containerRef = useRef(null); - const viewerRef = useRef(null); - - useEffect(() => { - if (!containerRef.current) return; - - viewerRef.current = LichessPgnViewer(containerRef.current, { - pgn, - scrollToMove: false, - }); - - return () => { - if (containerRef.current) { - containerRef.current.innerHTML = ""; - } - }; - }, [pgn]); - - return
; -}; - -export default PgnViewerComponent; diff --git a/src/components/PgnViewer.tsx b/src/components/PgnViewer.tsx new file mode 100644 index 0000000..7cf32a4 --- /dev/null +++ b/src/components/PgnViewer.tsx @@ -0,0 +1,32 @@ +import LichessPgnViewer from "lichess-pgn-viewer"; +import type PgnViewerType from "lichess-pgn-viewer/pgnViewer"; +import { useEffect, useRef } from "react"; + +interface IProps { + gamePgn: string; +} + +const PgnViewer = ({ gamePgn }: IProps) => { + const containerRef = useRef(null); + const viewerRef = useRef(null); + + useEffect(() => { + const element: HTMLElement | null = document.querySelector(".lpv-board"); + if (!element) return; + + viewerRef.current = LichessPgnViewer(element, { + pgn: gamePgn, + scrollToMove: false, + }); + + return () => { + if (containerRef.current) { + containerRef.current = null; + } + }; + }, [gamePgn]); + + return
; +}; + +export default PgnViewer; diff --git a/src/pages/Chessboard.container.tsx b/src/pages/Chessboard.container.tsx index 794d059..e8caf00 100644 --- a/src/pages/Chessboard.container.tsx +++ b/src/pages/Chessboard.container.tsx @@ -1,7 +1,81 @@ +import { type ChangeEvent, useReducer, useRef } from "react"; import Chessboard from "#/pages/Chessboard.tsx"; +import { gameReducer, initialGameState } from "#/reducers/gameReducer.ts"; const ChessboardContainer = () => { - return ; + const [gameState, gameDispatch] = useReducer(gameReducer, initialGameState); + const fileInputRef = useRef(null); + + const handleSavePgn = () => { + console.log("Save PGN"); + }; + + const handleSavePdf = () => { + console.log("Save PDF"); + }; + + const handleClearGame = () => { + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + + gameDispatch({ + type: "CLEAR_GAME", + }); + }; + + const handleLoadPgn = (e: ChangeEvent) => { + e.preventDefault(); + + const selectedFile = e.target.files?.[0]; + + if (selectedFile?.name.endsWith(".pgn")) { + const reader = new FileReader(); + reader.onload = (event) => { + const pgnData = event.target?.result; + let pgnString = ""; + + if (pgnData) { + if (typeof pgnData === "string") { + pgnString = pgnData; + } else { + pgnString = new TextDecoder().decode(pgnData); + } + } + + if (pgnString) { + gameDispatch({ + type: "SET_GAME", + payload: { pgn: pgnString, headers: "" }, + }); + } else { + gameDispatch({ + type: "CLEAR_GAME", + }); + } + }; + reader.readAsText(selectedFile); + } else { + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + e.target.files = null; + gameDispatch({ + type: "CLEAR_GAME", + }); + } + }; + + return ( + + ); }; export default ChessboardContainer; diff --git a/src/pages/Chessboard.tsx b/src/pages/Chessboard.tsx index 5d0c484..88eee44 100644 --- a/src/pages/Chessboard.tsx +++ b/src/pages/Chessboard.tsx @@ -1,16 +1,65 @@ +import type { ChangeEvent, RefObject } from "react"; import LichessButton from "#/components/LichessButton.tsx"; -import PgnViewerComponent from "#/components/LichessPgnViewerComponent.tsx"; +import PgnViewer from "#/components/PgnViewer.tsx"; import Section from "#/components/Section.tsx"; +import type { IGameState } from "#/reducers/gameReducer.ts"; -const Chessboard = () => { +interface IProps { + gameState: IGameState; + handleLoadPgn: (e: ChangeEvent) => void; + handleClearGame: () => void; + handleSavePgn: () => void; + handleSavePdf: () => void; + fileInputRef: RefObject; +} + +const Chessboard = ({ + gameState, + handleLoadPgn, + handleClearGame, + handleSavePgn, + handleSavePdf, + fileInputRef, +}: IProps) => { return (
or{" "} - +
- +
+ + + + + +
+
); }; diff --git a/src/reducers/gameReducer.ts b/src/reducers/gameReducer.ts new file mode 100644 index 0000000..38cbe1d --- /dev/null +++ b/src/reducers/gameReducer.ts @@ -0,0 +1,38 @@ +interface IDiagrams { + ply: number; + fen: string; +} + +export interface IGameState { + pgn: string; + headers: string; + diagrams: IDiagrams[]; + diagramClock: boolean; +} + +type GameAction = + | { type: "SET_GAME"; payload: { pgn: string; headers: "" } } + | { type: "CLEAR_GAME" }; + +export const initialGameState = { + pgn: "", + headers: "", + diagrams: [], + diagramClock: false, +}; + +export const gameReducer = (state: IGameState, action: GameAction) => { + switch (action.type) { + case "SET_GAME": + return { + pgn: action.payload.pgn, + headers: "", + diagrams: [], + diagramClock: false, + }; + case "CLEAR_GAME": + return initialGameState; + default: + return state; + } +};