fix: coordinate letters, formatting
This commit is contained in:
@@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import Modal, { ModalButton } from "./Modal";
|
||||
import { useState } from "react";
|
||||
|
||||
const ChessUI: React.FC<InferProps<[typeof ChessBoard, typeof History]>> = (
|
||||
props
|
||||
props,
|
||||
) => {
|
||||
const [showModal, setShowModal] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -121,7 +121,7 @@ const ChessBoard: React.FC<{
|
||||
if (tile == activeTile) return setActiveTile(-1);
|
||||
|
||||
setActiveTile(
|
||||
chess.getMovesForSquare(algebraic(tile)).length == 0 ? -1 : tile
|
||||
chess.getMovesForSquare(algebraic(tile)).length == 0 ? -1 : tile,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -198,60 +198,62 @@ const Tile: React.FC<{
|
||||
blackPerspective,
|
||||
endGameIcon,
|
||||
}) => {
|
||||
const color = squareColor(tileNumber);
|
||||
const color = squareColor(tileNumber);
|
||||
|
||||
const { bg: bgColor, text: textColor } = TILE_COLORS[color];
|
||||
const activeColor = TILE_COLORS[color].active;
|
||||
const { bg: bgColor, text: textColor } = TILE_COLORS[color];
|
||||
const activeColor = TILE_COLORS[color].active;
|
||||
|
||||
const tileFile = file(tileNumber);
|
||||
const tileRank = rank(tileNumber);
|
||||
const square = algebraic(tileNumber);
|
||||
const tileFile = file(tileNumber);
|
||||
const tileRank = rank(tileNumber);
|
||||
const square = algebraic(tileNumber);
|
||||
|
||||
const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A);
|
||||
const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST);
|
||||
const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A);
|
||||
const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"relative aspect-square font-bold text-xl group [&>*]:pointer-events-none outline-blue-300 hover:outline outline-2 sm:outline-4 -outline-offset-2 sm:-outline-offset-4",
|
||||
isActive ? activeColor : bgColor,
|
||||
piece ? "cursor-pointer" : "",
|
||||
].join(" ")}
|
||||
data-tile={tileNumber}
|
||||
>
|
||||
{piece && (
|
||||
<img
|
||||
src={PIECES[piece.color][piece.type]}
|
||||
className="max-w-full max-h-full aspect-square"
|
||||
/>
|
||||
)}
|
||||
<Show when={endGameIcon != undefined}>
|
||||
<div className="absolute top-0 left-full -translate-x-1/2 -translate-y-1/2 bg-white z-10 rounded-full p-1 shadow-lg shadow-black aspect-square flex justify-center items-center">
|
||||
{endGameIcon}
|
||||
</div>
|
||||
if (isFirstColumn || isLastRow) console.log(square);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"relative isolate aspect-square font-bold text-xl group [&>*]:pointer-events-none outline-blue-300 hover:outline outline-2 sm:outline-4 -outline-offset-2 sm:-outline-offset-4",
|
||||
isActive ? activeColor : bgColor,
|
||||
piece ? "cursor-pointer" : "",
|
||||
].join(" ")}
|
||||
data-tile={tileNumber}
|
||||
>
|
||||
{piece && (
|
||||
<img
|
||||
src={PIECES[piece.color][piece.type]}
|
||||
className="max-w-full max-h-full aspect-square"
|
||||
/>
|
||||
)}
|
||||
<Show when={endGameIcon != undefined}>
|
||||
<div className="absolute top-0 left-full -translate-x-1/2 -translate-y-1/2 bg-white z-10 rounded-full p-1 shadow-lg shadow-black aspect-square flex justify-center items-center">
|
||||
{endGameIcon}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isAttacked}>
|
||||
<Show
|
||||
when={piece == null}
|
||||
fallback={
|
||||
<div className="absolute z-10 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
|
||||
}
|
||||
>
|
||||
<div className="absolute z-10 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
|
||||
</Show>
|
||||
<Show when={isAttacked}>
|
||||
<Show
|
||||
when={piece == null}
|
||||
fallback={
|
||||
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
|
||||
}
|
||||
>
|
||||
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={isFirstColumn}>
|
||||
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
|
||||
{square[1]}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isLastRow}>
|
||||
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
|
||||
{square[0]}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Show>
|
||||
<Show when={isFirstColumn}>
|
||||
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
|
||||
{square[1]}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isLastRow}>
|
||||
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
|
||||
{square[0]}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChessBoard;
|
||||
|
||||
@@ -16,7 +16,7 @@ const ErrorNorification: React.FC<{
|
||||
divRef.current.classList.add(
|
||||
"opacity-0",
|
||||
"transition-opacity",
|
||||
"duration-300"
|
||||
"duration-300",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -2,92 +2,92 @@ import { ReactNode, useEffect, useRef } from "react";
|
||||
import Chess from "../../../server/src/engine";
|
||||
|
||||
const History: React.FC<{
|
||||
chess: Chess;
|
||||
buttons?: Array<{ onClick: () => void; title: string; icon: ReactNode }>;
|
||||
chess: Chess;
|
||||
buttons?: Array<{ onClick: () => void; title: string; icon: ReactNode }>;
|
||||
}> = ({ chess, buttons }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const historyRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const historyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { currentPosition, history: moveHistory } = chess.getHistory();
|
||||
const { currentPosition, history: moveHistory } = chess.getHistory();
|
||||
|
||||
useEffect(() => {
|
||||
// HACK: couldn't find any other way for ChessBoard and History to have the same height
|
||||
setTimeout(() => {
|
||||
const chessboard = document.querySelector(
|
||||
"#chessboard"
|
||||
) as HTMLDivElement;
|
||||
const { height } = chessboard.getBoundingClientRect();
|
||||
const containerDiv = containerRef.current as HTMLDivElement;
|
||||
containerDiv.style.setProperty("max-height", `${height}px`);
|
||||
}, 100);
|
||||
useEffect(() => {
|
||||
// HACK: couldn't find any other way for ChessBoard and History to have the same height
|
||||
setTimeout(() => {
|
||||
const chessboard = document.querySelector(
|
||||
"#chessboard",
|
||||
) as HTMLDivElement;
|
||||
const { height } = chessboard.getBoundingClientRect();
|
||||
const containerDiv = containerRef.current as HTMLDivElement;
|
||||
containerDiv.style.setProperty("max-height", `${height}px`);
|
||||
}, 100);
|
||||
|
||||
const historyDiv = historyRef.current as HTMLDivElement;
|
||||
historyDiv.scrollTop = historyDiv.scrollHeight;
|
||||
}, [moveHistory.length]);
|
||||
const historyDiv = historyRef.current as HTMLDivElement;
|
||||
historyDiv.scrollTop = historyDiv.scrollHeight;
|
||||
}, [moveHistory.length]);
|
||||
|
||||
const history: ReactNode[] = [];
|
||||
const history: ReactNode[] = [];
|
||||
|
||||
moveHistory.forEach(({ san }, idx) => {
|
||||
const fullMove = (idx >> 1) + 1;
|
||||
const current = idx == currentPosition ? "bg-gray-500/80" : "";
|
||||
moveHistory.forEach(({ san }, idx) => {
|
||||
const fullMove = (idx >> 1) + 1;
|
||||
const current = idx == currentPosition ? "bg-gray-500/80" : "";
|
||||
|
||||
if (idx % 2 == 0)
|
||||
history.push(
|
||||
<span key={idx} className="px-2 py-1">
|
||||
{fullMove}.
|
||||
</span>
|
||||
);
|
||||
if (idx % 2 == 0)
|
||||
history.push(
|
||||
<span key={idx} className="px-2 py-1">
|
||||
{fullMove}.
|
||||
</span>,
|
||||
);
|
||||
|
||||
history.push(
|
||||
<span key={idx + san} className="py-1">
|
||||
<span className={`inline-block h-full w-2/3 rounded-md ${current}`}>
|
||||
{san}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
if (history.length % 3) history.push(<span key="__history-last__"></span>);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-zinc-800 rounded-lg text-white hidden md:grid grid-rows-[auto,1fr,auto]"
|
||||
>
|
||||
<div className="text-xl text-center my-1 mx-4 border-b">History</div>
|
||||
<div
|
||||
ref={historyRef}
|
||||
className="p-4 min-w-[13rem] h-fit max-h-full overflow-y-scroll overflow-x-hidden grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md [&>:not(:nth-child(3n-2))]:font-bold [&>:nth-child(6n)]:bg-zinc-600 [&>:nth-child(6n-1)]:bg-zinc-600 [&>:nth-child(6n-2)]:bg-zinc-600"
|
||||
>
|
||||
{history}
|
||||
</div>
|
||||
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg empty:hidden">
|
||||
{buttons?.map(({ onClick, title, icon }) => (
|
||||
<Button key={title} title={title} onClick={onClick}>
|
||||
{icon}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
history.push(
|
||||
<span key={idx + san} className="py-1">
|
||||
<span className={`inline-block h-full w-2/3 rounded-md ${current}`}>
|
||||
{san}
|
||||
</span>
|
||||
</span>,
|
||||
);
|
||||
});
|
||||
|
||||
if (history.length % 3) history.push(<span key="__history-last__"></span>);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-zinc-800 rounded-lg text-white hidden md:grid grid-rows-[auto,1fr,auto]"
|
||||
>
|
||||
<div className="text-xl text-center my-1 mx-4 border-b">History</div>
|
||||
<div
|
||||
ref={historyRef}
|
||||
className="p-4 min-w-[13rem] h-fit max-h-full overflow-y-scroll overflow-x-hidden grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md [&>:not(:nth-child(3n-2))]:font-bold [&>:nth-child(6n)]:bg-zinc-600 [&>:nth-child(6n-1)]:bg-zinc-600 [&>:nth-child(6n-2)]:bg-zinc-600"
|
||||
>
|
||||
{history}
|
||||
</div>
|
||||
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg empty:hidden">
|
||||
{buttons?.map(({ onClick, title, icon }) => (
|
||||
<Button key={title} title={title} onClick={onClick}>
|
||||
{icon}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Button: React.FC<{
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ children, title, disabled, onClick }) => (
|
||||
<button
|
||||
className="relative min-w-[1rem] mt-auto bg-zinc-800 flex justify-center items-center p-2 rounded-md hover:bg-zinc-900 transition-colors group capitalize"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="absolute -top-full left-1/2 -translate-x-1/2 bg-black rounded-md py-0.5 px-2 whitespace-nowrap opacity-0 pointer-events-none group-hover:opacity-100 transition-opacity delay-100">
|
||||
{title}
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
<button
|
||||
className="relative min-w-[1rem] mt-auto bg-zinc-800 flex justify-center items-center p-2 rounded-md hover:bg-zinc-900 transition-colors group capitalize"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="absolute -top-full left-1/2 -translate-x-1/2 bg-black rounded-md py-0.5 px-2 whitespace-nowrap opacity-0 pointer-events-none group-hover:opacity-100 transition-opacity delay-100">
|
||||
{title}
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default History;
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
type State = {
|
||||
error: Error | null;
|
||||
text: string | null;
|
||||
error: Error | null;
|
||||
text: string | null;
|
||||
};
|
||||
|
||||
export default function useCopyToClipboard(): [
|
||||
State,
|
||||
(value: any) => Promise<void>
|
||||
State,
|
||||
(value: any) => Promise<void>,
|
||||
] {
|
||||
const [state, setState] = useState<State>({
|
||||
error: null,
|
||||
const [state, setState] = useState<State>({
|
||||
error: null,
|
||||
text: null,
|
||||
});
|
||||
|
||||
const copyToClipboard = useCallback(async (value: string) => {
|
||||
if (!navigator?.clipboard) {
|
||||
return setState({
|
||||
error: new Error("Clipboard not supported"),
|
||||
text: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const copyToClipboard = useCallback(async (value: string) => {
|
||||
if (!navigator?.clipboard) {
|
||||
return setState({
|
||||
error: new Error("Clipboard not supported"),
|
||||
text: null,
|
||||
});
|
||||
}
|
||||
const handleSuccess = () => {
|
||||
setState({
|
||||
error: null,
|
||||
text: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSuccess = () => {
|
||||
setState({
|
||||
error: null,
|
||||
text: value,
|
||||
});
|
||||
};
|
||||
const handleFailure = (e: any) => {
|
||||
setState({
|
||||
error: e,
|
||||
text: null,
|
||||
});
|
||||
};
|
||||
|
||||
const handleFailure = (e: any) => {
|
||||
setState({
|
||||
error: e,
|
||||
text: null,
|
||||
});
|
||||
};
|
||||
navigator.clipboard.writeText(value).then(handleSuccess, handleFailure);
|
||||
}, []);
|
||||
|
||||
navigator.clipboard.writeText(value).then(handleSuccess, handleFailure);
|
||||
}, []);
|
||||
|
||||
return [state, copyToClipboard];
|
||||
return [state, copyToClipboard];
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,5 +45,5 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<RouterProvider router={router} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
|
||||
@@ -3,71 +3,71 @@ import { useNavigate } from "react-router-dom";
|
||||
import Chess, { Move } from "../../../server/src/engine";
|
||||
import ChessUI from "../components/ChessUI";
|
||||
import {
|
||||
ArrowLeft,
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
Plus,
|
||||
Repeat,
|
||||
ArrowLeft,
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
Plus,
|
||||
Repeat,
|
||||
} from "../components/icons";
|
||||
import InferProps from "../utils/InferProps";
|
||||
|
||||
const LocalGame = () => {
|
||||
const [chess, setChess] = useState(Chess.load());
|
||||
const [blackPerspective, setBlackPerspective] = useState(false);
|
||||
const [, aux] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [chess, setChess] = useState(Chess.load());
|
||||
const [blackPerspective, setBlackPerspective] = useState(false);
|
||||
const [, aux] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const rerender = () => aux((prev) => !prev);
|
||||
const rerender = () => aux((prev) => !prev);
|
||||
|
||||
const makeMove = (move: Move) => {
|
||||
if (chess.didUndo()) chess.undoMoves();
|
||||
const makeMove = (move: Move) => {
|
||||
if (chess.didUndo()) chess.undoMoves();
|
||||
|
||||
chess.makeMove(move);
|
||||
chess.makeMove(move);
|
||||
rerender();
|
||||
};
|
||||
|
||||
const buttons: Pick<InferProps<[typeof ChessUI]>, "buttons">["buttons"] = [
|
||||
{
|
||||
onClick: () => navigate("/"),
|
||||
title: "to main page",
|
||||
icon: <ArrowLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => setChess(Chess.load()),
|
||||
title: "new game",
|
||||
icon: <Plus />,
|
||||
},
|
||||
{
|
||||
onClick: () => setBlackPerspective((prev) => !prev),
|
||||
title: "switch sides",
|
||||
icon: <Repeat />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.undo();
|
||||
rerender();
|
||||
};
|
||||
},
|
||||
title: "undo",
|
||||
icon: <CaretLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.redo();
|
||||
rerender();
|
||||
},
|
||||
title: "redo",
|
||||
icon: <CaretRight />,
|
||||
},
|
||||
];
|
||||
|
||||
const buttons: Pick<InferProps<[typeof ChessUI]>, "buttons">["buttons"] = [
|
||||
{
|
||||
onClick: () => navigate("/"),
|
||||
title: "to main page",
|
||||
icon: <ArrowLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => setChess(Chess.load()),
|
||||
title: "new game",
|
||||
icon: <Plus />,
|
||||
},
|
||||
{
|
||||
onClick: () => setBlackPerspective((prev) => !prev),
|
||||
title: "switch sides",
|
||||
icon: <Repeat />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.undo();
|
||||
rerender();
|
||||
},
|
||||
title: "undo",
|
||||
icon: <CaretLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.redo();
|
||||
rerender();
|
||||
},
|
||||
title: "redo",
|
||||
icon: <CaretRight />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ChessUI
|
||||
chess={chess}
|
||||
makeMove={makeMove}
|
||||
blackPerspective={blackPerspective}
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<ChessUI
|
||||
chess={chess}
|
||||
makeMove={makeMove}
|
||||
blackPerspective={blackPerspective}
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocalGame;
|
||||
|
||||
@@ -4,14 +4,14 @@ type ArgumentTypes<F extends React.FC<any>> = F extends React.FC<infer TT>
|
||||
|
||||
type Head<T extends Array<React.FC<any>>> = T extends [
|
||||
infer TT extends React.FC<any>,
|
||||
...any
|
||||
...any,
|
||||
]
|
||||
? TT
|
||||
: never;
|
||||
|
||||
type Tail<T extends Array<React.FC<any>>> = T extends [
|
||||
any,
|
||||
...infer TT extends Array<React.FC<any>>
|
||||
...infer TT extends Array<React.FC<any>>,
|
||||
]
|
||||
? TT
|
||||
: never;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { io } from "socket.io-client";
|
||||
|
||||
// "undefined" means the URL will be computed from the `window.location` object
|
||||
const URL =
|
||||
process.env.NODE_ENV === "production" ? undefined : "http://localhost:5000";
|
||||
process.env.NODE_ENV === "production" ? undefined : "http://localhost:5000";
|
||||
|
||||
export const socket = io(URL as string, {
|
||||
autoConnect: false,
|
||||
autoConnect: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user