fix: history scroll

This commit is contained in:
Cozma Rares
2023-08-05 12:56:08 +03:00
parent abda45d393
commit 41b99e91b6
10 changed files with 106 additions and 65 deletions
+15
View File
@@ -0,0 +1,15 @@
import InferProps from "../utils/InferProps";
import ChessBoard from "./Chessboard";
import History from "./History";
// TODO: fix border
const ChessUI: React.FC<InferProps<[typeof ChessBoard, typeof History]>> = (
props
) => (
<div className="max-h-[80vmin] m-4 flex flex-row gap-4 justify-center">
<ChessBoard {...props} />
<History {...props} />
</div>
);
export default ChessUI;
+26 -31
View File
@@ -30,7 +30,6 @@ import Chess, {
} from "../../../server/src/engine";
import { MouseEventHandler, useState } from "react";
import Show from "../utils/Show";
import useWindowSize from "../hooks/useWindowSize";
const PIECES: Record<Color, Record<PieceType, string>> = {
w: { p: wp, n: wn, b: wb, r: wr, q: wq, k: wk },
@@ -42,14 +41,11 @@ const ChessBoard: React.FC<{
blackPerspective?: boolean;
disabled?: boolean;
}> = ({ chess, makeMove, blackPerspective, disabled }) => {
const { width, height } = useWindowSize();
const [activeTile, setActiveTile] = useState<number>(-1);
const [promotionMove, setPromotionMove] = useState<
(Pick<Move, "from" | "to"> & { color: Color }) | null
>(null);
const gridSize = Math.min(width, height, 800);
const tileProps = new Array(64).fill(null).map((_, i) => ({
tileNumber: i,
piece: chess.getPiece(i),
@@ -112,34 +108,32 @@ const ChessBoard: React.FC<{
setActiveTile(-1);
};
// FIX: border
return (
<>
<div className="relative w-fit h-fit max-w-full isolate border-[6px] border-black rounded-lg peer">
<div className="relative aspect-square isolate border-[6px] border-black rounded-lg">
<div
className="grid grid-rows-8 grid-cols-8 aspect-square select-none max-w-full"
className="grid grid-rows-8 grid-cols-8 aspect-square select-none max-h-full"
onClick={handleClick}
style={{ width: `${gridSize}px` }}
>
{blackPerspective == true ? tiles.reverse() : tiles}
{blackPerspective ? tiles.reverse() : tiles}
</div>
{promotionMove == null ? (
<></>
) : (
<>
<div className="absolute top-0 left-0 right-0 bottom-0 bg-red-50 bg-opacity-50 peer-default:pointer-events-none"></div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 grid grid-cols-2 grid-rows-2 gap-1 bg-black bg-opacity-80 rounded-2xl p-2 z-10">
{PIECE_PROMOTION.map((p) => (
<img
key={p}
src={PIECES[promotionMove.color][p]}
style={{ width: `${gridSize / 8}px`, aspectRatio: 1 }}
onClick={() => sendPromotionMove(p)}
/>
))}
</div>
</>
)}
</div>
{promotionMove && (
<>
<div className="absolute top-0 left-0 right-0 bottom-0 bg-red-50 bg-opacity-50 pointer-events-none z-10"></div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 grid grid-cols-2 grid-rows-2 gap-1 bg-black bg-opacity-80 rounded-2xl p-2 z-20">
{PIECE_PROMOTION.map((p) => (
<img
key={p}
className="cursor-pointer"
src={PIECES[promotionMove.color][p]}
onClick={() => sendPromotionMove(p)}
/>
))}
</div>
</>
)}
</>
);
};
@@ -178,14 +172,15 @@ const Tile: React.FC<{
return (
<div
className={
"relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none " +
(isActive ? activeColor : bgColor)
}
className={[
"relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none outline-blue-300 hover:outline outline-4 -outline-offset-4",
isActive ? activeColor : bgColor,
piece ? "cursor-pointer" : "",
].join(" ")}
data-tile={tileNumber}
>
{piece == null ? <></> : <img src={PIECES[piece.color][piece.type]} />}
<Show when={isAttacked == true}>
{piece && <img src={PIECES[piece.color][piece.type]} />}
<Show when={isAttacked}>
<Show
when={piece == null}
fallback={
+14 -4
View File
@@ -1,4 +1,4 @@
import { ReactNode } from "react";
import { ReactNode, useEffect, useRef } from "react";
import Chess from "../../../server/src/engine";
import { CaretLeft, CaretRight, Plus, Repeat } from "../components/icons";
@@ -9,6 +9,13 @@ const History: React.FC<{
undo?: () => void;
redo?: () => void;
}> = ({ chess, newGame, switchSides, undo, redo }) => {
const historyRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const div = historyRef.current as HTMLDivElement;
if (div) div.scrollTop = div.scrollHeight;
}, [chess.getHistory().length]);
const history: ReactNode[] = [];
chess.getHistory().forEach(({ san }, idx) => {
@@ -21,6 +28,7 @@ const History: React.FC<{
{idk}.
</span>
);
history.push(
<span key={idx + san} className={bg}>
{san}
@@ -39,11 +47,13 @@ const History: React.FC<{
return (
<div className="bg-zinc-800 rounded-lg text-white grid grid-rows-[auto,1fr,auto]">
<div className="text-xl text-center my-1 mx-4 border-b ">History</div>
{/* FIX: scroll*/}
<div className="p-4 min-w-[10rem] h-fit overflow-scroll grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md">
<div
ref={historyRef}
className="p-4 min-w-[13rem] h-fit max-h-full overflow-scroll grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md"
>
{history}
</div>
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg">
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg empty:hidden">
{newGame && (
<Button title="New Game" onClick={newGame}>
<Plus />
-5
View File
@@ -8,11 +8,6 @@
font-family: "Ubuntu Mono", monospace;
}
img {
max-width: 100%;
max-height: 100%;
}
.loading:after {
display: inline-block;
animation: dotty steps(1, end) 2s infinite;
+6 -3
View File
@@ -40,7 +40,10 @@ const router = createBrowserRouter([
// TODO: better UI
// React's StricMode doesn't play well with how I implemented the socket connection
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-zinc-500 -z-100">
<RouterProvider router={router} />
</div>
<>
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-zinc-200 -z-50"></div>
<div className="z-0">
<RouterProvider router={router} />
</div>
</>
);
+10 -16
View File
@@ -1,7 +1,6 @@
import { useState } from "react";
import Chess, { Move } from "../../../server/src/engine";
import ChessBoard from "../components/Chessboard";
import History from "../components/History";
import ChessUI from "../components/ChessUI";
const LocalGame = () => {
const [chess, setChess] = useState(Chess.load());
@@ -27,20 +26,15 @@ const LocalGame = () => {
};
return (
<div className="mt-4 flex flex-row gap-4 justify-center">
<ChessBoard
chess={chess}
makeMove={makeMove}
blackPerspective={blackPerspective}
/>
<History
chess={chess}
newGame={newGame}
switchSides={switchSides}
undo={undo}
redo={redo}
/>
</div>
<ChessUI
chess={chess}
makeMove={makeMove}
blackPerspective={blackPerspective}
newGame={newGame}
switchSides={switchSides}
undo={undo}
redo={redo}
/>
);
};
+2 -4
View File
@@ -1,6 +1,5 @@
import Chess, { COLOR, Move } from "../../../server/src/engine";
import { useState, useEffect } from "react";
import ChessBoard from "../components/Chessboard";
import { useLocation, useNavigate } from "react-router-dom";
import useCopyToClipboard from "../hooks/useCopyToClipboard";
@@ -9,6 +8,7 @@ import { CopyIcon } from "../components/icons";
import Show from "../utils/Show";
import ErrorNorification from "../components/ErrorNotification";
import Modal, { ModalButton } from "../components/Modal";
import ChessUI from "../components/ChessUI";
const Game = () => {
const navigate = useNavigate();
@@ -68,15 +68,13 @@ const Game = () => {
error={err?.message}
removeError={() => setErr(undefined)}
/>
{/* TODO: add taken pieces */}
<ChessBoard
<ChessUI
key={chess.getFEN()}
chess={chess}
makeMove={makeMove}
blackPerspective={color === COLOR.BLACK}
disabled={color !== chess.getTurn()}
/>
{/* TODO: add history */}
<Show when={opponentDisconnect || chess.isGameOver()}>
<Modal enableOverlay>
<div className="text-center w-50 max-w-full">
+31
View File
@@ -0,0 +1,31 @@
// NOTE: will work as long as React keeps the props as the first argument in the functional components
type ArgumentTypes<F extends Function> = F extends (
props: infer TT,
...args: any
) => any
? TT
: never;
type Head<T extends Array<Function>> = T extends [
infer TT extends Function,
...any
]
? TT
: never;
type Tail<T extends Array<Function>> = T extends [
any,
...infer TT extends Array<Function>
]
? TT
: never;
type UglyInferProps<T extends Array<Function>> = T extends [Function]
? ArgumentTypes<Head<T>>
: ArgumentTypes<Head<T>> & UglyInferProps<Tail<T>>;
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type InferProps<T extends Array<Function>> = Prettify<UglyInferProps<T>>;
export default InferProps;
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "budget chess.com",
"name": "multiplayer chess",
"version": "1.0.0",
"description": "",
"main": "index.js",
+1 -1
View File
@@ -881,7 +881,7 @@ export default class Chess {
san = pieceType + this._sanDisambiguator(move) + "x" + move.to;
else san = pieceType + this._sanDisambiguator(move) + move.to;
if (move.promotion) san += `= ${move.promotion.toUpperCase()} `;
if (move.promotion) san += `=${move.promotion.toUpperCase()}`;
const chess = Chess._load(this.getFEN(), false);
chess._makeMove(move);