Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
528d5a6
Sauvegarde avant rebase
May 17, 2025
7e55a89
Italian variations enhanced
May 18, 2025
dff2d0d
Italian variations
May 18, 2025
a36035c
small icon to indicate incorrect moves
May 19, 2025
89ea0f1
the reset button is now reseting the board
May 19, 2025
d31dbe7
Quick fix / enhancement
May 19, 2025
aad8c43
Quick fix / resizing the board
May 19, 2025
7785bcd
Quick fix / vertical scroll fixed
May 19, 2025
f162765
Quick fix : board position
May 19, 2025
6027d4b
comments translated in English
May 19, 2025
d0002d2
Skip variations button added
May 19, 2025
6df2f6f
repositioning of the buttons
May 19, 2025
14db82e
Fixes
May 19, 2025
4ce2ff8
mistake icon fixed
May 19, 2025
fbc6d74
Skip variations fixed + progression correctly stored
May 19, 2025
8e4f260
Comments translated in English
May 20, 2025
b1cc047
Evaluation bar added
May 20, 2025
2b4d82a
Positioning the chessboard on the left and the text on the right.
May 20, 2025
c1af03c
Ajustement du positionnement des objets.
May 21, 2025
f1780ba
Code cleaned
May 22, 2025
1a5d156
Code cleaned: Quick fix
May 22, 2025
419f0e8
Remove player avatars and names from board edges in Play and Opening …
May 22, 2025
cb24198
Major improvement to the code structure
May 25, 2025
1987344
code structure enhanced
May 25, 2025
95ad425
enhancement
May 30, 2025
a2c6f84
Added a page to choose the opening
May 30, 2025
96f0b98
fix: prevent LinearProgressBar from rendering at 0 value to avoid bug…
May 30, 2025
56776eb
Ui enhancement
May 31, 2025
9562db2
fr to en
May 31, 2025
ae40a2c
Merge branch 'GuillaumeSD:main' into Opening-trainer
Speedauge Jun 1, 2025
ab5b814
UI edit
Jun 1, 2025
e1b19a0
light mode fix
Jun 1, 2025
d069d81
correction du chemin d'importation pour italian.ts
Jul 4, 2025
175db35
Improve opening trainer logic and format components
Speedauge Jul 4, 2025
7f7ff56
fix : vertical scrolling
Speedauge Jul 4, 2025
0da3791
Amélioration de la mise en page du panneau de progression avec des aj…
Speedauge Jul 4, 2025
0d7435a
fix : UI improvement
Speedauge Jul 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions src/components/LinearProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ import {
linearProgressClasses,
} from "@mui/material";

