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

@@ -54,7 +54,7 @@ export default function PrettyMoveSan({
</Typography>
)}
<Typography component="span" {...typographyProps}>
<Typography component="span" noWrap {...typographyProps}>
{text}
{additionalText}
</Typography>

View File

@@ -36,6 +36,7 @@ export default function GameAnalysis() {
const { reset: resetGame } = useChessActions(gameAtom);
const [gameEval, setGameEval] = useAtom(gameEvalAtom);
const game = useAtomValue(gameAtom);
const board = useAtomValue(boardAtom);
const setBoardOrientation = useSetAtom(boardOrientationAtom);
const router = useRouter();
@@ -50,12 +51,12 @@ export default function GameAnalysis() {
}
}, [gameId, setGameEval, setBoardOrientation, resetBoard, resetGame]);
const isGameLoaded = game.history().length > 0;
const showMovesTab = game.history().length > 0 || board.history().length > 0;
useEffect(() => {
if (tab === 1 && !isGameLoaded) setTab(0);
if (tab === 1 && !showMovesTab) setTab(0);
if (tab === 2 && !gameEval) setTab(0);
}, [isGameLoaded, gameEval, tab]);
}, [showMovesTab, gameEval, tab]);
return (
<Grid container gap={4} justifyContent="space-evenly" alignItems="start">
@@ -65,7 +66,7 @@ export default function GameAnalysis() {
<Grid
container
justifyContent="center"
justifyContent="start"
alignItems="center"
borderRadius={2}
border={1}
@@ -81,26 +82,24 @@ export default function GameAnalysis() {
maxWidth: "1200px",
}}
rowGap={2}
maxHeight={{ lg: "calc(95vh - 60px)", xs: "900px" }}
display="grid"
gridTemplateRows={
gameEval
? "repeat(2, auto) max-content fit-content(100%) fit-content(100%) auto"
: "repeat(2, auto) max-content fit-content(100%)"
}
height={{ xs: tab === 1 ? "40rem" : "auto", lg: "calc(95vh - 60px)" }}
display="flex"
flexDirection="column"
flexWrap="nowrap"
size={{
xs: 12,
lg: "grow",
}}
>
{isLgOrGreater ? (
<PanelHeader key="analysis-panel-header" />
<Box width="100%">
<PanelHeader key="analysis-panel-header" />
<Divider sx={{ marginX: "5%", marginTop: 2.5 }} />
</Box>
) : (
<PanelToolBar key="review-panel-toolbar" />
)}
{isLgOrGreater && <Divider sx={{ marginX: "5%" }} />}
{!isLgOrGreater && !gameEval && <Divider sx={{ marginX: "5%" }} />}
{!isLgOrGreater && !gameEval && (
<PanelHeader key="analysis-panel-header" />
@@ -108,6 +107,7 @@ export default function GameAnalysis() {
{!isLgOrGreater && (
<Box
width="95%"
sx={{
borderBottom: 1,
borderColor: "divider",
@@ -142,7 +142,7 @@ export default function GameAnalysis() {
sx={{
textTransform: "none",
minHeight: 15,
display: isGameLoaded ? undefined : "none",
display: showMovesTab ? undefined : "none",
padding: "5px 0em 12px",
}}
disableFocusRipple
@@ -177,24 +177,24 @@ export default function GameAnalysis() {
id="tabContent0"
/>
{isGameLoaded && (
<ClassificationTab
role="tabpanel"
hidden={tab !== 1 && !isLgOrGreater}
id="tabContent1"
/>
)}
<ClassificationTab
role="tabpanel"
hidden={tab !== 1 && !isLgOrGreater}
id="tabContent1"
/>
{isLgOrGreater && (
<Box>
<Box width="100%">
<Divider sx={{ marginX: "5%", marginBottom: 1.5 }} />
<PanelToolBar key="review-panel-toolbar" />
</Box>
)}
{!isLgOrGreater && gameEval && <Divider sx={{ marginX: "5%" }} />}
{!isLgOrGreater && gameEval && (
<PanelHeader key="analysis-panel-header" />
<Box width="100%">
<Divider sx={{ marginX: "5%", marginBottom: 2.5 }} />
<PanelHeader key="analysis-panel-header" />
</Box>
)}
</Grid>

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",