Avatar pictures (#16)

* feat(analysis): add Chess.com player avatars to analysis board
This commit is contained in:
titanium_machine
2025-05-07 23:17:06 +02:00
committed by GitHub
parent b0a4081709
commit 8167b9b621
3 changed files with 69 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ import { CurrentPosition } from "@/types/eval";
import EvaluationBar from "./evaluationBar";
import CapturedPieces from "./capturedPieces";
import { moveClassificationColors } from "@/lib/chess";
import Avatar from "@mui/material/Avatar";
export interface Props {
id: string;
@@ -24,6 +25,8 @@ export interface Props {
boardSize?: number;
whitePlayer?: string;
blackPlayer?: string;
whiteAvatar?: string;
blackAvatar?: string;
boardOrientation?: Color;
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
showBestMoveArrow?: boolean;
@@ -38,6 +41,8 @@ export default function Board({
boardSize,
whitePlayer,
blackPlayer,
whiteAvatar,
blackAvatar,
boardOrientation = Color.White,
currentPositionAtom = atom({}),
showBestMoveArrow = false,
@@ -249,6 +254,14 @@ export default function Board({
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>
@@ -298,6 +311,14 @@ export default function Board({
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>

View File

@@ -1,6 +1,7 @@
import { Chess } from "chess.js";
import { PrimitiveAtom, useAtomValue } from "jotai";
import { useGameDatabase } from "./useGameDatabase";
import { useState, useEffect } from "react";
export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
const game = useAtomValue(gameAtom);
@@ -18,10 +19,54 @@ export const usePlayersNames = (gameAtom: PrimitiveAtom<Chess>) => {
const whiteElo = gameFromUrl?.white?.rating || headers.WhiteElo || 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 {
whiteName,
blackName,
whiteElo,
blackElo,
whiteAvatar,
blackAvatar,
};
};

View File

@@ -17,7 +17,7 @@ export default function BoardContainer() {
const screenSize = useScreenSize();
const boardOrientation = useAtomValue(boardOrientationAtom);
const showBestMoveArrow = useAtomValue(showBestMoveArrowAtom);
const { whiteName, whiteElo, blackName, blackElo } =
const { whiteName, whiteElo, blackName, blackElo, whiteAvatar, blackAvatar } =
usePlayersNames(gameAtom);
const boardSize = useMemo(() => {
@@ -40,6 +40,8 @@ export default function BoardContainer() {
gameAtom={boardAtom}
whitePlayer={whiteElo ? `${whiteName} (${whiteElo})` : whiteName}
blackPlayer={blackElo ? `${blackName} (${blackElo})` : blackName}
whiteAvatar={whiteAvatar}
blackAvatar={blackAvatar}
boardOrientation={boardOrientation ? Color.White : Color.Black}
currentPositionAtom={currentPositionAtom}
showBestMoveArrow={showBestMoveArrow}