fix: history scroll
This commit is contained in:
@@ -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;
|
||||
@@ -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={
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "budget chess.com",
|
||||
"name": "multiplayer chess",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user