Avatar pictures (#16)
* feat(analysis): add Chess.com player avatars to analysis board
This commit is contained in:
@@ -16,6 +16,7 @@ import { CurrentPosition } from "@/types/eval";
|
|||||||
import EvaluationBar from "./evaluationBar";
|
import EvaluationBar from "./evaluationBar";
|
||||||
import CapturedPieces from "./capturedPieces";
|
import CapturedPieces from "./capturedPieces";
|
||||||
import { moveClassificationColors } from "@/lib/chess";
|
import { moveClassificationColors } from "@/lib/chess";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -24,6 +25,8 @@ export interface Props {
|
|||||||
boardSize?: number;
|
boardSize?: number;
|
||||||
whitePlayer?: string;
|
whitePlayer?: string;
|
||||||
blackPlayer?: string;
|
blackPlayer?: string;
|
||||||
|
whiteAvatar?: string;
|
||||||
|
blackAvatar?: string;
|
||||||
boardOrientation?: Color;
|
boardOrientation?: Color;
|
||||||
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
|
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
|
||||||
showBestMoveArrow?: boolean;
|
showBestMoveArrow?: boolean;
|
||||||
@@ -38,6 +41,8 @@ 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,
|
||||||
@@ -249,6 +254,14 @@ export default function Board({
|
|||||||
columnGap={2}
|
columnGap={2}
|
||||||
size={12}
|
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>
|
<Typography>
|
||||||
{boardOrientation === Color.White ? blackPlayer : whitePlayer}
|
{boardOrientation === Color.White ? blackPlayer : whitePlayer}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -298,6 +311,14 @@ export default function Board({
|
|||||||
columnGap={2}
|
columnGap={2}
|
||||||
size={12}
|
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>
|
<Typography>
|
||||||
{boardOrientation === Color.White ? whitePlayer : blackPlayer}
|
{boardOrientation === Color.White ? whitePlayer : blackPlayer}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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";
|
||||||
|
|
||||||
export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
||||||
const game = useAtomValue(gameAtom);
|
const game = useAtomValue(gameAtom);
|
||||||
@@ -18,10 +19,54 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
|
|||||||
const whiteElo = gameFromUrl?.white?.rating || headers.WhiteElo || undefined;
|
const whiteElo = gameFromUrl?.white?.rating || headers.WhiteElo || undefined;
|
||||||
const blackElo = gameFromUrl?.black?.rating || headers.BlackElo || undefined;
|
const blackElo = gameFromUrl?.black?.rating || headers.BlackElo || undefined;
|
||||||
|
|
||||||
|
// Determine if this game came from Chess.com (via PGN header or URL)
|
||||||
|
const siteHeader = gameFromUrl?.site || headers.Site || "";
|
||||||
|
const isChessCom = siteHeader.toLowerCase().includes("chess.com");
|
||||||
|
|
||||||
|
// Avatars fetched only for Chess.com games
|
||||||
|
const [whiteAvatar, setWhiteAvatar] = useState<string | undefined>(undefined);
|
||||||
|
const [blackAvatar, setBlackAvatar] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
// Fetch white avatar
|
||||||
|
useEffect(() => {
|
||||||
|
if (isChessCom && whiteName && whiteName !== "White") {
|
||||||
|
// 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,
|
whiteName,
|
||||||
blackName,
|
blackName,
|
||||||
whiteElo,
|
whiteElo,
|
||||||
blackElo,
|
blackElo,
|
||||||
|
whiteAvatar,
|
||||||
|
blackAvatar,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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 } =
|
const { whiteName, whiteElo, blackName, blackElo, whiteAvatar, blackAvatar } =
|
||||||
usePlayersNames(gameAtom);
|
usePlayersNames(gameAtom);
|
||||||
|
|
||||||
const boardSize = useMemo(() => {
|
const boardSize = useMemo(() => {
|
||||||
@@ -40,6 +40,8 @@ export default function BoardContainer() {
|
|||||||
gameAtom={boardAtom}
|
gameAtom={boardAtom}
|
||||||
whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName}
|
whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName}
|
||||||
blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName}
|
blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName}
|
||||||
|
whiteAvatar={whiteAvatar}
|
||||||
|
blackAvatar={blackAvatar}
|
||||||
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
boardOrientation={boardOrientation ? Color.White : Color.Black}
|
||||||
currentPositionAtom={currentPositionAtom}
|
currentPositionAtom={currentPositionAtom}
|
||||||
showBestMoveArrow={showBestMoveArrow}
|
showBestMoveArrow={showBestMoveArrow}
|
||||||
|
|||||||
Reference in New Issue
Block a user