style : analysis card UI rework adjustments

This commit is contained in:
GuillaumeSD
2025-06-03 02:27:52 +02:00
parent d04c4d99df
commit 6310f7a375
13 changed files with 171 additions and 141 deletions

View File

@@ -25,7 +25,7 @@ export default function BoardContainer() {
// 1200 is the lg layout breakpoint
if (window?.innerWidth < 1200) {
return Math.min(width, height - 150);
return Math.min(width - 15, height - 150);
}
return Math.min(width - 700, height * 0.92);

View File

@@ -0,0 +1,35 @@
import { Grid2 as Grid, Grid2Props as GridProps, List } from "@mui/material";
import LineEvaluation from "./lineEvaluation";
import {
boardAtom,
currentPositionAtom,
engineMultiPvAtom,
} from "../../../states";
import { useAtomValue } from "jotai";
import { LineEval } from "@/types/eval";
export default function EngineLines(props: GridProps) {
const board = useAtomValue(boardAtom);
const linesNumber = useAtomValue(engineMultiPvAtom);
const position = useAtomValue(currentPositionAtom);
const linesSkeleton: LineEval[] = Array.from({ length: linesNumber }).map(
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
);
const engineLines = position?.eval?.lines?.length
? position.eval.lines
: linesSkeleton;
if (board.isCheckmate()) return null;
return (
<Grid container justifyContent="center" alignItems="center" {...props}>
<List sx={{ width: "95%", padding: 0 }}>
{engineLines.map((line) => (
<LineEvaluation key={line.multiPv} line={line} />
))}
</List>
</Grid>
);
}

View File

@@ -1,7 +1,7 @@
import { LineEval } from "@/types/eval";
import { ListItem, Skeleton, Typography } from "@mui/material";
import { useAtomValue } from "jotai";
import { boardAtom } from "../../states";
import { boardAtom } from "../../../states";
import { getLineEvalLabel, moveLineUciToSan } from "@/lib/chess";
import { useChessActions } from "@/hooks/useChessActions";
import PrettyMoveSan from "@/components/prettyMoveSan";

View File

@@ -1,74 +1,76 @@
import { Grid2 as Grid, Grid2Props as GridProps, List } from "@mui/material";
import { useAtomValue } from "jotai";
import {
boardAtom,
currentPositionAtom,
engineMultiPvAtom,
gameEvalAtom,
} from "../../states";
import LineEvaluation from "./lineEvaluation";
import { LineEval } from "@/types/eval";
Grid2 as Grid,
Grid2Props as GridProps,
Stack,
Typography,
} from "@mui/material";
import { useAtomValue } from "jotai";
import { boardAtom, gameAtom, gameEvalAtom } from "../../states";
import PlayersMetric from "./playersMetric";
import MoveInfo from "./moveInfo";
import Opening from "./opening";
import EngineLines from "./engineLines";
export default function AnalysisTab(props: GridProps) {
const linesNumber = useAtomValue(engineMultiPvAtom);
const position = useAtomValue(currentPositionAtom);
const board = useAtomValue(boardAtom);
const gameEval = useAtomValue(gameEvalAtom);
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const linesSkeleton: LineEval[] = Array.from({ length: linesNumber }).map(
(_, i) => ({ pv: [`${i}`], depth: 0, multiPv: i + 1 })
);
const boardHistory = board.history();
const gameHistory = game.history();
const engineLines = position?.eval?.lines?.length
? position.eval.lines
: linesSkeleton;
const isGameOver =
boardHistory.length > 0 &&
(board.isCheckmate() ||
board.isDraw() ||
boardHistory.join() === gameHistory.join());
return (
<Grid
container
size={12}
justifyContent="center"
alignItems="start"
height="100%"
rowGap={0.8}
size={{ xs: 12, lg: gameEval ? 11 : 12 }}
justifyContent={{ xs: "center", lg: gameEval ? "start" : "center" }}
alignItems="center"
flexWrap={{ lg: gameEval ? "nowrap" : undefined }}
gap={2}
marginY={{ lg: gameEval ? 1 : undefined }}
{...props}
sx={
props.hidden
? { display: "none" }
: { overflow: "hidden", overflowY: "auto", ...props.sx }
}
sx={props.hidden ? { display: "none" } : props.sx}
>
{gameEval && (
<PlayersMetric
title="Accuracy"
whiteValue={`${gameEval.accuracy.white.toFixed(1)} %`}
blackValue={`${gameEval.accuracy.black.toFixed(1)} %`}
/>
)}
<Stack
justifyContent="center"
alignItems="center"
rowGap={1}
minWidth={gameEval ? "min(25rem, 95vw)" : undefined}
>
{gameEval && (
<PlayersMetric
title="Accuracy"
whiteValue={`${gameEval.accuracy.white.toFixed(1)} %`}
blackValue={`${gameEval.accuracy.black.toFixed(1)} %`}
/>
)}
{gameEval?.estimatedElo && (
<PlayersMetric
title="Game Rating"
whiteValue={Math.round(gameEval.estimatedElo.white)}
blackValue={Math.round(gameEval.estimatedElo.black)}
/>
)}
{gameEval?.estimatedElo && (
<PlayersMetric
title="Game Rating"
whiteValue={Math.round(gameEval.estimatedElo.white)}
blackValue={Math.round(gameEval.estimatedElo.black)}
/>
)}
<MoveInfo />
<MoveInfo />
<Opening />
<Opening />
<Grid container justifyContent="center" alignItems="center" size={12}>
<List sx={{ width: { xs: "95%", lg: "90%" }, padding: 0 }}>
{!board.isCheckmate() &&
engineLines.map((line) => (
<LineEvaluation key={line.multiPv} line={line} />
))}
</List>
</Grid>
{isGameOver && (
<Typography align="center" fontSize="0.9rem" noWrap>
Game is over
</Typography>
)}
</Stack>
<EngineLines size={{ lg: gameEval ? undefined : 12 }} />
</Grid>
);
}

