feat : mui & state refacto
This commit is contained in:
28
src/sections/gameReport/board.tsx
Normal file
28
src/sections/gameReport/board.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Grid, Typography } from "@mui/material";
|
||||
import { Chessboard } from "react-chessboard";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom } from "./states";
|
||||
|
||||
export default function Board() {
|
||||
const board = useAtomValue(boardAtom);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
rowGap={2}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
xs={12}
|
||||
md={6}
|
||||
>
|
||||
<Typography variant="h4" align="center">
|
||||
White Player (?)
|
||||
</Typography>
|
||||
<Chessboard id="BasicBoard" position={board.fen()} />
|
||||
<Typography variant="h4" align="center">
|
||||
Black Player (?)
|
||||
</Typography>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
66
src/sections/gameReport/reviewPanelBody.tsx
Normal file
66
src/sections/gameReport/reviewPanelBody.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import {
|
||||
Divider,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, gameEvalAtom } from "./states";
|
||||
|
||||
export default function ReviewPanelBody() {
|
||||
const board = useAtomValue(boardAtom);
|
||||
const gameEval = useAtomValue(gameEvalAtom);
|
||||
if (!gameEval) return null;
|
||||
|
||||
const evalIndex = board.history().length;
|
||||
const moveEval = gameEval.moves[evalIndex];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ width: "90%", marginY: 3 }} />
|
||||
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
columnGap={1}
|
||||
>
|
||||
<Icon
|
||||
icon="pepicons-pop:star-filled-circle"
|
||||
color="#27f019"
|
||||
height={30}
|
||||
/>
|
||||
<Typography variant="h5" align="center">
|
||||
Bilan de la partie
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Typography variant="h6" align="center">
|
||||
{moveEval ? `${moveEval.bestMove} is the best move` : "Game is over"}
|
||||
</Typography>
|
||||
|
||||
<Grid item container xs={12} justifyContent="center" alignItems="center">
|
||||
<List>
|
||||
{moveEval?.lines.map((line) => (
|
||||
<ListItem disablePadding key={line.pv[0]}>
|
||||
<ListItemText
|
||||
primary={
|
||||
line.cp !== undefined
|
||||
? line.cp / 100
|
||||
: `Mate in ${Math.abs(line.mate ?? 0)}`
|
||||
}
|
||||
sx={{ marginRight: 2 }}
|
||||
/>
|
||||
<Typography>{line.pv.slice(0, 7).join(", ")}</Typography>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
58
src/sections/gameReport/reviewPanelHeader.tsx
Normal file
58
src/sections/gameReport/reviewPanelHeader.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import SelectGameOrigin from "./selectGame/selectGameOrigin";
|
||||
import { Stockfish } from "@/lib/engine/stockfish";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { boardAtom, gameAtom, gameEvalAtom } from "./states";
|
||||
import { useChessActions } from "@/hooks/useChess";
|
||||
import { gameInputPgnAtom } from "./selectGame/gameInput.states";
|
||||
import { pgnToFens } from "@/lib/chess";
|
||||
|
||||
export default function ReviewPanelHeader() {
|
||||
const [engine, setEngine] = useState<Stockfish | null>(null);
|
||||
const setEval = useSetAtom(gameEvalAtom);
|
||||
const boardActions = useChessActions(boardAtom);
|
||||
const gameActions = useChessActions(gameAtom);
|
||||
const pgnInput = useAtomValue(gameInputPgnAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const engine = new Stockfish();
|
||||
engine.init();
|
||||
setEngine(engine);
|
||||
|
||||
return () => {
|
||||
engine.shutdown();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAnalyse = async () => {
|
||||
boardActions.reset();
|
||||
gameActions.setPgn(pgnInput);
|
||||
const gameFens = pgnToFens(pgnInput);
|
||||
if (engine?.isReady() && gameFens.length) {
|
||||
const newGameEval = await engine.evaluateGame(gameFens);
|
||||
setEval(newGameEval);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon icon="ph:file-magnifying-glass-fill" height={40} />
|
||||
<Typography variant="h4" align="center">
|
||||
Game Report
|
||||
</Typography>
|
||||
|
||||
<SelectGameOrigin />
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={<Icon icon="streamline:magnifying-glass-solid" />}
|
||||
onClick={handleAnalyse}
|
||||
>
|
||||
Analyse
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
51
src/sections/gameReport/reviewPanelToolbar.tsx
Normal file
51
src/sections/gameReport/reviewPanelToolbar.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Divider, Grid, IconButton } from "@mui/material";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardAtom, gameAtom } from "./states";
|
||||
import { useChessActions } from "@/hooks/useChess";
|
||||
|
||||
export default function ReviewPanelToolBar() {
|
||||
const game = useAtomValue(gameAtom);
|
||||
const board = useAtomValue(boardAtom);
|
||||
const boardActions = useChessActions(boardAtom);
|
||||
|
||||
const addNextMoveToGame = () => {
|
||||
const nextMoveIndex = board.history().length;
|
||||
const nextMove = game.history({ verbose: true })[nextMoveIndex];
|
||||
|
||||
if (nextMove) {
|
||||
boardActions.move({
|
||||
from: nextMove.from,
|
||||
to: nextMove.to,
|
||||
promotion: nextMove.promotion,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ width: "90%", marginY: 3 }} />
|
||||
|
||||
<Grid container item justifyContent="center" alignItems="center" xs={12}>
|
||||
<IconButton>
|
||||
<Icon icon="eva:flip-fill" />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => boardActions.reset()}>
|
||||
<Icon icon="ri:skip-back-line" />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => boardActions.undo()}>
|
||||
<Icon icon="ri:arrow-left-s-line" height={30} />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => addNextMoveToGame()}>
|
||||
<Icon icon="ri:arrow-right-s-line" height={30} />
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
<Icon icon="ri:skip-forward-line" />
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
<Icon icon="ri:save-3-line" />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
3
src/sections/gameReport/selectGame/gameInput.states.ts
Normal file
3
src/sections/gameReport/selectGame/gameInput.states.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const gameInputPgnAtom = atom("");
|
||||
19
src/sections/gameReport/selectGame/inputGame.tsx
Normal file
19
src/sections/gameReport/selectGame/inputGame.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { TextField } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { gameInputPgnAtom } from "./gameInput.states";
|
||||
|
||||
export default function InputGame() {
|
||||
const [pgn, setPgn] = useAtom(gameInputPgnAtom);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Enter PGN here..."
|
||||
sx={{ marginX: 4 }}
|
||||
value={pgn}
|
||||
onChange={(e) => {
|
||||
setPgn(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
46
src/sections/gameReport/selectGame/selectGameOrigin.tsx
Normal file
46
src/sections/gameReport/selectGame/selectGameOrigin.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { GameOrigin } from "@/types/enums";
|
||||
import { FormControl, Grid, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import InputGame from "./inputGame";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function SelectGameOrigin() {
|
||||
const [gameOrigin, setGameOrigin] = useState<GameOrigin>(GameOrigin.Pgn);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
rowGap={1}
|
||||
>
|
||||
<FormControl sx={{ m: 1, minWidth: 150 }}>
|
||||
<InputLabel id="game-origin-select-label">Game Origin</InputLabel>
|
||||
<Select
|
||||
labelId="game-origin-select-label"
|
||||
id="game-origin-select"
|
||||
label="Game Origin"
|
||||
autoWidth
|
||||
value={gameOrigin}
|
||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
||||
>
|
||||
{Object.values(GameOrigin).map((origin) => (
|
||||
<MenuItem key={origin} value={origin}>
|
||||
{gameOriginLabel[origin]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<InputGame />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
const gameOriginLabel: Record<GameOrigin, string> = {
|
||||
[GameOrigin.Pgn]: "PGN",
|
||||
[GameOrigin.ChessCom]: "Chess.com",
|
||||
[GameOrigin.Lichess]: "Lichess",
|
||||
[GameOrigin.Json]: "JSON",
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { initPgn } from "@/lib/chess";
|
||||
import { GameEval } from "@/types/eval";
|
||||
import { Chess } from "chess.js";
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const gameEvalAtom = atom<GameEval | undefined>(undefined);
|
||||
export const gamePgnAtom = atom(initPgn);
|
||||
export const boardPgnAtom = atom(initPgn);
|
||||
export const gameAtom = atom(new Chess());
|
||||
export const boardAtom = atom(new Chess());
|
||||
@@ -1,42 +0,0 @@
|
||||
import { drawBoard } from "@/lib/board";
|
||||
import { drawEvaluationBar } from "@/lib/evalBar";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { boardPgnAtom } from "./index.state";
|
||||
import { getLastFen } from "@/lib/chess";
|
||||
|
||||
export default function Board() {
|
||||
const boardRef = useRef<HTMLCanvasElement>(null);
|
||||
const evalBarRef = useRef<HTMLCanvasElement>(null);
|
||||
const boardPgn = useAtomValue(boardPgnAtom);
|
||||
const boardFen = getLastFen(boardPgn);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = boardRef.current?.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
drawBoard(ctx, boardFen);
|
||||
|
||||
const evalCtx = evalBarRef.current?.getContext("2d");
|
||||
if (!evalCtx) return;
|
||||
drawEvaluationBar(evalCtx);
|
||||
}, [boardFen]);
|
||||
|
||||
return (
|
||||
<div id="board-outer-container" className="center">
|
||||
<canvas id="evaluation-bar" width="30" height="720" ref={evalBarRef} />
|
||||
|
||||
<div id="board-inner-container" className="center">
|
||||
<div id="top-player-profile" className="profile">
|
||||
Black Player (?)
|
||||
</div>
|
||||
|
||||
<canvas id="board" width="720" height="720" ref={boardRef} />
|
||||
|
||||
<div id="bottom-player-profile" className="profile">
|
||||
White Player (?)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import ReviewResult from "./reviewResult";
|
||||
import SelectDepth from "./selectDepth";
|
||||
import SelectGameOrigin from "./selectGame/selectGameOrigin";
|
||||
import { Stockfish } from "@/lib/engine/stockfish";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { boardPgnAtom, gameEvalAtom, gamePgnAtom } from "./index.state";
|
||||
import { getGameFens, initPgn } from "@/lib/chess";
|
||||
|
||||
export default function ReviewPanelBody() {
|
||||
const [engine, setEngine] = useState<Stockfish | null>(null);
|
||||
const setGameEval = useSetAtom(gameEvalAtom);
|
||||
const setBoardPgn = useSetAtom(boardPgnAtom);
|
||||
const gamePgn = useAtomValue(gamePgnAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const engine = new Stockfish();
|
||||
engine.init();
|
||||
setEngine(engine);
|
||||
|
||||
return () => {
|
||||
engine.shutdown();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAnalyse = async () => {
|
||||
setBoardPgn(initPgn);
|
||||
const gameFens = getGameFens(gamePgn);
|
||||
if (engine?.isReady() && gameFens.length) {
|
||||
const newGameEval = await engine.evaluateGame(gameFens);
|
||||
setGameEval(newGameEval);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="review-panel-main">
|
||||
<h1 id="review-panel-title" className="white">
|
||||
📑 Game Report
|
||||
</h1>
|
||||
|
||||
<SelectGameOrigin />
|
||||
|
||||
<button id="review-button" className="std-btn success-btn white">
|
||||
<img src="analysis_icon.png" height="25" />
|
||||
<b onClick={handleAnalyse}>Analyse</b>
|
||||
</button>
|
||||
|
||||
<SelectDepth />
|
||||
|
||||
{false && <progress id="evaluation-progress-bar" max="100" />}
|
||||
|
||||
<b id="status-message" />
|
||||
|
||||
<b id="secondary-message" className="white" />
|
||||
|
||||
<ReviewResult />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { boardPgnAtom, gamePgnAtom } from "./index.state";
|
||||
import { addNextMove, initPgn, undoLastMove } from "@/lib/chess";
|
||||
|
||||
export default function ReviewPanelToolBar() {
|
||||
const [boardPgn, setBoardPgn] = useAtom(boardPgnAtom);
|
||||
const gamePgn = useAtomValue(gamePgnAtom);
|
||||
|
||||
return (
|
||||
<div id="review-panel-toolbar">
|
||||
<div id="review-panel-toolbar-buttons" className="center">
|
||||
<img
|
||||
id="flip-board-button"
|
||||
src="flip.png"
|
||||
alt="Flip Board"
|
||||
title="Flip board"
|
||||
/>
|
||||
<img
|
||||
id="back-start-move-button"
|
||||
src="back_to_start.png"
|
||||
alt="Back to start"
|
||||
title="Back to start"
|
||||
onClick={() => setBoardPgn(initPgn)}
|
||||
/>
|
||||
<img
|
||||
id="back-move-button"
|
||||
src="back.png"
|
||||
alt="Back"
|
||||
title="Back"
|
||||
onClick={() => {
|
||||
setBoardPgn(undoLastMove(boardPgn));
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
id="next-move-button"
|
||||
src="next.png"
|
||||
alt="Next"
|
||||
title="Next"
|
||||
onClick={() => {
|
||||
const nextBoardPgn = addNextMove(boardPgn, gamePgn);
|
||||
setBoardPgn(nextBoardPgn);
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
id="go-end-move-button"
|
||||
src="go_to_end.png"
|
||||
alt="Go to end"
|
||||
title="Go to end"
|
||||
/>
|
||||
<img
|
||||
id="save-analysis-button"
|
||||
src="save.png"
|
||||
alt="Save analysis"
|
||||
title="Save analysis"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="white" style={{ marginBottom: "10px" }}>
|
||||
<input
|
||||
id="suggestion-arrows-setting"
|
||||
type="checkbox"
|
||||
style={{ marginRight: "0.4rem" }}
|
||||
/>
|
||||
<span>Suggestion Arrows</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useAtomValue } from "jotai";
|
||||
import { boardPgnAtom, gameEvalAtom } from "./index.state";
|
||||
import { getNextMoveIndex } from "@/lib/chess";
|
||||
|
||||
export default function ReviewResult() {
|
||||
const boardPgn = useAtomValue(boardPgnAtom);
|
||||
const gameEval = useAtomValue(gameEvalAtom);
|
||||
if (!gameEval) return null;
|
||||
|
||||
const evalIndex = getNextMoveIndex(boardPgn);
|
||||
const moveEval = gameEval.moves[evalIndex];
|
||||
|
||||
return (
|
||||
<div id="report-cards">
|
||||
<h2 id="accuracies-title" className="white">
|
||||
Accuracies
|
||||
</h2>
|
||||
<div id="accuracies">
|
||||
<b id="white-accuracy">{gameEval.whiteAccuracy.toFixed(1)}%</b>
|
||||
<b id="black-accuracy">{gameEval.blackAccuracy.toFixed(1)}%</b>
|
||||
</div>
|
||||
|
||||
<div id="classification-message-container">
|
||||
<img id="classification-icon" src="book.png" height="25" />
|
||||
<b id="classification-message" />
|
||||
</div>
|
||||
|
||||
<b id="top-alternative-message">
|
||||
{moveEval ? `${moveEval.bestMove} is best` : "Game is over"}
|
||||
</b>
|
||||
|
||||
<div id="engine-suggestions">
|
||||
<h2 id="engine-suggestions-title" className="white">
|
||||
Engine
|
||||
</h2>
|
||||
{moveEval?.lines.map((line) => (
|
||||
<div key={line.pv[0]} style={{ color: "white" }}>
|
||||
<span style={{ marginRight: "2em" }}>
|
||||
{line.cp !== undefined
|
||||
? line.cp / 100
|
||||
: `Mate in ${Math.abs(line.mate ?? 0)}`}
|
||||
</span>
|
||||
<span>{line.pv.slice(0, 7).join(", ")}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span id="opening-name" className="white" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export default function SelectDepth() {
|
||||
return (
|
||||
<>
|
||||
<b className="white">⚙️ Search depth</b>
|
||||
<div id="depth-slider-container">
|
||||
<input
|
||||
id="depth-slider"
|
||||
type="range"
|
||||
min="14"
|
||||
max="20"
|
||||
defaultValue="16"
|
||||
/>
|
||||
<span id="depth-counter" className="white">
|
||||
16 🐇
|
||||
</span>
|
||||
</div>
|
||||
<h6 id="depth-message" className="white">
|
||||
Lower depths recommended for slower devices.
|
||||
</h6>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { GameOrigin } from "@/types/enums";
|
||||
import { useAtom } from "jotai";
|
||||
import { gamePgnAtom } from "../index.state";
|
||||
|
||||
interface Props {
|
||||
gameOrigin: GameOrigin;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export default function InputGame({ placeholder }: Props) {
|
||||
const [gamePgn, setGamePgn] = useAtom(gamePgnAtom);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
id="pgn"
|
||||
className="white"
|
||||
cols={30}
|
||||
rows={10}
|
||||
spellCheck="false"
|
||||
placeholder={placeholder}
|
||||
value={gamePgn}
|
||||
onChange={(e) => {
|
||||
setGamePgn(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { GameOrigin } from "@/types/enums";
|
||||
|
||||
interface Props {
|
||||
gameOrigin: GameOrigin;
|
||||
}
|
||||
|
||||
export default function SelectGameAccount({}: Props) {
|
||||
return (
|
||||
<div id="chess-site-username-container">
|
||||
<textarea
|
||||
id="chess-site-username"
|
||||
className="white"
|
||||
spellCheck="false"
|
||||
maxLength={48}
|
||||
placeholder="Username..."
|
||||
/>
|
||||
<button id="fetch-account-games-button" className="std-btn success-btn">
|
||||
<img src="next.png" alt=">" height="25" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import InputGame from "./inputGame";
|
||||
import SelectGameAccount from "./selectGameAccount";
|
||||
import { GameOrigin } from "@/types/enums";
|
||||
|
||||
export default function SelectGameOrigin() {
|
||||
const [gameOrigin, setGameOrigin] = useState(GameOrigin.Pgn);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="load-type-dropdown-container" className="white">
|
||||
<span style={{ marginRight: "0.3rem" }}>Load game from</span>
|
||||
<select
|
||||
id="load-type-dropdown"
|
||||
value={gameOrigin}
|
||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
||||
>
|
||||
{Object.values(GameOrigin).map((origin) => (
|
||||
<option key={origin} value={origin}>
|
||||
{gameOriginLabel[origin]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{renderSelectGameInfo(gameOrigin)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const gameOriginLabel: Record<GameOrigin, string> = {
|
||||
[GameOrigin.Pgn]: "PGN",
|
||||
[GameOrigin.ChessCom]: "Chess.com",
|
||||
[GameOrigin.Lichess]: "Lichess",
|
||||
[GameOrigin.Json]: "JSON",
|
||||
};
|
||||
|
||||
const renderSelectGameInfo = (gameOrigin: GameOrigin) => {
|
||||
switch (gameOrigin) {
|
||||
case GameOrigin.Pgn:
|
||||
return (
|
||||
<InputGame gameOrigin={gameOrigin} placeholder="Enter PGN here..." />
|
||||
);
|
||||
case GameOrigin.Json:
|
||||
return (
|
||||
<InputGame gameOrigin={gameOrigin} placeholder="Enter JSON here..." />
|
||||
);
|
||||
default:
|
||||
return <SelectGameAccount gameOrigin={gameOrigin} />;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export default function TopBar() {
|
||||
return (
|
||||
<div id="announcement">
|
||||
<b>Welcome ❤️</b>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
src/sections/layout/NavBar.tsx
Normal file
67
src/sections/layout/NavBar.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Box from "@mui/material/Box";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { useEffect, useState } from "react";
|
||||
import NavMenu from "./NavMenu";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { useRouter } from "next/router";
|
||||
import NavLink from "@/components/NavLink";
|
||||
|
||||
interface Props {
|
||||
darkMode: boolean;
|
||||
switchDarkMode: () => void;
|
||||
}
|
||||
|
||||
export default function NavBar({ darkMode, switchDarkMode }: Props) {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
setDrawerOpen(false);
|
||||
}, [router.pathname]);
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, display: "flex" }}>
|
||||
<AppBar
|
||||
position="static"
|
||||
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ mr: 2 }}
|
||||
onClick={() => setDrawerOpen((val) => !val)}
|
||||
>
|
||||
<Icon icon="mdi:menu" />
|
||||
</IconButton>
|
||||
<NavLink href="/">
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Free Chess
|
||||
</Typography>
|
||||
</NavLink>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() =>
|
||||
window.open("https://github.com/GuillaumeSD/freechess")
|
||||
}
|
||||
>
|
||||
<Icon icon="mdi:github" />
|
||||
</IconButton>
|
||||
<IconButton sx={{ ml: 1 }} onClick={switchDarkMode} color="inherit">
|
||||
{darkMode ? (
|
||||
<Icon icon="mdi:brightness-7" />
|
||||
) : (
|
||||
<Icon icon="mdi:brightness-4" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<NavMenu open={drawerOpen} onClose={() => setDrawerOpen(false)} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
45
src/sections/layout/NavMenu.tsx
Normal file
45
src/sections/layout/NavMenu.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import NavLink from "@/components/NavLink";
|
||||
import { Icon } from "@iconify/react";
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Toolbar,
|
||||
} from "@mui/material";
|
||||
|
||||
const MenuOptions = [
|
||||
{ text: "Game Report", icon: "streamline:magnifying-glass-solid", href: "/" },
|
||||
];
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function NavMenu({ open, onClose }: Props) {
|
||||
return (
|
||||
<Drawer anchor="left" open={open} onClose={onClose}>
|
||||
<Toolbar />
|
||||
<Box sx={{ width: 250 }}>
|
||||
<List>
|
||||
{MenuOptions.map(({ text, icon, href }) => (
|
||||
<ListItem key={text} disablePadding>
|
||||
<NavLink href={href}>
|
||||
<ListItemButton onClick={onClose}>
|
||||
<ListItemIcon style={{ paddingLeft: "0.5em" }}>
|
||||
<Icon icon={icon} height="1.5em" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={text} />
|
||||
</ListItemButton>
|
||||
</NavLink>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
36
src/sections/layout/index.tsx
Normal file
36
src/sections/layout/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
|
||||
import { PropsWithChildren, useMemo } from "react";
|
||||
import NavBar from "./NavBar";
|
||||
import { red } from "@mui/material/colors";
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
|
||||
export default function Layout({ children }: PropsWithChildren) {
|
||||
const [useDarkMode, setDarkMode] = useLocalStorage("useDarkMode", true);
|
||||
|
||||
const theme = useMemo(
|
||||
() =>
|
||||
createTheme({
|
||||
palette: {
|
||||
mode: useDarkMode ? "dark" : "light",
|
||||
error: {
|
||||
main: red[400],
|
||||
},
|
||||
secondary: {
|
||||
main: useDarkMode ? "#424242" : "#90caf9",
|
||||
},
|
||||
},
|
||||
}),
|
||||
[useDarkMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<NavBar
|
||||
darkMode={useDarkMode}
|
||||
switchDarkMode={() => setDarkMode((val) => !val)}
|
||||
/>
|
||||
<main style={{ margin: "2em 2vw" }}>{children}</main>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user