feat : add board style options UI

This commit is contained in:
GuillaumeSD
2025-05-10 19:52:12 +02:00
parent 10935c72c5
commit 82d216dfb0
26 changed files with 153 additions and 56 deletions

View File

@@ -15,7 +15,6 @@ See the LICENSE file for a copy of the _GNU Affero General Public License_.
Files | Author(s) | License Files | Author(s) | License
--- | --- | --- --- | --- | ---
public/piece/horsey | cham, michael1241 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) public/piece/horsey | cham, michael1241 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
public/piece/mono | Thibault Duplessis and [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt)
public/piece/cburnett | [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt) public/piece/cburnett | [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt)
public/piece/chessnut | [Alexis Luengas](https://github.com/LexLuengas) | [Apache 2.0](https://github.com/LexLuengas/chessnut-pieces/blob/master/LICENSE.txt) public/piece/chessnut | [Alexis Luengas](https://github.com/LexLuengas) | [Apache 2.0](https://github.com/LexLuengas/chessnut-pieces/blob/master/LICENSE.txt)
public/piece/letter | [usolando](https://lichess.org/@/usolando) | AGPLv3+ public/piece/letter | [usolando](https://lichess.org/@/usolando) | AGPLv3+
@@ -38,7 +37,6 @@ public/piece/tatiana | sadsnake1 | [CC BY-NC-SA 4.0](https://creativecommons.org
public/piece/staunty | sadsnake1 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) public/piece/staunty | sadsnake1 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
public/piece/dubrovny | sadsnake1 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) public/piece/dubrovny | sadsnake1 | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
public/piece/anarcandy | [caderek](https://github.com/caderek) | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) public/piece/anarcandy | [caderek](https://github.com/caderek) | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
public/piece/disguised | danegraphics | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
public/piece/kiwen-suwi | [neverRare](https://github.com/neverRare) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) public/piece/kiwen-suwi | [neverRare](https://github.com/neverRare) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
public/piece/mpchess | [Maxime Chupin](https://github.com/chupinmaxime) | [GPL3v3+](https://www.gnu.org/licenses/quick-guide-gplv3.en.html) public/piece/mpchess | [Maxime Chupin](https://github.com/chupinmaxime) | [GPL3v3+](https://www.gnu.org/licenses/quick-guide-gplv3.en.html)
public/piece/cooke | [fejfar](https://github.com/fejfar) | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) public/piece/cooke | [fejfar](https://github.com/fejfar) | [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="170" stroke="#000" stroke-width="20"/></svg>

Before

Width:  |  Height:  |  Size: 135 B

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
b.svg

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="170" fill="#fff" stroke="#000" stroke-width="20"/></svg>

Before

Width:  |  Height:  |  Size: 147 B

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
w.svg

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><path fill="#888" stroke="#888" d="M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.354.49-2.323.47-3-.5 1.354-1.94 3-2 3-2zm6-4c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2zM25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z"/></svg>

Before

Width:  |  Height:  |  Size: 467 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><g fill="none" fill-rule="evenodd" stroke="#888" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M22.5 11.63V6"/><path fill="#888" d="M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5m-11 12c5.5 3.5 15.5 3.5 21 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-3.5-7.5-13-10.5-16-4-3 6 5 10 5 10z"/><path d="M20 8h5"/></g></svg>

Before

Width:  |  Height:  |  Size: 437 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><g fill="#888" fill-rule="evenodd" stroke="#888" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21"/><path d="M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3"/></g></svg>

Before

Width:  |  Height:  |  Size: 453 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><path fill="#888" stroke="#888" stroke-linecap="round" stroke-width="1.5" d="M22 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38-1.95 1.12-3.28 3.21-3.28 5.62 0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z"/></svg>

Before

Width:  |  Height:  |  Size: 402 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><g fill="#888" fill-rule="evenodd" stroke="#888" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><circle cx="6" cy="12" r="2.75"/><circle cx="14" cy="9" r="2.75"/><circle cx="22.5" cy="8" r="2.75"/><circle cx="31" cy="9" r="2.75"/><circle cx="39" cy="12" r="2.75"/><path d="M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1.5 2.5-1.5 2.5-1.5 1.5.5 2.5.5 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0zm0 0c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5z"/><path fill="none" d="M11 38.5a35 35 1 0 0 23 0"/></g></svg>

Before

Width:  |  Height:  |  Size: 699 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45"><path fill="#888" fill-rule="evenodd" stroke="#888" stroke-linejoin="round" stroke-width="1.5" d="M9 39h27v-3H9zm3.5-7 1.5-2.5h17l1.5 2.5zm-.5 4v-4h21v4zm2-6.5v-13h17v13zm0-13L11 14h23l-3 2.5zM11 14V9h4v2h5V9h5v2h5V9h4v5z"/></svg>

Before

Width:  |  Height:  |  Size: 290 B

View File

@@ -0,0 +1,56 @@
import { Piece } from "react-chessboard/dist/chessboard/types";
export const PIECE_CODES = [
"wP",
"wB",
"wN",
"wR",
"wQ",
"wK",
"bP",
"bB",
"bN",
"bR",
"bQ",
"bK",
] as const satisfies Piece[];
export const PIECE_SETS = [
"alpha",
"anarcandy",
"caliente",
"california",
"cardinal",
"cburnett",
"celtic",
"chess7",
"chessnut",
"companion",
"cooke",
"dubrovny",
"fantasy",
"firi",
"fresca",
"gioco",
"governor",
"horsey",
"icpieces",
"kiwen-suwi",
"kosal",
"leipzig",
"letter",
"maestro",
"merida",
"monarchy",
"mpchess",
"pirouetti",
"pixel",
"reillycraig",
"rhosgfx",
"riohacha",
"shapes",
"spatial",
"staunty",
"tatiana",
"xkcd",
] as const satisfies string[];

View File

@@ -5,7 +5,6 @@ import {
Arrow, Arrow,
CustomPieces, CustomPieces,
CustomSquareRenderer, CustomSquareRenderer,
Piece,
PromotionPieceOption, PromotionPieceOption,
Square, Square,
} from "react-chessboard/dist/chessboard/types"; } from "react-chessboard/dist/chessboard/types";
@@ -22,6 +21,7 @@ import PlayerHeader from "./playerHeader";
import Image from "next/image"; import Image from "next/image";
import { boardHueAtom, pieceSetAtom } from "./states"; import { boardHueAtom, pieceSetAtom } from "./states";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { PIECE_CODES } from "./constants";
export interface Props { export interface Props {
id: string; id: string;
@@ -233,7 +233,7 @@ export default function Board({
const customPieces = useMemo( const customPieces = useMemo(
() => () =>
pieceCodes.reduce<CustomPieces>((acc, piece) => { PIECE_CODES.reduce<CustomPieces>((acc, piece) => {
acc[piece] = ({ squareWidth }) => ( acc[piece] = ({ squareWidth }) => (
<Image <Image
src={`/piece/${pieceSet}/${piece}.svg`} src={`/piece/${pieceSet}/${piece}.svg`}
@@ -325,18 +325,3 @@ export default function Board({
</Grid> </Grid>
); );
} }
const pieceCodes = [
"wP",
"wB",
"wN",
"wR",
"wQ",
"wK",
"bP",
"bB",
"bN",
"bR",
"bQ",
"bK",
] as const satisfies Piece[];

View File

@@ -1,4 +1,8 @@
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
import { PIECE_SETS } from "./constants";
export const pieceSetAtom = atomWithStorage("pieceSet", "cburnett"); export const pieceSetAtom = atomWithStorage<(typeof PIECE_SETS)[number]>(
"pieceSet",
"maestro"
);
export const boardHueAtom = atomWithStorage("boardHue", 0); export const boardHueAtom = atomWithStorage("boardHue", 0);

View File

@@ -1,4 +1,9 @@
import { Grid2 as Grid, Slider as MuiSlider, Typography } from "@mui/material"; import {
Grid2 as Grid,
Slider as MuiSlider,
styled,
Typography,
} from "@mui/material";
export interface Props { export interface Props {
value: number; value: number;
@@ -28,11 +33,16 @@ export default function Slider({
alignItems="center" alignItems="center"
size={size ?? 11} size={size ?? 11}
> >
<Typography id={`input-${label}`} textAlign="left" width="100%"> <Typography
id={`input-${label}`}
textAlign="left"
width="100%"
variant="body2"
>
{step === 1 && marksFilter ? label : `${label}: ${value}`} {step === 1 && marksFilter ? label : `${label}: ${value}`}
</Typography> </Typography>
<MuiSlider <CustomSlider
min={min} min={min}
max={max} max={max}
marks={ marks={
@@ -52,3 +62,15 @@ export default function Slider({
</Grid> </Grid>
); );
} }
const CustomSlider = styled(MuiSlider)(() => ({
".MuiSlider-markLabel": {
fontSize: "0.8rem",
lineHeight: "0.8rem",
},
".MuiSlider-thumb": {
width: "18px",
height: "18px",
},
marginBottom: "1rem",
}));

View File

@@ -24,6 +24,9 @@ import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage";
import { useEffect } from "react"; import { useEffect } from "react";
import { isEngineSupported } from "@/lib/engine/shared"; import { isEngineSupported } from "@/lib/engine/shared";
import { Stockfish16_1 } from "@/lib/engine/stockfish16_1"; import { Stockfish16_1 } from "@/lib/engine/stockfish16_1";
import { useAtom } from "jotai";
import { boardHueAtom, pieceSetAtom } from "@/components/board/states";
import { PIECE_SETS } from "@/components/board/constants";
interface Props { interface Props {
open: boolean; open: boolean;
@@ -43,6 +46,8 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
"engine-name", "engine-name",
engineNameAtom engineNameAtom
); );
const [boardHue, setBoardHue] = useAtom(boardHueAtom);
const [pieceSet, setPieceSet] = useAtom(pieceSetAtom);
useEffect(() => { useEffect(() => {
if (!isEngineSupported(engineName)) { if (!isEngineSupported(engineName)) {
@@ -56,25 +61,33 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
return ( return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle marginY={1} variant="h5"> <DialogTitle variant="h5">Settings</DialogTitle>
Set engine parameters
</DialogTitle>
<DialogContent sx={{ paddingBottom: 0 }}> <DialogContent sx={{ paddingBottom: 0 }}>
<Typography>
Stockfish 17 Lite is the default engine if your device support its
requirements. It offers the best balance between speed and strength.
Stockfish 17 is the strongest engine available, note that it requires
a one time download of 75MB.
</Typography>
<Grid <Grid
marginTop={4}
container container
justifyContent="center" justifyContent="center"
alignItems="center" alignItems="center"
rowGap={3} spacing={3}
size={12} size={12}
> >
<Grid container justifyContent="center" size={12}> <Grid
container
justifyContent="center"
size={{ xs: 12, sm: 7, md: 8 }}
>
<Typography>
Stockfish 17 Lite is the default engine if your device support its
requirements. It offers the best balance between speed and
strength. Stockfish 17 is the strongest engine available, note
that it requires a one time download of 75MB.
</Typography>
</Grid>
<Grid
container
justifyContent="center"
size={{ xs: 12, sm: 5, md: 4 }}
>
<FormControl variant="outlined"> <FormControl variant="outlined">
<InputLabel id="dialog-select-label">Engine</InputLabel> <InputLabel id="dialog-select-label">Engine</InputLabel>
<Select <Select
@@ -119,9 +132,48 @@ export default function EngineSettingsDialog({ open, onClose }: Props) {
/> />
<ArrowOptions /> <ArrowOptions />
<Grid
container
justifyContent="center"
size={{ xs: 12, sm: 8, md: 9 }}
>
<Slider
label="Board hue"
value={boardHue}
setValue={setBoardHue}
min={0}
max={360}
/>
</Grid>
<Grid
container
justifyContent="center"
size={{ xs: 12, sm: 4, md: 3 }}
>
<FormControl variant="outlined">
<InputLabel id="dialog-select-label">Piece set</InputLabel>
<Select
labelId="dialog-select-label"
id="dialog-select"
displayEmpty
input={<OutlinedInput label="Piece set" />}
value={pieceSet}
onChange={(e) => setPieceSet(e.target.value)}
sx={{ width: 200, maxWidth: "100%" }}
>
{PIECE_SETS.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid> </Grid>
</DialogContent> </DialogContent>
<DialogActions sx={{ m: 2 }}> <DialogActions sx={{ m: 1 }}>
<Button variant="contained" onClick={onClose}> <Button variant="contained" onClick={onClose}>
Done Done
</Button> </Button>