From 5e2d944513b0687fd2ee4c0223f82e14e6447ce7 Mon Sep 17 00:00:00 2001 From: GuillaumeSD Date: Tue, 10 Jun 2025 02:42:43 +0200 Subject: [PATCH] refacto : load game --- src/lib/chessCom.ts | 65 ++++++- src/lib/lichess.ts | 39 +++- src/sections/loadGame/chessComInput.tsx | 109 +---------- src/sections/loadGame/game-item-utils.tsx | 105 ---------- src/sections/loadGame/gameItem/dateChip.tsx | 20 ++ .../loadGame/gameItem/gameResultChip.tsx | 86 +++++++++ .../index.tsx} | 63 ++---- .../loadGame/gameItem/movesNbChip.tsx | 20 ++ .../loadGame/gameItem/timeControlChip.tsx | 20 ++ src/sections/loadGame/gamePgnInput.tsx | 22 +-- src/sections/loadGame/lichess-game-item.tsx | 179 ------------------ src/sections/loadGame/lichessInput.tsx | 52 +---- src/types/chessCom.ts | 34 +--- src/types/game.ts | 13 ++ src/types/lichess.ts | 39 +--- 15 files changed, 310 insertions(+), 556 deletions(-) delete mode 100644 src/sections/loadGame/game-item-utils.tsx create mode 100644 src/sections/loadGame/gameItem/dateChip.tsx create mode 100644 src/sections/loadGame/gameItem/gameResultChip.tsx rename src/sections/loadGame/{chess-com-game-item.tsx => gameItem/index.tsx} (78%) create mode 100644 src/sections/loadGame/gameItem/movesNbChip.tsx create mode 100644 src/sections/loadGame/gameItem/timeControlChip.tsx delete mode 100644 src/sections/loadGame/lichess-game-item.tsx diff --git a/src/lib/chessCom.ts b/src/lib/chessCom.ts index 3485955..755267b 100644 --- a/src/lib/chessCom.ts +++ b/src/lib/chessCom.ts @@ -1,10 +1,12 @@ -import { ChessComRawGameData } from "@/types/chessCom"; +import { ChessComGame } from "@/types/chessCom"; import { getPaddedNumber } from "./helpers"; +import { LoadedGame } from "@/types/game"; +import { Chess } from "chess.js"; export const getChessComUserRecentGames = async ( username: string, signal?: AbortSignal -): Promise => { +): Promise => { const date = new Date(); const year = date.getUTCFullYear(); const month = date.getUTCMonth() + 1; @@ -24,7 +26,7 @@ export const getChessComUserRecentGames = async ( throw new Error("Error fetching games from Chess.com"); } - const games: ChessComRawGameData[] = data?.games ?? []; + const games: ChessComGame[] = data?.games ?? []; if (games.length < 50) { const previousMonth = month === 1 ? 12 : month - 1; @@ -42,7 +44,8 @@ export const getChessComUserRecentGames = async ( const gamesToReturn = games .sort((a, b) => b.end_time - a.end_time) - .slice(0, 50); + .slice(0, 50) + .map(formatChessComGame); return gamesToReturn; }; @@ -58,3 +61,57 @@ export const getChessComUserAvatar = async ( return typeof avatarUrl === "string" ? avatarUrl : null; }; + +const formatChessComGame = (data: ChessComGame): LoadedGame => { + const game = new Chess(); + game.loadPgn(data.pgn); + + return { + id: data.uuid || data.url?.split("/").pop() || data.id, + pgn: data.pgn || "", + white: { + name: data.white?.username || "White", + rating: data.white?.rating || 0, + title: data.white?.title, + }, + black: { + name: data.black?.username || "Black", + rating: data.black?.rating || 0, + title: data.black?.title, + }, + result: game.getHeaders().Result, + timeControl: getGameTimeControl(data), + date: data.end_time + ? new Date(data.end_time * 1000).toLocaleDateString() + : new Date().toLocaleDateString(), + movesNb: game.history().length, + url: data.url, + }; +}; + +const getGameTimeControl = (game: ChessComGame): string | undefined => { + const rawTimeControl = game.time_control; + if (!rawTimeControl) return undefined; + + const [firstPart, secondPart] = rawTimeControl.split("+"); + if (!firstPart) return undefined; + + const timeControl = Number(firstPart); + const increment = secondPart ? `+${secondPart}` : ""; + if (timeControl < 60) return `${timeControl}s${increment}`; + + if (timeControl < 3600) { + const minutes = Math.floor(timeControl / 60); + const seconds = timeControl % 60; + + return seconds + ? `${minutes}m${getPaddedNumber(seconds)}s${increment}` + : `${minutes}m${increment}`; + } + + const hours = Math.floor(timeControl / 3600); + const minutes = Math.floor((timeControl % 3600) / 60); + return minutes + ? `${hours}h${getPaddedNumber(minutes)}m${increment}` + : `${hours}h${increment}`; +}; diff --git a/src/lib/lichess.ts b/src/lib/lichess.ts index f42ba6d..781ce18 100644 --- a/src/lib/lichess.ts +++ b/src/lib/lichess.ts @@ -3,11 +3,12 @@ import { sortLines } from "./engine/helpers/parseResults"; import { LichessError, LichessEvalBody, - LichessRawGameData, + LichessGame, LichessResponse, } from "@/types/lichess"; import { logErrorToSentry } from "./sentry"; import { formatUciPv } from "./chess"; +import { LoadedGame } from "@/types/game"; export const getLichessEval = async ( fen: string, @@ -58,7 +59,7 @@ export const getLichessEval = async ( export const getLichessUserRecentGames = async ( username: string, signal?: AbortSignal -): Promise => { +): Promise => { const res = await fetch( `https://lichess.org/api/games/user/${username}?until=${Date.now()}&max=50&pgnInJson=true&sort=dateDesc&clocks=true`, { method: "GET", headers: { accept: "application/x-ndjson" }, signal } @@ -69,12 +70,12 @@ export const getLichessUserRecentGames = async ( } const rawData = await res.text(); - const games: LichessRawGameData[] = rawData + const games: LichessGame[] = rawData .split("\n") .filter((game) => game.length > 0) .map((game) => JSON.parse(game)); - return games; + return games.map(formatLichessGame); }; const fetchLichessEval = async ( @@ -94,3 +95,33 @@ const fetchLichessEval = async ( return { error: LichessError.NotFound }; } }; + +const formatLichessGame = (data: LichessGame): LoadedGame => { + return { + id: data.id, + pgn: data.pgn || "", + white: { + name: data.players.white.user?.name || "White", + rating: data.players.white.rating, + title: data.players.white.user?.title, + }, + black: { + name: data.players.black.user?.name || "Black", + rating: data.players.black.rating, + title: data.players.black.user?.title, + }, + result: getGameResult(data), + timeControl: `${Math.floor(data.clock?.initial / 60 || 0)}+${data.clock?.increment || 0}`, + date: new Date(data.createdAt || data.lastMoveAt).toLocaleDateString(), + movesNb: data.moves?.split(" ").length || 0, + url: `https://lichess.org/${data.id}`, + }; +}; + +const getGameResult = (data: LichessGame): string => { + if (data.status === "draw") return "1/2-1/2"; + + if (data.winner) return data.winner === "white" ? "1-0" : "0-1"; + + return "*"; +}; diff --git a/src/sections/loadGame/chessComInput.tsx b/src/sections/loadGame/chessComInput.tsx index 2300dbc..e2e25b7 100644 --- a/src/sections/loadGame/chessComInput.tsx +++ b/src/sections/loadGame/chessComInput.tsx @@ -11,103 +11,13 @@ import { import { Icon } from "@iconify/react"; import { useDebounce } from "@/hooks/useDebounce"; import { useQuery } from "@tanstack/react-query"; -import { ChessComGameItem } from "./chess-com-game-item"; -import { ChessComRawGameData, NormalizedGameData } from "@/types/chessCom"; import { useMemo, useState } from "react"; +import { GameItem } from "./gameItem"; interface Props { onSelect: (pgn: string, boardOrientation?: boolean) => void; } -// Helper function to normalize Chess.com data -const normalizeChessComData = ( - data: ChessComRawGameData -): NormalizedGameData => { - const timeControl = data.time_control + "s" || "unknown"; - - // Todo Convert from seconds to minutes to time + increment seconds - - // Determine result from multiple sources - let gameResult = "*"; // default to ongoing - - if (data.result) { - gameResult = data.result; - } else if (data.white?.result && data.black?.result) { - if (data.white.result === "win") { - gameResult = "1-0"; - } else if (data.black.result === "win") { - gameResult = "0-1"; - } else if ( - (data.white.result === "stalemate" && - data.black.result === "stalemate") || - (data.white.result === "repetition" && - data.black.result === "repetition") || - (data.white.result === "insufficient" && - data.black.result === "insufficient") || - (data.white.result === "50move" && data.black.result === "50move") || - (data.white.result === "agreed" && data.black.result === "agreed") - ) { - gameResult = "1/2-1/2"; - } - } - - //* Function to count moves from PGN. Generated from claude..... :) - const countMovesFromPGN = (pgn: string) => { - if (!pgn) return 0; - - // Split PGN into lines and find the moves section (after headers) - const lines = pgn.split("\n"); - let movesSection = ""; - let inMoves = false; - - for (const line of lines) { - if (line.trim() === "" && !inMoves) { - inMoves = true; - continue; - } - if (inMoves) { - movesSection += line + " "; - } - } - - // Remove comments in curly braces and square brackets - movesSection = movesSection - .replace(/\{[^}]*\}/g, "") - .replace(/\[[^\]]*\]/g, ""); - - // Remove result indicators - movesSection = movesSection.replace(/1-0|0-1|1\/2-1\/2|\*/g, ""); - - // Split by move numbers and count them - // Match pattern like "1." "58." etc. - const moveNumbers = movesSection.match(/\d+\./g); - - return moveNumbers ? moveNumbers.length : 0; - }; - - return { - id: data.uuid || data.url?.split("/").pop() || data.id, - white: { - username: data.white?.username || "White", - rating: data.white?.rating || 0, - title: data.white?.title, - }, - black: { - username: data.black?.username || "Black", - rating: data.black?.rating || 0, - title: data.black?.title, - }, - result: gameResult, - timeControl: timeControl, - date: data.end_time - ? new Date(data.end_time * 1000).toLocaleDateString() - : new Date().toLocaleDateString(), - opening: data.opening?.name || data.eco, - moves: data.pgn ? countMovesFromPGN(data.pgn) : 0, - url: data.url, - }; -}; - export default function ChessComInput({ onSelect }: Props) { const [rawStoredValue, setStoredValues] = useLocalStorage( "chesscom-username", @@ -239,24 +149,23 @@ export default function ChessComInput({ onSelect }: Props) { ) : ( {games.map((game) => { - const normalizedGame = normalizeChessComData(game); const perspectiveUserColor = - normalizedGame.white.username.toLowerCase() === - chessComUsername.toLowerCase() + game.white.name.toLowerCase() === + debouncedUsername.toLowerCase() ? "white" : "black"; return ( - { updateHistory(debouncedUsername); const boardOrientation = - debouncedUsername.toLowerCase() !== - game.black?.username?.toLowerCase(); - onSelect(game.pgn, boardOrientation); + debouncedUsername.toLowerCase() !== + game.black?.name?.toLowerCase(); + onSelect(game.pgn, boardOrientation); }} /> ); diff --git a/src/sections/loadGame/game-item-utils.tsx b/src/sections/loadGame/game-item-utils.tsx deleted file mode 100644 index f3b2235..0000000 --- a/src/sections/loadGame/game-item-utils.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Icon } from "@iconify/react"; -import { Chip, Tooltip, useTheme } from "@mui/material"; -import React from "react"; - -export const GameResult: React.FC<{ - result: string; - perspectiveUserColor: "white" | "black"; -}> = ({ result, perspectiveUserColor }) => { - const theme = useTheme(); - - let color = theme.palette.text.secondary; // Neutral gray for ongoing - let bgColor = theme.palette.action.hover; - let icon = ; - let label = "Game in Progress"; - - if (result === "1-0") { - // White wins - if (perspectiveUserColor === "white") { - color = theme.palette.success.main; // Success green - bgColor = `${theme.palette.success.main}1A`; // 10% opacity - icon = ; - } else { - // perspectiveUserColor is black - color = theme.palette.error.main; // Confident red - bgColor = `${theme.palette.error.main}1A`; // 10% opacity - icon = ; // A suitable icon for loss - } - label = "White Wins"; - } else if (result === "0-1") { - // Black wins - if (perspectiveUserColor === "black") { - color = theme.palette.success.main; // Success green - bgColor = `${theme.palette.success.main}1A`; // 10% opacity - icon = ; - } else { - // perspectiveUserColor is white - color = theme.palette.error.main; // Confident red - bgColor = `${theme.palette.error.main}1A`; // 10% opacity - icon = ; // A suitable icon for loss - } - label = "Black Wins"; - } else if (result === "1/2-1/2") { - color = theme.palette.info.main; // Balanced blue (using info for a neutral, distinct color) - bgColor = `${theme.palette.info.main}1A`; // 10% opacity - icon = ; - label = "Draw"; - } - return ( - - - - ); -}; - -export const TimeControlChip: React.FC<{ timeControl: string }> = ({ - timeControl, -}) => { - return ( - - } - label={timeControl} - size="small" - /> - - ); -}; - -export const MovesChip: React.FC<{ moves: number }> = ({ moves }) => { - return ( - - } - label={`${Math.round(moves / 2)} moves`} - size="small" - /> - - ); -}; - -export const DateChip: React.FC<{ date: string }> = ({ date }) => { - return ( - - } - label={date} - size="small" - /> - - ); -}; diff --git a/src/sections/loadGame/gameItem/dateChip.tsx b/src/sections/loadGame/gameItem/dateChip.tsx new file mode 100644 index 0000000..4e54f12 --- /dev/null +++ b/src/sections/loadGame/gameItem/dateChip.tsx @@ -0,0 +1,20 @@ +import { Icon } from "@iconify/react"; +import { Chip, Tooltip } from "@mui/material"; + +interface Props { + date?: string; +} + +export default function DateChip({ date }: Props) { + if (!date) return null; + + return ( + + } + label={date} + size="small" + /> + + ); +} diff --git a/src/sections/loadGame/gameItem/gameResultChip.tsx b/src/sections/loadGame/gameItem/gameResultChip.tsx new file mode 100644 index 0000000..a9da152 --- /dev/null +++ b/src/sections/loadGame/gameItem/gameResultChip.tsx @@ -0,0 +1,86 @@ +import { Icon } from "@iconify/react"; +import { Chip, Theme, Tooltip, useTheme } from "@mui/material"; +import React from "react"; + +interface Props { + result?: string; + perspectiveUserColor: "white" | "black"; +} + +export default function GameResultChip({ + result, + perspectiveUserColor, +}: Props) { + const theme = useTheme(); + + const { label, color, bgColor, icon } = getResultSpecs( + theme, + perspectiveUserColor, + result + ); + + return ( + + + + ); +} + +const getResultSpecs = ( + theme: Theme, + perspectiveUserColor: "white" | "black", + result?: string +) => { + if ( + (result === "1-0" && perspectiveUserColor === "white") || + (result === "0-1" && perspectiveUserColor === "black") + ) { + return { + label: result === "1-0" ? "White won" : "Black won", + color: theme.palette.success.main, + bgColor: `${theme.palette.success.main}1A`, + icon: , + }; + } + + if ( + (result === "1-0" && perspectiveUserColor === "black") || + (result === "0-1" && perspectiveUserColor === "white") + ) { + return { + label: result === "1-0" ? "White won" : "Black won", + color: theme.palette.error.main, + bgColor: `${theme.palette.error.main}1A`, + }; + } + + if (result === "1/2-1/2") { + return { + label: "Draw", + color: theme.palette.info.main, + bgColor: `${theme.palette.info.main}1A`, + icon: , + }; + } + + return { + label: "Game in Progress", + color: theme.palette.text.secondary, + bgColor: theme.palette.action.hover, + icon: , + }; +}; diff --git a/src/sections/loadGame/chess-com-game-item.tsx b/src/sections/loadGame/gameItem/index.tsx similarity index 78% rename from src/sections/loadGame/chess-com-game-item.tsx rename to src/sections/loadGame/gameItem/index.tsx index ae17918..477844c 100644 --- a/src/sections/loadGame/chess-com-game-item.tsx +++ b/src/sections/loadGame/gameItem/index.tsx @@ -5,55 +5,26 @@ import { Typography, Box, useTheme, - IconButton, - Tooltip, } from "@mui/material"; -import { Icon } from "@iconify/react"; -import { - DateChip, - GameResult, - MovesChip, - TimeControlChip, -} from "./game-item-utils"; +import { LoadedGame } from "@/types/game"; +import TimeControlChip from "./timeControlChip"; +import MovesNbChip from "./movesNbChip"; +import DateChip from "./dateChip"; +import GameResultChip from "./gameResultChip"; -type ChessComPlayer = { - username: string; - rating: number; - title?: string; -}; - -type ChessComGameProps = { - id: string; - white: ChessComPlayer; - black: ChessComPlayer; - result: string; - timeControl: string; - date: string; - opening?: string; - moves?: number; - url: string; +interface Props { + game: LoadedGame; onClick?: () => void; perspectiveUserColor: "white" | "black"; -}; +} -export const ChessComGameItem: React.FC = ({ - white, - black, - result, - timeControl, - date, - moves, - url, +export const GameItem: React.FC = ({ + game, onClick, perspectiveUserColor, }) => { const theme = useTheme(); - - const formatPlayerName = (player: ChessComPlayer) => { - return player.title - ? `${player.title} ${player.username}` - : player.username; - }; + const { white, black, result, timeControl, date, movesNb } = game; const whiteWon = result === "1-0"; const blackWon = result === "0-1"; @@ -122,7 +93,7 @@ export const ChessComGameItem: React.FC = ({ {formatPlayerName(black)} ({black.rating}) - @@ -138,14 +109,14 @@ export const ChessComGameItem: React.FC = ({ }} > - {moves && moves > 0 && } + } sx={{ mr: 2 }} /> - + {/* { @@ -165,7 +136,11 @@ export const ChessComGameItem: React.FC = ({ - + */} ); }; + +const formatPlayerName = (player: LoadedGame["white"]) => { + return player.title ? `${player.title} ${player.name}` : player.name; +}; diff --git a/src/sections/loadGame/gameItem/movesNbChip.tsx b/src/sections/loadGame/gameItem/movesNbChip.tsx new file mode 100644 index 0000000..2e6cbdb --- /dev/null +++ b/src/sections/loadGame/gameItem/movesNbChip.tsx @@ -0,0 +1,20 @@ +import { Icon } from "@iconify/react"; +import { Chip, Tooltip } from "@mui/material"; + +interface Props { + movesNb?: number; +} + +export default function MovesNbChip({ movesNb }: Props) { + if (!movesNb) return null; + + return ( + + } + label={`${Math.ceil(movesNb / 2)} moves`} + size="small" + /> + + ); +} diff --git a/src/sections/loadGame/gameItem/timeControlChip.tsx b/src/sections/loadGame/gameItem/timeControlChip.tsx new file mode 100644 index 0000000..09dc8eb --- /dev/null +++ b/src/sections/loadGame/gameItem/timeControlChip.tsx @@ -0,0 +1,20 @@ +import { Icon } from "@iconify/react"; +import { Chip, Tooltip } from "@mui/material"; + +interface Props { + timeControl?: string; +} + +export default function TimeControlChip({ timeControl }: Props) { + if (!timeControl) return null; + + return ( + + } + label={timeControl} + size="small" + /> + + ); +} diff --git a/src/sections/loadGame/gamePgnInput.tsx b/src/sections/loadGame/gamePgnInput.tsx index 253eb5b..7060bbd 100644 --- a/src/sections/loadGame/gamePgnInput.tsx +++ b/src/sections/loadGame/gamePgnInput.tsx @@ -10,16 +10,15 @@ interface Props { export default function GamePgnInput({ pgn, setPgn }: Props) { const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - const reader = new FileReader(); + if (!file) return; + const reader = new FileReader(); - reader.onload = (e) => { - const fileContent = e.target?.result as string; - setPgn(fileContent); - }; + reader.onload = (e) => { + const fileContent = e.target?.result as string; + setPgn(fileContent); + }; - reader.readAsText(file); // Read the file as text - } + reader.readAsText(file); }; return ( @@ -39,12 +38,7 @@ export default function GamePgnInput({ pgn, setPgn }: Props) { startIcon={} > Choose PGN File - + ); diff --git a/src/sections/loadGame/lichess-game-item.tsx b/src/sections/loadGame/lichess-game-item.tsx deleted file mode 100644 index 072d033..0000000 --- a/src/sections/loadGame/lichess-game-item.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import type React from "react"; -import { - ListItem, - ListItemText, - Typography, - Chip, - Box, - useTheme, - IconButton, - Tooltip, -} from "@mui/material"; -import { Icon } from "@iconify/react"; -import { - DateChip, - GameResult, - MovesChip, - TimeControlChip, -} from "./game-item-utils"; - -type LichessPlayer = { - username: string; - rating: number; - title?: string; -}; - -type LichessGameProps = { - id: string; - white: LichessPlayer; - black: LichessPlayer; - result: string; - timeControl: string; - date: string; - opening?: string; - moves?: number; - url: string; - onClick?: () => void; - perspectiveUserColor: "white" | "black"; -}; - -export const LichessGameItem: React.FC = ({ - white, - black, - result, - timeControl, - date, - perspectiveUserColor, - moves, - url, - onClick, -}) => { - const theme = useTheme(); - - // If it is a titled played append the title to the start of the name - const formatPlayerName = (player: LichessPlayer) => { - return player.title - ? `${player.title} ${player.username}` - : player.username; - }; - - const whiteWon = result === "1-0"; - const blackWon = result === "0-1"; - - return ( - - - - {formatPlayerName(white)} ({white.rating}) - - - - vs - - - - {formatPlayerName(black)} ({black.rating}) - - - - - } - secondary={ - - - {moves && moves > 0 && } - - - } - label="Lichess" - size="small" - /> - - } - sx={{ mr: 2 }} - /> - - - - { - e.stopPropagation(); - window.open(url, "_blank"); - }} - size="small" - sx={{ - color: theme.palette.primary.main, - "&:hover": { - backgroundColor: theme.palette.action.hover, - transform: "scale(1.1)", - }, - transition: "all 0.2s ease-in-out", - }} - > - - - - - - ); -}; diff --git a/src/sections/loadGame/lichessInput.tsx b/src/sections/loadGame/lichessInput.tsx index 512a174..778f9de 100644 --- a/src/sections/loadGame/lichessInput.tsx +++ b/src/sections/loadGame/lichessInput.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useLocalStorage } from "@/hooks/useLocalStorage"; import { getLichessUserRecentGames } from "@/lib/lichess"; import { @@ -13,44 +11,13 @@ import { import { Icon } from "@iconify/react"; import { useDebounce } from "@/hooks/useDebounce"; import { useQuery } from "@tanstack/react-query"; -import { LichessGameItem } from "./lichess-game-item"; -import { LichessRawGameData, NormalizedLichessGameData } from "@/types/lichess"; import { useMemo, useState } from "react"; +import { GameItem } from "./gameItem"; interface Props { onSelect: (pgn: string, boardOrientation?: boolean) => void; } -// Helper function to normalize Lichess data -const normalizeLichessData = ( - data: LichessRawGameData -): NormalizedLichessGameData => ({ - id: data.id, - white: { - username: data.players.white.user?.name || "Anonymous", - rating: data.players.white.rating, - title: data.players.white.user?.title, - }, - black: { - username: data.players.black.user?.name || "Anonymous", - rating: data.players.black.rating, - title: data.players.black.user?.title, - }, - result: - data.status === "draw" - ? "1/2-1/2" - : data.winner - ? data.winner === "white" - ? "1-0" - : "0-1" - : "*", - timeControl: `${Math.floor(data.clock?.initial / 60 || 0)}+${data.clock?.increment || 0}`, - date: new Date(data.createdAt || data.lastMoveAt).toLocaleDateString(), - opening: data.opening?.name, - moves: data.moves?.split(" ").length || 0, - url: `https://lichess.org/${data.id}`, -}); - export default function LichessInput({ onSelect }: Props) { const [rawStoredValue, setStoredValues] = useLocalStorage( "lichess-username", @@ -181,24 +148,23 @@ export default function LichessInput({ onSelect }: Props) { ) : ( {games.map((game) => { - const normalizedGame = normalizeLichessData(game); const perspectiveUserColor = - normalizedGame.white.username.toLowerCase() === - lichessUsername.toLowerCase() + game.white.name.toLowerCase() === + debouncedUsername.toLowerCase() ? "white" : "black"; return ( - { + const boardOrientation = + debouncedUsername.toLowerCase() !== + game.black.name.toLowerCase(); + onSelect(game.pgn, boardOrientation); updateHistory(debouncedUsername); - const boardOrientation = - debouncedUsername.toLowerCase() !== - game.players?.black?.user?.name?.toLowerCase(); - onSelect(game.pgn, boardOrientation); }} /> ); diff --git a/src/types/chessCom.ts b/src/types/chessCom.ts index d5549eb..5186d8a 100644 --- a/src/types/chessCom.ts +++ b/src/types/chessCom.ts @@ -1,46 +1,20 @@ -interface ChessComPlayerData { +interface ChessComPlayer { username: string; rating: number; result?: string; title?: string; } -interface ChessComOpeningData { - name: string; - eco?: string; -} - -export interface ChessComRawGameData { +export interface ChessComGame { uuid: string; id: string; url: string; pgn: string; - white: ChessComPlayerData; - black: ChessComPlayerData; + white: ChessComPlayer; + black: ChessComPlayer; result: string; time_control: string; end_time: number; - opening?: ChessComOpeningData; eco?: string; termination?: string; } - -export interface NormalizedGameData { - id: string; - white: { - username: string; - rating: number; - title?: string; - }; - black: { - username: string; - rating: number; - title?: string; - }; - result: string; - timeControl: string; - date: string; - opening?: string; - moves: number; - url: string; -} diff --git a/src/types/game.ts b/src/types/game.ts index 6c32bc6..d6ae587 100644 --- a/src/types/game.ts +++ b/src/types/game.ts @@ -19,4 +19,17 @@ export interface Player { name: string; rating?: number; avatarUrl?: string; + title?: string; +} + +export interface LoadedGame { + id: string; + pgn: string; + date?: string; + white: Player; + black: Player; + result?: string; + timeControl?: string; + movesNb?: number; + url?: string; } diff --git a/src/types/lichess.ts b/src/types/lichess.ts index d7d8f59..a77afa6 100644 --- a/src/types/lichess.ts +++ b/src/types/lichess.ts @@ -17,7 +17,7 @@ export enum LichessError { NotFound = "No cloud evaluation available for that position", } -interface LichessPlayerData { +interface LichessPlayer { user: { name: string; title?: string; @@ -25,51 +25,24 @@ interface LichessPlayerData { rating: number; } -interface LichessClockData { +interface LichessClock { initial: number; increment: number; totalTime: number; } -interface LichessOpeningData { - eco: string; - name: string; - ply: number; -} - -export interface LichessRawGameData { +export interface LichessGame { id: string; createdAt: number; lastMoveAt: number; status: string; players: { - white: LichessPlayerData; - black: LichessPlayerData; + white: LichessPlayer; + black: LichessPlayer; }; winner?: "white" | "black"; - opening?: LichessOpeningData; moves: string; pgn: string; - clock: LichessClockData; + clock: LichessClock; url?: string; } - -export interface NormalizedLichessGameData { - id: string; - white: { - username: string; - rating: number; - title?: string; - }; - black: { - username: string; - rating: number; - title?: string; - }; - result: string; - timeControl: string; - date: string; - opening?: string; - moves: number; - url: string; -}