feat : add chessCom games import
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
import { Checkbox, FormControlLabel, Grid } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import {
|
||||
showBestMoveArrowAtom,
|
||||
showPlayerMoveArrowAtom,
|
||||
} from "../analysis/states";
|
||||
import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
|
||||
|
||||
export default function ArrowOptions() {
|
||||
const [showBestMove, setShowBestMove] = useAtom(showBestMoveArrowAtom);
|
||||
const [showPlayerMove, setShowPlayerMove] = useAtom(showPlayerMoveArrowAtom);
|
||||
const [showBestMove, setShowBestMove] = useAtomLocalStorage(
|
||||
"show-arrow-best-move",
|
||||
showBestMoveArrowAtom
|
||||
);
|
||||
const [showPlayerMove, setShowPlayerMove] = useAtomLocalStorage(
|
||||
"show-arrow-player-move",
|
||||
showPlayerMoveArrowAtom
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { engineDepthAtom, engineMultiPvAtom } from "../analysis/states";
|
||||
import ArrowOptions from "./arrowOptions";
|
||||
import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -23,6 +24,15 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function EngineSettingsDialog({ open, onClose }: Props) {
|
||||
const [depth, setDepth] = useAtomLocalStorage(
|
||||
"engine-depth",
|
||||
engineDepthAtom
|
||||
);
|
||||
const [multiPv, setMultiPv] = useAtomLocalStorage(
|
||||
"engine-multi-pv",
|
||||
engineMultiPvAtom
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||
<DialogTitle marginY={1} variant="h5">
|
||||
@@ -65,7 +75,8 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
||||
|
||||
<Slider
|
||||
label="Maximum depth"
|
||||
atom={engineDepthAtom}
|
||||
value={depth}
|
||||
setValue={setDepth}
|
||||
min={10}
|
||||
max={30}
|
||||
marksFilter={2}
|
||||
@@ -73,7 +84,8 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
||||
|
||||
<Slider
|
||||
label="Number of lines"
|
||||
atom={engineMultiPvAtom}
|
||||
value={multiPv}
|
||||
setValue={setMultiPv}
|
||||
min={1}
|
||||
max={6}
|
||||
xs={6}
|
||||
|
||||
93
src/sections/loadGame/chessComInput.tsx
Normal file
93
src/sections/loadGame/chessComInput.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
import { getUserRecentGames } from "@/lib/chessCom";
|
||||
import { capitalize } from "@/lib/helpers";
|
||||
import { ChessComGame } from "@/types/chessCom";
|
||||
import {
|
||||
FormControl,
|
||||
Grid,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
pgn: string;
|
||||
setPgn: (pgn: string) => void;
|
||||
}
|
||||
|
||||
export default function ChessComInput({ pgn, setPgn }: Props) {
|
||||
const [requestCount, setRequestCount] = useState(0);
|
||||
const [chessComUsername, setChessComUsername] = useLocalStorage(
|
||||
"chesscom-username",
|
||||
""
|
||||
);
|
||||
const [games, setGames] = useState<ChessComGame[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chessComUsername) {
|
||||
setGames([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(
|
||||
async () => {
|
||||
const games = await getUserRecentGames(chessComUsername);
|
||||
setGames(games);
|
||||
},
|
||||
requestCount === 0 ? 0 : 500
|
||||
);
|
||||
|
||||
setRequestCount((prev) => prev + 1);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [chessComUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControl sx={{ m: 1, width: 300 }}>
|
||||
<TextField
|
||||
label="Enter your Chess.com username..."
|
||||
variant="outlined"
|
||||
value={chessComUsername ?? ""}
|
||||
onChange={(e) => setChessComUsername(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
xs={12}
|
||||
gap={2}
|
||||
justifyContent="center"
|
||||
alignContent="center"
|
||||
>
|
||||
{games.map((game) => (
|
||||
<ListItemButton
|
||||
onClick={() => setPgn(game.pgn)}
|
||||
selected={pgn === game.pgn}
|
||||
style={{ width: 350, maxWidth: 350 }}
|
||||
key={game.uuid}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${capitalize(game.white.username) || "White"} (${
|
||||
game.white.rating || "?"
|
||||
}) vs ${capitalize(game.black.username) || "Black"} (${
|
||||
game.black.rating || "?"
|
||||
})`}
|
||||
secondary={`${capitalize(game.time_class)} played at ${new Date(
|
||||
game.end_time * 1000
|
||||
)
|
||||
.toLocaleString()
|
||||
.slice(0, -3)}`}
|
||||
primaryTypographyProps={{ noWrap: true }}
|
||||
secondaryTypographyProps={{ noWrap: true }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
20
src/sections/loadGame/gamePgnInput.tsx
Normal file
20
src/sections/loadGame/gamePgnInput.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FormControl, TextField } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
pgn: string;
|
||||
setPgn: (pgn: string) => void;
|
||||
}
|
||||
|
||||
export default function GamePgnInput({ pgn, setPgn }: Props) {
|
||||
return (
|
||||
<FormControl sx={{ m: 1, width: 600 }}>
|
||||
<TextField
|
||||
label="Enter PGN here..."
|
||||
variant="outlined"
|
||||
multiline
|
||||
value={pgn}
|
||||
onChange={(e) => setPgn(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -8,16 +8,18 @@ import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Box,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
DialogActions,
|
||||
TextField,
|
||||
Typography,
|
||||
Grid,
|
||||
} from "@mui/material";
|
||||
import { Chess } from "chess.js";
|
||||
import { useState } from "react";
|
||||
import GamePgnInput from "./gamePgnInput";
|
||||
import ChessComInput from "./chessComInput";
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -27,6 +29,10 @@ interface Props {
|
||||
|
||||
export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
const [pgn, setPgn] = useState("");
|
||||
const [gameOrigin, setGameOrigin] = useLocalStorage(
|
||||
"preferred-game-origin",
|
||||
GameOrigin.Pgn
|
||||
);
|
||||
const [parsingError, setParsingError] = useState("");
|
||||
const { addGame } = useGameDatabase();
|
||||
|
||||
@@ -63,11 +69,16 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
||||
<DialogTitle marginY={1} variant="h5">
|
||||
Add a game to your database
|
||||
{setGame ? "Load a game" : "Add a game to your database"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>Only PGN input is supported at the moment</Typography>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap" }} marginTop={4}>
|
||||
<Grid
|
||||
container
|
||||
marginTop={1}
|
||||
alignItems="center"
|
||||
justifyContent="start"
|
||||
rowGap={2}
|
||||
>
|
||||
<FormControl sx={{ m: 1, width: 150 }}>
|
||||
<InputLabel id="dialog-select-label">Game origin</InputLabel>
|
||||
<Select
|
||||
@@ -75,8 +86,8 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
id="dialog-select"
|
||||
displayEmpty
|
||||
input={<OutlinedInput label="Game origin" />}
|
||||
value={GameOrigin.Pgn}
|
||||
disabled={true}
|
||||
value={gameOrigin ?? ""}
|
||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
||||
>
|
||||
{Object.values(GameOrigin).map((origin) => (
|
||||
<MenuItem key={origin} value={origin}>
|
||||
@@ -85,15 +96,15 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl sx={{ m: 1, width: 600 }}>
|
||||
<TextField
|
||||
label="Enter PGN here..."
|
||||
variant="outlined"
|
||||
multiline
|
||||
value={pgn}
|
||||
onChange={(e) => setPgn(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
{gameOrigin === GameOrigin.Pgn && (
|
||||
<GamePgnInput pgn={pgn} setPgn={setPgn} />
|
||||
)}
|
||||
|
||||
{gameOrigin === GameOrigin.ChessCom && (
|
||||
<ChessComInput pgn={pgn} setPgn={setPgn} />
|
||||
)}
|
||||
|
||||
{parsingError && (
|
||||
<FormControl fullWidth>
|
||||
<Typography color="red" textAlign="center" marginTop={1}>
|
||||
@@ -101,7 +112,7 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
</Typography>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ m: 2 }}>
|
||||
<Button
|
||||
@@ -122,5 +133,4 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
const gameOriginLabel: Record<GameOrigin, string> = {
|
||||
[GameOrigin.Pgn]: "PGN",
|
||||
[GameOrigin.ChessCom]: "Chess.com",
|
||||
[GameOrigin.Lichess]: "Lichess",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user