refacto : board player header
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:import/recommended",
|
"plugin:import/recommended",
|
||||||
"plugin:import/typescript",
|
"plugin:import/typescript",
|
||||||
|
"plugin:@tanstack/eslint-plugin-query/recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"plugin:deprecation/recommended",
|
"plugin:deprecation/recommended",
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"ignorePatterns": [".out/*"],
|
"ignorePatterns": [".out/*"],
|
||||||
"plugins": ["@typescript-eslint", "import", "prettier"],
|
"plugins": ["@typescript-eslint", "import", "prettier", "@tanstack/query"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||||
"prettier/prettier": ["error", {"endOfLine": "auto"}],
|
"prettier/prettier": ["error", {"endOfLine": "auto"}],
|
||||||
|
|||||||
45
package-lock.json
generated
45
package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"@mui/material": "^6.3.0",
|
"@mui/material": "^6.3.0",
|
||||||
"@mui/x-data-grid": "^7.23.5",
|
"@mui/x-data-grid": "^7.23.5",
|
||||||
"@sentry/nextjs": "^8.47.0",
|
"@sentry/nextjs": "^8.47.0",
|
||||||
|
"@tanstack/react-query": "^5.75.5",
|
||||||
"chess.js": "^1.2.0",
|
"chess.js": "^1.2.0",
|
||||||
"firebase": "^11.1.0",
|
"firebase": "^11.1.0",
|
||||||
"idb": "^8.0.1",
|
"idb": "^8.0.1",
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"recharts": "^2.15.0"
|
"recharts": "^2.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.74.7",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "18.2.11",
|
"@types/react": "18.2.11",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
@@ -3644,6 +3646,49 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/eslint-plugin-query": {
|
||||||
|
"version": "5.74.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.74.7.tgz",
|
||||||
|
"integrity": "sha512-EeHuaaYiCOD+XOGyB7LMNEx9OEByAa5lkgP+S3ZggjKJpmIO6iRWeoIYYDKo2F8uc3qXcVhTfC7pn7NddQiNtA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/utils": "^8.18.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/query-core": {
|
||||||
|
"version": "5.75.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.75.5.tgz",
|
||||||
|
"integrity": "sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/react-query": {
|
||||||
|
"version": "5.75.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.75.5.tgz",
|
||||||
|
"integrity": "sha512-QrLCJe40BgBVlWdAdf2ZEVJ0cISOuEy/HKupId1aTKU6gPJZVhSvZpH+Si7csRflCJphzlQ77Yx6gUxGW9o0XQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-core": "5.75.5"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/connect": {
|
"node_modules/@types/connect": {
|
||||||
"version": "3.4.36",
|
"version": "3.4.36",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@mui/material": "^6.3.0",
|
"@mui/material": "^6.3.0",
|
||||||
"@mui/x-data-grid": "^7.23.5",
|
"@mui/x-data-grid": "^7.23.5",
|
||||||
"@sentry/nextjs": "^8.47.0",
|
"@sentry/nextjs": "^8.47.0",
|
||||||
|
"@tanstack/react-query": "^5.75.5",
|
||||||
"chess.js": "^1.2.0",
|
"chess.js": "^1.2.0",
|
||||||
"firebase": "^11.1.0",
|
"firebase": "^11.1.0",
|
||||||
"idb": "^8.0.1",
|
"idb": "^8.0.1",
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
"recharts": "^2.15.0"
|
"recharts": "^2.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.74.7",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "18.2.11",
|
"@types/react": "18.2.11",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Grid2 as Grid, Typography } from "@mui/material";
|
import { Grid2 as Grid } from "@mui/material";
|
||||||
import { Chessboard } from "react-chessboard";
|
import { Chessboard } from "react-chessboard";
|
||||||
import { PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
|
import { PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
@@ -14,19 +14,17 @@ import { Chess } from "chess.js";
|
|||||||
import { getSquareRenderer } from "./squareRenderer";
|
import { getSquareRenderer } from "./squareRenderer";
|
||||||
import { CurrentPosition } from "@/types/eval";
|
import { CurrentPosition } from "@/types/eval";
|
||||||
import EvaluationBar from "./evaluationBar";
|
import EvaluationBar from "./evaluationBar";
|
||||||
import CapturedPieces from "./capturedPieces";
|
|
||||||
import { moveClassificationColors } from "@/lib/chess";
|
import { moveClassificationColors } from "@/lib/chess";
|
||||||
import Avatar from "@mui/material/Avatar";
|
import { Player } from "@/types/game";
|
||||||
|
import PlayerHeader from "./playerHeader";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
canPlay?: Color | boolean;
|
canPlay?: Color | boolean;
|
||||||
gameAtom: PrimitiveAtom<Chess>;
|
gameAtom: PrimitiveAtom<Chess>;
|
||||||
boardSize?: number;
|
boardSize?: number;
|
||||||
whitePlayer?: string;
|
whitePlayer: Player;
|
||||||
blackPlayer?: string;
|
blackPlayer: Player;
|
||||||
whiteAvatar?: string;
|
|
||||||
blackAvatar?: string;
|
|
||||||
boardOrientation?: Color;
|
boardOrientation?: Color;
|
||||||
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
|
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
|
||||||
showBestMoveArrow?: boolean;
|
showBestMoveArrow?: boolean;
|
||||||
@@ -41,8 +39,6 @@ export default function Board({
|
|||||||
boardSize,
|
boardSize,
|
||||||
whitePlayer,
|
whitePlayer,
|
||||||
blackPlayer,
|
blackPlayer,
|
||||||
whiteAvatar,
|
|
||||||
blackAvatar,
|
|
||||||
boardOrientation = Color.White,
|
boardOrientation = Color.White,
|
||||||
currentPositionAtom = atom({}),
|
currentPositionAtom = atom({}),
|
||||||
showBestMoveArrow = false,
|
showBestMoveArrow = false,
|
||||||
@@ -247,30 +243,11 @@ export default function Board({
|
|||||||
paddingLeft={showEvaluationBar ? 2 : 0}
|
paddingLeft={showEvaluationBar ? 2 : 0}
|
||||||
size="grow"
|
size="grow"
|
||||||
>
|
>
|
||||||
<Grid
|
<PlayerHeader
|
||||||
container
|
color={boardOrientation === Color.White ? Color.Black : Color.White}
|
||||||
justifyContent="center"
|
fen={gameFen}
|
||||||
alignItems="center"
|
player={boardOrientation === Color.White ? blackPlayer : whitePlayer}
|
||||||
columnGap={2}
|
/>
|
||||||
size={12}
|
|
||||||
>
|
|
||||||
{/* Player avatar, only render if URL is available */}
|
|
||||||
{(boardOrientation === Color.White ? blackAvatar : whiteAvatar) && (
|
|
||||||
<Avatar
|
|
||||||
src={boardOrientation === Color.White ? blackAvatar : whiteAvatar}
|
|
||||||
variant="circular"
|
|
||||||
sx={{ width: 24, height: 24 }}
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
<Typography>
|
|
||||||
{boardOrientation === Color.White ? blackPlayer : whitePlayer}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<CapturedPieces
|
|
||||||
fen={gameFen}
|
|
||||||
color={boardOrientation === Color.White ? Color.Black : Color.White}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
@@ -304,27 +281,11 @@ export default function Board({
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<PlayerHeader
|
||||||
container
|
color={boardOrientation}
|
||||||
justifyContent="center"
|
fen={gameFen}
|
||||||
alignItems="center"
|
player={boardOrientation === Color.White ? whitePlayer : blackPlayer}
|
||||||
columnGap={2}
|
/>
|
||||||
size={12}
|
|
||||||
>
|
|
||||||
{/* Player avatar, only render if URL is available */}
|
|
||||||
{ (boardOrientation === Color.White ? whiteAvatar : blackAvatar) && (
|
|
||||||
<Avatar
|
|
||||||
src={boardOrientation === Color.White ? whiteAvatar : blackAvatar}
|
|
||||||
variant="circular"
|
|
||||||
sx={{ width: 24, height: 24 }}
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
<Typography>
|
|
||||||
{boardOrientation === Color.White ? whitePlayer : blackPlayer}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<CapturedPieces fen={gameFen} color={boardOrientation} />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|||||||
35
src/components/board/playerHeader.tsx
Normal file
35
src/components/board/playerHeader.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Color } from "@/types/enums";
|
||||||
|
import { Player } from "@/types/game";
|
||||||
|
import { Avatar, Grid2 as Grid, Typography } from "@mui/material";
|
||||||
|
import CapturedPieces from "./capturedPieces";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
player: Player;
|
||||||
|
color: Color;
|
||||||
|
fen: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlayerHeader({ color, player, fen }: Props) {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
columnGap={2}
|
||||||
|
size={12}
|
||||||
|
>
|
||||||
|
{player.avatarUrl && (
|
||||||
|
<Avatar
|
||||||
|
src={player.avatarUrl}
|
||||||
|
variant="circular"
|
||||||
|
sx={{ width: 24, height: 24 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography>
|
||||||
|
{player.rating ? `${player.name} (${player.rating})` : player.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<CapturedPieces fen={fen} color={color} />
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
import { PrimitiveAtom, useAtomValue } from "jotai";
|
import { PrimitiveAtom, useAtomValue } from "jotai";
|
||||||
import { useGameDatabase } from "./useGameDatabase";
|
import { useGameDatabase } from "./useGameDatabase";
|
||||||
import { useState, useEffect } from "react";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getChessComUserAvatar } from "@/lib/chessCom";
|
||||||
|
import { Player } from "@/types/game";
|
||||||
|
|
||||||
export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
export const usePlayersData = (
|
||||||
|
gameAtom: PrimitiveAtom<Chess>
|
||||||
|
): { white: Player; black: Player } => {
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
const { gameFromUrl } = useGameDatabase();
|
const { gameFromUrl } = useGameDatabase();
|
||||||
const headers = game.getHeaders();
|
const headers = game.getHeaders();
|
||||||
@@ -16,57 +20,49 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
|||||||
const whiteName = gameFromUrl?.white?.name || headersWhiteName || "White";
|
const whiteName = gameFromUrl?.white?.name || headersWhiteName || "White";
|
||||||
const blackName = gameFromUrl?.black?.name || headersBlackName || "Black";
|
const blackName = gameFromUrl?.black?.name || headersBlackName || "Black";
|
||||||
|
|
||||||
const whiteElo = gameFromUrl?.white?.rating || headers.WhiteElo || undefined;
|
const whiteElo =
|
||||||
const blackElo = gameFromUrl?.black?.rating || headers.BlackElo || undefined;
|
gameFromUrl?.white?.rating || Number(headers.WhiteElo) || undefined;
|
||||||
|
const blackElo =
|
||||||
|
gameFromUrl?.black?.rating || Number(headers.BlackElo) || undefined;
|
||||||
|
|
||||||
// Determine if this game came from Chess.com (via PGN header or URL)
|
const siteHeader = gameFromUrl?.site || headers.Site || "unknown";
|
||||||
const siteHeader = gameFromUrl?.site || headers.Site || "";
|
|
||||||
const isChessCom = siteHeader.toLowerCase().includes("chess.com");
|
const isChessCom = siteHeader.toLowerCase().includes("chess.com");
|
||||||
|
|
||||||
// Avatars fetched only for Chess.com games
|
const whiteAvatarUrl = usePlayerAvatarUrl(
|
||||||
const [whiteAvatar, setWhiteAvatar] = useState<string | undefined>(undefined);
|
whiteName,
|
||||||
const [blackAvatar, setBlackAvatar] = useState<string | undefined>(undefined);
|
isChessCom && !!whiteName && whiteName !== "White"
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch white avatar
|
const blackAvatarUrl = usePlayerAvatarUrl(
|
||||||
useEffect(() => {
|
blackName,
|
||||||
if (isChessCom && whiteName && whiteName !== "White") {
|
isChessCom && !!blackName && blackName !== "Black"
|
||||||
// Normalize and encode username
|
);
|
||||||
const trimmedWhiteName = whiteName.trim().toLowerCase();
|
|
||||||
const usernameParam = encodeURIComponent(trimmedWhiteName);
|
|
||||||
fetch(`https://api.chess.com/pub/player/${usernameParam}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setWhiteAvatar(data.avatar || undefined))
|
|
||||||
.catch(() => {
|
|
||||||
setWhiteAvatar(undefined);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setWhiteAvatar(undefined);
|
|
||||||
}
|
|
||||||
}, [isChessCom, whiteName]);
|
|
||||||
|
|
||||||
// Fetch black avatar
|
|
||||||
useEffect(() => {
|
|
||||||
if (isChessCom && blackName && blackName !== "Black") {
|
|
||||||
// Normalize and encode username
|
|
||||||
const trimmedBlackName = blackName.trim().toLowerCase();
|
|
||||||
const usernameParamBlack = encodeURIComponent(trimmedBlackName);
|
|
||||||
fetch(`https://api.chess.com/pub/player/${usernameParamBlack}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setBlackAvatar(data.avatar || undefined))
|
|
||||||
.catch(() => {
|
|
||||||
setBlackAvatar(undefined);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setBlackAvatar(undefined);
|
|
||||||
}
|
|
||||||
}, [isChessCom, blackName]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
whiteName,
|
white: {
|
||||||
blackName,
|
name: whiteName,
|
||||||
whiteElo,
|
rating: whiteElo,
|
||||||
blackElo,
|
avatarUrl: whiteAvatarUrl ?? undefined,
|
||||||
whiteAvatar,
|
},
|
||||||
blackAvatar,
|
black: {
|
||||||
|
name: blackName,
|
||||||
|
rating: blackElo,
|
||||||
|
avatarUrl: blackAvatarUrl ?? undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const usePlayerAvatarUrl = (
|
||||||
|
playerName: string,
|
||||||
|
enabled: boolean
|
||||||
|
): string | null | undefined => {
|
||||||
|
const { data: avatarUrl } = useQuery({
|
||||||
|
queryKey: ["CCAvatar", playerName],
|
||||||
|
enabled,
|
||||||
|
queryFn: () => getChessComUserAvatar(playerName),
|
||||||
|
staleTime: 1000 * 60 * 60, // 1 hour
|
||||||
|
gcTime: 1000 * 60 * 60 * 24, // 1 day
|
||||||
|
});
|
||||||
|
|
||||||
|
return avatarUrl;
|
||||||
|
};
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export const formatGameToDatabase = (game: Chess): Omit<Game, "id"> => {
|
|||||||
date: headers.Date,
|
date: headers.Date,
|
||||||
round: headers.Round ?? "?",
|
round: headers.Round ?? "?",
|
||||||
white: {
|
white: {
|
||||||
name: headers.White,
|
name: headers.White || "White",
|
||||||
rating: headers.WhiteElo ? Number(headers.WhiteElo) : undefined,
|
rating: headers.WhiteElo ? Number(headers.WhiteElo) : undefined,
|
||||||
},
|
},
|
||||||
black: {
|
black: {
|
||||||
name: headers.Black,
|
name: headers.Black || "Black",
|
||||||
rating: headers.BlackElo ? Number(headers.BlackElo) : undefined,
|
rating: headers.BlackElo ? Number(headers.BlackElo) : undefined,
|
||||||
},
|
},
|
||||||
result: headers.Result,
|
result: headers.Result,
|
||||||
|
|||||||
@@ -39,3 +39,15 @@ export const getChessComUserRecentGames = async (
|
|||||||
|
|
||||||
return gamesToReturn;
|
return gamesToReturn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getChessComUserAvatar = async (
|
||||||
|
username: string
|
||||||
|
): Promise<string | null> => {
|
||||||
|
const usernameParam = encodeURIComponent(username.trim().toLowerCase());
|
||||||
|
|
||||||
|
const res = await fetch(`https://api.chess.com/pub/player/${usernameParam}`);
|
||||||
|
const data = await res.json();
|
||||||
|
const avatarUrl = data?.avatar;
|
||||||
|
|
||||||
|
return typeof avatarUrl === "string" ? avatarUrl : null;
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import "@fontsource/roboto/500.css";
|
|||||||
import "@fontsource/roboto/700.css";
|
import "@fontsource/roboto/700.css";
|
||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
import Layout from "@/sections/layout";
|
import Layout from "@/sections/layout";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Component {...pageProps} />
|
<Layout>
|
||||||
</Layout>
|
<Component {...pageProps} />
|
||||||
|
</Layout>
|
||||||
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ import { useMemo } from "react";
|
|||||||
import { useScreenSize } from "@/hooks/useScreenSize";
|
import { useScreenSize } from "@/hooks/useScreenSize";
|
||||||
import { Color } from "@/types/enums";
|
import { Color } from "@/types/enums";
|
||||||
import Board from "@/components/board";
|
import Board from "@/components/board";
|
||||||
import { usePlayersNames } from "@/hooks/usePlayerNames";
|
import { usePlayersData } from "@/hooks/usePlayerNames";
|
||||||
|
|
||||||
export default function BoardContainer() {
|
export default function BoardContainer() {
|
||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
const boardOrientation = useAtomValue(boardOrientationAtom);
|
const boardOrientation = useAtomValue(boardOrientationAtom);
|
||||||
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
|
||||||
const { whiteName, whiteElo, blackName, blackElo, whiteAvatar, blackAvatar } =
|
const { white, black } = usePlayersData(gameAtom);
|
||||||
usePlayersNames(gameAtom);
|
|
||||||
|
|
||||||
const boardSize = useMemo(() => {
|
const boardSize = useMemo(() => {
|
||||||
const width = screenSize.width;
|
const width = screenSize.width;
|
||||||
@@ -38,10 +37,8 @@ export default function BoardContainer() {
|
|||||||
boardSize={boardSize}
|
boardSize={boardSize}
|
||||||
canPlay={true}
|
canPlay={true}
|
||||||
gameAtom={boardAtom}
|
gameAtom={boardAtom}
|
||||||
whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName}
|
whitePlayer={white}
|
||||||
blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName}
|
blackPlayer={black}
|
||||||
whiteAvatar={whiteAvatar}
|
|
||||||
blackAvatar={blackAvatar}
|
|
||||||
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
||||||
currentPositionAtom={currentPositionAtom}
|
currentPositionAtom={currentPositionAtom}
|
||||||
showBestMoveArrow={showBestMoveArrow}
|
showBestMoveArrow={showBestMoveArrow}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { usePlayersNames } from "@/hooks/usePlayerNames";
|
import { usePlayersData } from "@/hooks/usePlayerNames";
|
||||||
import { Grid2 as Grid, Typography } from "@mui/material";
|
import { Grid2 as Grid, Typography } from "@mui/material";
|
||||||
import { gameAtom, gameEvalAtom } from "../../../states";
|
import { gameAtom, gameEvalAtom } from "../../../states";
|
||||||
import { MoveClassification } from "@/types/enums";
|
import { MoveClassification } from "@/types/enums";
|
||||||
@@ -6,7 +6,7 @@ import ClassificationRow from "./classificationRow";
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
export default function MovesClassificationsRecap() {
|
export default function MovesClassificationsRecap() {
|
||||||
const { whiteName, blackName } = usePlayersNames(gameAtom);
|
const { white, black } = usePlayersData(gameAtom);
|
||||||
const gameEval = useAtomValue(gameEvalAtom);
|
const gameEval = useAtomValue(gameEvalAtom);
|
||||||
|
|
||||||
if (!gameEval?.positions.length) return null;
|
if (!gameEval?.positions.length) return null;
|
||||||
@@ -29,13 +29,13 @@ export default function MovesClassificationsRecap() {
|
|||||||
size={12}
|
size={12}
|
||||||
>
|
>
|
||||||
<Typography width="12rem" align="center" noWrap fontSize="0.9rem">
|
<Typography width="12rem" align="center" noWrap fontSize="0.9rem">
|
||||||
{whiteName}
|
{white.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography width="7rem" />
|
<Typography width="7rem" />
|
||||||
|
|
||||||
<Typography width="12rem" align="center" noWrap fontSize="0.9rem">
|
<Typography width="12rem" align="center" noWrap fontSize="0.9rem">
|
||||||
{blackName}
|
{black.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
|||||||
value={engine}
|
value={engine}
|
||||||
disabled={!isEngineSupported(engine)}
|
disabled={!isEngineSupported(engine)}
|
||||||
>
|
>
|
||||||
{engineLabel[engine]}
|
{engineLabel[engine].full}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -129,12 +129,34 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const engineLabel: Record<EngineName, string> = {
|
export const engineLabel: Record<EngineName, { small: string; full: string }> =
|
||||||
[EngineName.Stockfish17]: "Stockfish 17 (75MB)",
|
{
|
||||||
[EngineName.Stockfish17Lite]: "Stockfish 17 Lite (6MB)",
|
[EngineName.Stockfish17]: {
|
||||||
[EngineName.Stockfish16_1]: "Stockfish 16.1 (64MB)",
|
full: "Stockfish 17 (75MB)",
|
||||||
[EngineName.Stockfish16_1Lite]: "Stockfish 16.1 Lite (6MB)",
|
small: "Stockfish 17",
|
||||||
[EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB)",
|
},
|
||||||
[EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)",
|
[EngineName.Stockfish17Lite]: {
|
||||||
[EngineName.Stockfish11]: "Stockfish 11",
|
full: "Stockfish 17 Lite (6MB)",
|
||||||
};
|
small: "Stockfish 17 Lite",
|
||||||
|
},
|
||||||
|
[EngineName.Stockfish16_1]: {
|
||||||
|
full: "Stockfish 16.1 (64MB)",
|
||||||
|
small: "Stockfish 16.1",
|
||||||
|
},
|
||||||
|
[EngineName.Stockfish16_1Lite]: {
|
||||||
|
full: "Stockfish 16.1 Lite (6MB)",
|
||||||
|
small: "Stockfish 16.1 Lite",
|
||||||
|
},
|
||||||
|
[EngineName.Stockfish16NNUE]: {
|
||||||
|
full: "Stockfish 16 (40MB)",
|
||||||
|
small: "Stockfish 16",
|
||||||
|
},
|
||||||
|
[EngineName.Stockfish16]: {
|
||||||
|
full: "Stockfish 16 Lite (HCE)",
|
||||||
|
small: "Stockfish 16 Lite",
|
||||||
|
},
|
||||||
|
[EngineName.Stockfish11]: {
|
||||||
|
full: "Stockfish 11 (HCE)",
|
||||||
|
small: "Stockfish 11",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,17 +10,18 @@ import {
|
|||||||
import { useChessActions } from "@/hooks/useChessActions";
|
import { useChessActions } from "@/hooks/useChessActions";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useScreenSize } from "@/hooks/useScreenSize";
|
import { useScreenSize } from "@/hooks/useScreenSize";
|
||||||
import { Color } from "@/types/enums";
|
|
||||||
import { useEngine } from "@/hooks/useEngine";
|
import { useEngine } from "@/hooks/useEngine";
|
||||||
import { uciMoveParams } from "@/lib/chess";
|
import { uciMoveParams } from "@/lib/chess";
|
||||||
import Board from "@/components/board";
|
import Board from "@/components/board";
|
||||||
import { useGameData } from "@/hooks/useGameData";
|
import { useGameData } from "@/hooks/useGameData";
|
||||||
|
import { usePlayersData } from "@/hooks/usePlayerNames";
|
||||||
|
|
||||||
export default function BoardContainer() {
|
export default function BoardContainer() {
|
||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
const engineName = useAtomValue(enginePlayNameAtom);
|
const engineName = useAtomValue(enginePlayNameAtom);
|
||||||
const engine = useEngine(engineName, 1);
|
const engine = useEngine(engineName, 1);
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
|
const { white, black } = usePlayersData(gameAtom);
|
||||||
const playerColor = useAtomValue(playerColorAtom);
|
const playerColor = useAtomValue(playerColorAtom);
|
||||||
const { makeMove: makeGameMove } = useChessActions(gameAtom);
|
const { makeMove: makeGameMove } = useChessActions(gameAtom);
|
||||||
const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
|
const engineSkillLevel = useAtomValue(engineSkillLevelAtom);
|
||||||
@@ -72,16 +73,8 @@ export default function BoardContainer() {
|
|||||||
canPlay={isGameInProgress ? playerColor : false}
|
canPlay={isGameInProgress ? playerColor : false}
|
||||||
gameAtom={gameAtom}
|
gameAtom={gameAtom}
|
||||||
boardSize={boardSize}
|
boardSize={boardSize}
|
||||||
whitePlayer={
|
whitePlayer={white}
|
||||||
playerColor === Color.White
|
blackPlayer={black}
|
||||||
? "You 🧠"
|
|
||||||
: `Stockfish level ${engineSkillLevel} 🤖`
|
|
||||||
}
|
|
||||||
blackPlayer={
|
|
||||||
playerColor === Color.Black
|
|
||||||
? "You 🧠"
|
|
||||||
: `Stockfish level ${engineSkillLevel} 🤖`
|
|
||||||
}
|
|
||||||
boardOrientation={playerColor}
|
boardOrientation={playerColor}
|
||||||
currentPositionAtom={gameDataAtom}
|
currentPositionAtom={gameDataAtom}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { logAnalyticsEvent } from "@/lib/firebase";
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { isEngineSupported } from "@/lib/engine/shared";
|
import { isEngineSupported } from "@/lib/engine/shared";
|
||||||
import { Stockfish16_1 } from "@/lib/engine/stockfish16_1";
|
import { Stockfish16_1 } from "@/lib/engine/stockfish16_1";
|
||||||
|
import { engineLabel } from "@/sections/engineSettings/engineSettingsDialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -55,9 +56,13 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
|
|||||||
onClose();
|
onClose();
|
||||||
resetGame({
|
resetGame({
|
||||||
whiteName:
|
whiteName:
|
||||||
playerColor === Color.White ? "You" : `Stockfish level ${skillLevel}`,
|
playerColor === Color.White
|
||||||
|
? "You"
|
||||||
|
: `${engineLabel[engineName].small} level ${skillLevel}`,
|
||||||
blackName:
|
blackName:
|
||||||
playerColor === Color.Black ? "You" : `Stockfish level ${skillLevel}`,
|
playerColor === Color.Black
|
||||||
|
? "You"
|
||||||
|
: `${engineLabel[engineName].small} level ${skillLevel}`,
|
||||||
});
|
});
|
||||||
playGameStartSound();
|
playGameStartSound();
|
||||||
setIsGameInProgress(true);
|
setIsGameInProgress(true);
|
||||||
@@ -117,7 +122,7 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
|
|||||||
value={engine}
|
value={engine}
|
||||||
disabled={!isEngineSupported(engine)}
|
disabled={!isEngineSupported(engine)}
|
||||||
>
|
>
|
||||||
{engineLabel[engine]}
|
{engineLabel[engine].full}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -166,13 +171,3 @@ export default function GameSettingsDialog({ open, onClose }: Props) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const engineLabel: Record<EngineName, string> = {
|
|
||||||
[EngineName.Stockfish17]: "Stockfish 17 (75MB)",
|
|
||||||
[EngineName.Stockfish17Lite]: "Stockfish 17 Lite (6MB)",
|
|
||||||
[EngineName.Stockfish16_1]: "Stockfish 16.1 (64MB)",
|
|
||||||
[EngineName.Stockfish16_1Lite]: "Stockfish 16.1 Lite (6MB)",
|
|
||||||
[EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB)",
|
|
||||||
[EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)",
|
|
||||||
[EngineName.Stockfish11]: "Stockfish 11",
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Player {
|
export interface Player {
|
||||||
name?: string;
|
name: string;
|
||||||
rating?: number;
|
rating?: number;
|
||||||
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user