View File

@@ -1,6 +1,6 @@
import { Grid2 as Grid, Skeleton, Stack, Typography } from "@mui/material";
import { Skeleton, Stack, Typography } from "@mui/material";
import { useAtomValue } from "jotai";
import { boardAtom, currentPositionAtom, gameAtom } from "../../states";
import { boardAtom, currentPositionAtom } from "../../states";
import { useMemo } from "react";
import { moveLineUciToSan } from "@/lib/chess";
import { MoveClassification } from "@/types/enums";
@@ -10,7 +10,6 @@ import PrettyMoveSan from "@/components/prettyMoveSan";
export default function MoveInfo() {
const position = useAtomValue(currentPositionAtom);
const board = useAtomValue(boardAtom);
const game = useAtomValue(gameAtom);
const bestMove = position?.lastEval?.bestMove;
@@ -23,36 +22,22 @@ export default function MoveInfo() {
return moveLineUciToSan(lastPosition)(bestMove);
}, [bestMove, board]);
const boardHistory = board.history();
const gameHistory = game.history();
if (board.history().length === 0) return null;
const isGameOver =
boardHistory.length > 0 &&
(board.isCheckmate() ||
board.isDraw() ||
boardHistory.join() === gameHistory.join());
if (!bestMoveSan) {
return (
<Grid
size={12}
justifyItems="center"
alignContent="center"
marginTop={0.5}
>
<Stack direction="row" alignItems="center" columnGap={5} marginTop={0.8}>
<Skeleton
variant="rounded"
animation="wave"
width={"12em"}
sx={{ color: "transparent", maxWidth: "7vw", maxHeight: "3.5vw" }}
sx={{ color: "transparent", maxWidth: "7vw" }}
>
<Typography align="center" fontSize="0.9rem">
placeholder
</Typography>
</Skeleton>
</Grid>
</Stack>
);
}
@@ -66,12 +51,13 @@ export default function MoveInfo() {
moveClassification !== MoveClassification.Perfect;
return (
<Grid
container
columnGap={5}
<Stack
direction="row"
alignItems="center"
justifyContent="center"
size={12}
columnGap={4}
marginTop={0.5}
flexWrap="wrap"
>
{moveClassification && (
<Stack direction="row" alignItems="center" spacing={1}>
@@ -121,13 +107,7 @@ export default function MoveInfo() {
/>
</Stack>
)}
{isGameOver && (
<Typography align="center" fontSize="0.9rem">
Game is over
</Typography>
)}
</Grid>
</Stack>
);
}

View File

@@ -12,7 +12,7 @@ export default function Opening() {
if (!opening) {
return (
<Grid size={12} justifyItems="center" alignContent="center">
<Grid justifyItems="center" alignContent="center">
<Skeleton
variant="rounded"
animation="wave"
@@ -28,7 +28,7 @@ export default function Opening() {
}
return (
<Grid size={12}>
<Grid>
<Typography align="center" fontSize="0.9rem">
{opening}
</Typography>

View File

@@ -1,4 +1,4 @@
import { Grid2 as Grid, Typography } from "@mui/material";
import { Stack, Typography } from "@mui/material";
interface Props {
title: string;
@@ -12,21 +12,20 @@ export default function PlayersMetric({
blackValue,
}: Props) {
return (
<Grid
container
<Stack
justifyContent="center"
alignItems="center"
flexDirection="row"
columnGap={{ xs: "8vw", md: 10 }}
size={12}
>
<ValueBlock value={whiteValue} color="white" />
<Typography align="center" fontSize="0.8em">
<Typography align="center" fontSize="0.8em" noWrap>
{title}
</Typography>
<ValueBlock value={blackValue} color="black" />
</Grid>
</Stack>
);
}
@@ -50,6 +49,7 @@ const ValueBlock = ({
padding={0.8}
fontWeight="500"
border="1px solid #424242"
noWrap
>
{value}
</Typography>

View File

@@ -8,8 +8,8 @@ export default function ClassificationTab(props: GridProps) {
container
justifyContent="center"
alignItems="start"
height="100%"
maxHeight={{ xs: "18rem", lg: "none" }}
size={12}
flexGrow={1}
{...props}
sx={
props.hidden ? { display: "none" } : { overflow: "hidden", ...props.sx }

View File

@@ -2,15 +2,19 @@ import { Grid2 as Grid } from "@mui/material";
import MovesLine from "./movesLine";
import { useMemo } from "react";
import { useAtomValue } from "jotai";
import { gameAtom, gameEvalAtom } from "../../../states";
import { boardAtom, gameAtom, gameEvalAtom } from "../../../states";
import { MoveClassification } from "@/types/enums";
export default function MovesPanel() {
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const gameEval = useAtomValue(gameEvalAtom);
const gameMoves = useMemo(() => {
const history = game.history();
const gameHistory = game.history();
const boardHistory = board.history();
const history = gameHistory.length ? gameHistory : boardHistory;
if (!history.length) return undefined;
const moves: { san: string; moveClassification?: MoveClassification }[][] =
@@ -20,14 +24,18 @@ export default function MovesPanel() {
const items = [
{
san: history[i],
moveClassification: gameEval?.positions[i + 1]?.moveClassification,
moveClassification: gameHistory.length
? gameEval?.positions[i + 1]?.moveClassification
: undefined,
},
];
if (history[i + 1]) {
items.push({
san: history[i + 1],
moveClassification: gameEval?.positions[i + 2]?.moveClassification,
moveClassification: gameHistory.length
? gameEval?.positions[i + 2]?.moveClassification
: undefined,
});
}
@@ -35,17 +43,19 @@ export default function MovesPanel() {
}
return moves;
}, [game, gameEval]);
}, [game, board, gameEval]);
if (!gameMoves?.length) return null;
return (
<Grid
container
justifyContent="center"
alignItems="start"
gap={0.7}
gap={0.5}
paddingY={1}
sx={{ scrollbarWidth: "thin", overflowY: "auto" }}
height="100%"
maxHeight="100%"
size={6}
id="moves-panel"
>

View File

@@ -23,6 +23,7 @@ export default function MoveItem({
moveColor,
}: Props) {
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const { goToMove } = useChessActions(boardAtom);
const position = useAtomValue(currentPositionAtom);
const color = getMoveColor(moveClassification);
@@ -42,7 +43,8 @@ export default function MoveItem({
const handleClick = () => {
if (isCurrentMove) return;
goToMove(moveIdx, game);
const gameToUse = game.moveNumber() > 1 ? game : board;
goToMove(moveIdx, gameToUse);
};
return (
@@ -82,7 +84,11 @@ export default function MoveItem({
/>
)}
<PrettyMoveSan san={san} color={moveColor} />
<PrettyMoveSan
san={san}
color={moveColor}
typographyProps={{ fontSize: "0.9rem" }}
/>
</Grid>
);
}

View File

@@ -87,20 +87,17 @@ export default function GraphTab(props: GridProps) {
<Grid
container
justifyContent="center"
alignItems="start"
height="100%"
alignItems="center"
minHeight="min(10rem, 8vh)"
height={{ xs: "8rem", lg: "none" }}
maxHeight="10rem"
{...props}
sx={
props.hidden
? { display: "none" }
: { overflow: "hidden", overflowY: "auto", ...props.sx }
}
sx={props.hidden ? { display: "none" } : props.sx}
size={12}
>
<Box
width="max(35rem, 90%)"
maxWidth="100%"
height="min(8rem, 8vh)"
maxHeight="6rem"
height="100%"
width={{ xs: "100%", lg: "90%" }}
sx={{
backgroundColor: "#2e2e2e",
borderRadius: "15px",