const LinearProgressBar = (
props: LinearProgressProps & { value: number; label: string }
) => {
if (props.value === 0) return null;
/**
* A styled linear progress bar with optional label and percentage display.
*/
interface LinearProgressBarProps extends LinearProgressProps {
value: number;
label: string;
}

function LinearProgressBar({ value, label, ...rest }: LinearProgressBarProps) {
// Allow rendering if label === "" (OpeningProgress case), otherwise hide if value === 0
if (value === 0 && label !== "") return null;
return (
<Grid
container
Expand All @@ -22,13 +28,17 @@ const LinearProgressBar = (
columnGap={2}
size={12}
>
<Typography variant="caption" align="center">
{props.label}
<Typography variant="caption" align="center" aria-label="progress-label">
{label}
</Typography>
<Grid sx={{ width: "100%" }}>
<LinearProgress
variant="determinate"
{...props}
value={value}
aria-valuenow={value}
aria-valuemax={100}
aria-label={label}
{...rest}
sx={(theme) => ({
borderRadius: "5px",
height: "5px",
Expand All @@ -45,11 +55,11 @@ const LinearProgressBar = (
</Grid>
<Grid>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value
value
)}%`}</Typography>
</Grid>
</Grid>
);
};
}

export default LinearProgressBar;
50 changes: 50 additions & 0 deletions src/components/OpeningControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button, Stack } from "@mui/material";
import { memo } from "react";

/**
* Control buttons for skipping or resetting the opening variation.
*/
export interface OpeningControlsProps {
moveIdx: number;
selectedVariationMovesLength: number;
allDone: boolean;
onSkip: () => void;
onReset: () => void;
disabled?: boolean;
}

function OpeningControls({
moveIdx,
selectedVariationMovesLength,
allDone,
onSkip,
onReset,
disabled = false,
}: OpeningControlsProps) {
return (
<Stack direction="row" spacing={2} sx={{ width: "100%" }}>
<Button
variant="outlined"
color="primary"
fullWidth
disabled={
moveIdx >= selectedVariationMovesLength || allDone || disabled
}
onClick={onSkip}
>
Skip variation
</Button>
<Button
variant="outlined"
color="primary"
fullWidth
onClick={onReset}
disabled={disabled}
>
Reset progress
</Button>
</Stack>
);
}

export default memo(OpeningControls);
60 changes: 60 additions & 0 deletions src/components/OpeningProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useEffect, useState, memo } from "react";
import LinearProgressBar from "./LinearProgressBar";
import { Box } from "@mui/material";
import { useTheme } from "@mui/material/styles";

// Props:
// - total: total number of variations
// - currentVariationIndex: index of the current variation (optional, for display)

/**
* Progress bar for opening training, showing completed variations out of total.
*/
export interface OpeningProgressProps {
total: number;
// List of completed variation indexes
completed: number[];
}

function OpeningProgress({ total, completed }: OpeningProgressProps) {
const [progress, setProgress] = useState<number[]>(completed);
const theme = useTheme();

useEffect(() => {
setProgress(completed);
}, [completed]);

// Calculate percentage
const percent = total > 0 ? (progress.length / total) * 100 : 0;
const label = `${progress.length} / ${total}`;

return (
<Box
width={{ xs: "100%", sm: 320, md: 340 }}
sx={{
mt: { xs: 2, md: 3 },
mb: { xs: 0, md: 1 },
px: { xs: 0, sm: 1 },
alignSelf: "flex-end",
position: "relative",
left: 0,
bottom: 0,
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 1,
}}
>
<Box minWidth={48}>
<span style={{ fontSize: 14, color: theme.palette.text.secondary }}>
{label}
</span>
</Box>
<Box flex={1} minWidth={0}>
<LinearProgressBar value={percent} label={""} />
</Box>
</Box>
);
}

export default memo(OpeningProgress);
70 changes: 70 additions & 0 deletions src/components/VariationHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Typography, Stack, Button } from "@mui/material";
import { memo } from "react";

/**
* Header for the opening variation panel.
*/
export interface VariationHeaderProps {
variationName?: string;
trainingMode: boolean;
onSetTrainingMode: (training: boolean) => void;
variationComplete: boolean;
}

const VariationHeader: React.FC<VariationHeaderProps> = ({
variationName,
trainingMode,
onSetTrainingMode,
variationComplete,
}) => (
<>
<Typography
variant="h4"
gutterBottom
sx={{
mb: 2,
wordBreak: "break-word",
textAlign: "center",
width: "100%",
}}
>
{variationName}
</Typography>

<Stack
direction="row"
spacing={2}
sx={{ mb: 3, justifyContent: "center", width: "100%" }}
>
<Button
variant={trainingMode ? "contained" : "outlined"}
onClick={() => onSetTrainingMode(true)}
fullWidth
>
Training Mode
</Button>
<Button
variant={!trainingMode ? "contained" : "outlined"}
onClick={() => onSetTrainingMode(false)}
fullWidth
>
Learning Mode
</Button>
</Stack>
{variationComplete ? (
<Typography color="success.main" sx={{ mb: 2, textAlign: "center" }}>
Variation complete! Next variation loading…
</Typography>
) : trainingMode ? (
<Typography color="text.secondary" sx={{ mb: 2, textAlign: "center" }}>
Play the correct move to continue.
</Typography>
) : (
<Typography color="text.secondary" sx={{ mb: 2, textAlign: "center" }}>
Play the move indicated by the arrow.
</Typography>
)}
</>
);

export default memo(VariationHeader);
61 changes: 47 additions & 14 deletions src/components/board/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import PlayerHeader from "./playerHeader";
import { boardHueAtom, pieceSetAtom } from "./states";
import tinycolor from "tinycolor2";

export interface TrainingFeedback {
square: string; // ex: 'e4'
icon: string; // chemin de l'icône
alt: string; // texte alternatif
}

export interface Props {
id: string;
canPlay?: Color | boolean;
Expand All @@ -34,6 +40,9 @@ export interface Props {
showBestMoveArrow?: boolean;
showPlayerMoveIconAtom?: PrimitiveAtom<boolean>;
showEvaluationBar?: boolean;
trainingFeedback?: TrainingFeedback;
bestMoveUci?: string;
hidePlayerHeaders?: boolean;
}

export default function Board({
Expand All @@ -48,6 +57,9 @@ export default function Board({
showBestMoveArrow = false,
showPlayerMoveIconAtom,
showEvaluationBar = false,
trainingFeedback,
bestMoveUci,
hidePlayerHeaders = false,
}: Props) {
const boardRef = useRef<HTMLDivElement>(null);
const game = useAtomValue(gameAtom);
Expand Down Expand Up @@ -208,9 +220,21 @@ export default function Board({
);

const customArrows: Arrow[] = useMemo(() => {
if (bestMoveUci && showBestMoveArrow) {
// Priorité à la flèche d'ouverture
return [
[
bestMoveUci.slice(0, 2),
bestMoveUci.slice(2, 4),
tinycolor(CLASSIFICATION_COLORS[MoveClassification.Best])
.spin(-boardHue)
.toHexString(),
] as Arrow,
];
}
// Fallback moteur
const bestMove = position?.lastEval?.bestMove;
const moveClassification = position?.eval?.moveClassification;

if (
bestMove &&
showBestMoveArrow &&
Expand All @@ -226,25 +250,25 @@ export default function Board({
.spin(-boardHue)
.toHexString(),
] as Arrow;

return [bestMoveArrow];
}

return [];
}, [position, showBestMoveArrow, boardHue]);
}, [bestMoveUci, position, showBestMoveArrow, boardHue]);

const SquareRenderer: CustomSquareRenderer = useMemo(() => {
return getSquareRenderer({
currentPositionAtom: currentPositionAtom,
clickedSquaresAtom,
playableSquaresAtom,
showPlayerMoveIconAtom,
trainingFeedback, // nouvelle prop transmise
});
}, [
currentPositionAtom,
clickedSquaresAtom,
playableSquaresAtom,
showPlayerMoveIconAtom,
trainingFeedback,
]);

const customPieces = useMemo(
Expand Down Expand Up @@ -306,11 +330,16 @@ export default function Board({
paddingLeft={showEvaluationBar ? 2 : 0}
size="grow"
>
<PlayerHeader
color={boardOrientation === Color.White ? Color.Black : Color.White}
gameAtom={gameAtom}
player={boardOrientation === Color.White ? blackPlayer : whitePlayer}
/>
{/* Enlève l'affichage des PlayerHeader si hidePlayerHeaders est true */}
{!hidePlayerHeaders && (
<PlayerHeader
color={boardOrientation === Color.White ? Color.Black : Color.White}
gameAtom={gameAtom}
player={
boardOrientation === Color.White ? blackPlayer : whitePlayer
}
/>
)}

<Grid
container
Expand Down Expand Up @@ -342,11 +371,15 @@ export default function Board({
/>
</Grid>

<PlayerHeader
color={boardOrientation}
gameAtom={gameAtom}
player={boardOrientation === Color.White ? whitePlayer : blackPlayer}
/>
{!hidePlayerHeaders && (
<PlayerHeader
color={boardOrientation}
gameAtom={gameAtom}
player={
boardOrientation === Color.White ? whitePlayer : blackPlayer
}
/>
)}
</Grid>
</Grid>
);
Expand Down
Loading