Skip to content

Commit

Permalink
feat: add touch device / mobile support
Browse files Browse the repository at this point in the history
  • Loading branch information
adxy committed Jan 6, 2023
1 parent 60f1189 commit 9096f7f
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 99 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn-error.log*
.pnpm-debug.log*

# local env files
.env
.env.local
.env.development.local
.env.test.local
Expand Down
253 changes: 198 additions & 55 deletions components/Board/Board.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import {
CAPTURE_PROMOTION,
EN_PASSANT_CAPTURE,
} from "../../utils/constants";
import {
getEventPosition,
getKeyAtDomPos,
disableScroll,
enableScroll,
} from "../../utils/commonFunctions";
import PawnPromotionDialogue from "./PawnPromotionDialogue";
import { useChessState } from "../../store/chess";
import Loader from "../Loader";
Expand Down Expand Up @@ -99,6 +105,8 @@ export default function Board({
const [piecesPromotedCount, setPiecesPromotedCount] = useState(0);
const [boardState, setBoardState] = useState(undefined);

let lastTouchPosition = null;

useEffect(() => {
if (isMultiplayer && socketState.socket) {
socketState.socket.on("move", (move) => makeMove(move));
Expand Down Expand Up @@ -167,7 +175,6 @@ export default function Board({
const destinationSquare = document.getElementById(to);
const draggedPiece = document.getElementById(from).firstChild;
const destinationPiece = document.getElementById(to).firstChild;
destinationSquare.classList.remove("drag-over");

switch (move.flags) {
case STANDARD_CAPTURE:
Expand Down Expand Up @@ -297,8 +304,8 @@ export default function Board({
destinationSquare.removeChild(destinationSquare.firstChild);
}
destinationSquare.appendChild(promotedPiece);
promotedPiece.addEventListener("dragstart", dragStart);
promotedPiece.addEventListener("dragend", dragEnd);
promotedPiece.addEventListener("mousedown", handleDragStart);
promotedPiece.addEventListener("touchstart", handleDragStart);
setPiecesPromotedCount(piecesPromotedCount + 1);
};

Expand Down Expand Up @@ -327,67 +334,205 @@ export default function Board({
);
}

pieces.forEach((piece) => piece.addEventListener("dragstart", dragStart));
pieces.forEach((piece) => piece.addEventListener("dragend", dragEnd));

const squares = document.querySelectorAll(".square");

squares.forEach((square) => {
square.addEventListener("dragenter", dragEnter);
square.addEventListener("dragover", dragOver);
square.addEventListener("dragleave", dragLeave);
square.addEventListener("drop", drop);
pieces.forEach((piece) => {
piece.addEventListener("mousedown", handleDragStart);
piece.addEventListener("touchstart", handleDragStart);
});
}, [boardState]);
}

const dragStart = (e) => {
e.dataTransfer.setData("text/plain", e.target.id);
setTimeout(() => {
e.target.classList.add("hide");
}, 0);
};

const dragEnter = (e) => {
e.preventDefault();
e.target.classList.add("drag-over");
};
const handleDragStart = (event) => {
event.preventDefault();

const dragOver = (e) => {
e.preventDefault();
e.target.classList.add("drag-over");
};
if (window.scrollY !== 0 || window.scrollX !== 0) {
window.scrollTo(0, 0);
}

const dragLeave = (e) => {
e.target.classList.remove("drag-over");
};
disableScroll();
const draggedPiece = document.getElementById(event.target.id);
const isWhitePiece = draggedPiece.classList.contains("piece-w");

const dragEnd = (e) => {
const draggedPiece = document.getElementById(e.path[0].id);
draggedPiece.classList.remove("hide");
};
const onlyMoveSingleSide = !allowBothSideMoves || isMultiplayer;
const isMovingOpponentPiece =
(!isWhitePlayer && isWhitePiece) || (isWhitePlayer && !isWhitePiece);

const drop = (element) => {
const id = element.dataTransfer.getData("text/plain");
const draggedPiece = document.getElementById(id);
const dropSquareId = element.target.classList.contains("piece")
? element.target.parentElement.id
: element.target.id;

const isPromotionMove = isPromotion({
from: draggedPiece.parentElement.id,
to: dropSquareId,
});

if (isPromotionMove) {
handlePawnPromotionDialogue({
from: draggedPiece.parentElement.id,
to: dropSquareId,
});
if (onlyMoveSingleSide && isMovingOpponentPiece) {
return;
}

makeMove({ from: draggedPiece.parentElement.id, to: dropSquareId });
const draggedPieceClone = draggedPiece.cloneNode();
draggedPiece.classList.add("hide");

const chessBoard = document.getElementById("chess-board");

const boardHeight = chessBoard.offsetHeight;

const pieceHeight = boardHeight / 8 + "px";

draggedPieceClone.style.height = pieceHeight;
draggedPieceClone.style.width = pieceHeight;
draggedPieceClone.style.position = "absolute";
draggedPieceClone.style.zIndex = 1000;
document.body.append(draggedPieceClone);

var chessBoardRect = chessBoard.getBoundingClientRect();

const moveAt = ({ clientX, clientY }) => {
const isInsideParent =
clientX >= chessBoardRect.left &&
clientX <= chessBoardRect.right &&
clientY >= chessBoardRect.top &&
clientY <= chessBoardRect.bottom;

// Keep piece inside board
if (isInsideParent) {
draggedPieceClone.style.left = `${
clientX - draggedPieceClone.offsetWidth / 2
}px`;
draggedPieceClone.style.top = `${
clientY - draggedPieceClone.offsetHeight / 2
}px`;
} else {
if (clientX >= chessBoardRect.right) {
draggedPieceClone.style.left = `${
chessBoardRect.right - draggedPieceClone.offsetWidth / 2
}px`;
}

if (clientX <= chessBoardRect.left) {
draggedPieceClone.style.left = `${
chessBoardRect.left - draggedPieceClone.offsetWidth / 2
}px`;
}

if (clientY >= chessBoardRect.bottom) {
draggedPieceClone.style.top = `${
chessBoardRect.bottom - draggedPieceClone.offsetHeight / 2
}px`;
}
if (clientY <= chessBoardRect.top) {
draggedPieceClone.style.top = `${
chessBoardRect.top - draggedPieceClone.offsetHeight / 2
}px`;
}
}
};

const [clientX, clientY] = getEventPosition(event);

moveAt({ clientX, clientY });

let lastElementId = null;

const addOrRemoveSquareHighlight = ({ event }) => {
const eventPosition = getEventPosition(event) || lastTouchPosition;

let elementBelowId = getKeyAtDomPos({
pos: eventPosition,
asWhite: isWhitePlayer,
bounds: chessBoardRect,
});

let elementBelow = document.getElementById(elementBelowId);

if (!elementBelow) {
return;
}

if (!elementBelow.classList.contains("square")) {
return;
}

// don't highlight if over same color piece or origin
if (
elementBelow.firstElementChild &&
elementBelow.firstElementChild.classList.contains(
`piece-${isWhitePiece ? "w" : "b"}`
)
) {
return;
}

if (lastElementId && lastElementId !== elementBelow.id) {
document
.getElementsByClassName("inner-border-highlight")[0]
.classList.remove("inner-border-highlight");
}

if (elementBelow) {
lastElementId = elementBelow.id;
elementBelow.classList.add("inner-border-highlight");
}
};

const handleMoveEvent = (event) => {
if (!event.touches || event.touches.length < 2) {
lastTouchPosition = getEventPosition(event);
}
const [clientX, clientY] = getEventPosition(event);
moveAt({ clientX, clientY });
addOrRemoveSquareHighlight({ event });
};

const handleEndEvent = (event) => {
event.preventDefault();
enableScroll();
document.removeEventListener("mousemove", handleMoveEvent);
document.removeEventListener("mouseup", handleEndEvent);
document.removeEventListener("touchmove", handleMoveEvent);
document.removeEventListener("touchend", handleEndEvent);

draggedPieceClone.remove();
draggedPiece.classList.remove("hide");

const highlightedSquares = Array.from(
document.getElementsByClassName("inner-border-highlight")
);

if (highlightedSquares && highlightedSquares.length) {
highlightedSquares.forEach((highlightedSquare) =>
highlightedSquare.classList.remove("inner-border-highlight")
);
}

const eventPosition = getEventPosition(event) || lastTouchPosition;

if (!eventPosition) {
return;
}

const destination = getKeyAtDomPos({
pos: eventPosition,
asWhite: isWhitePlayer,
bounds: chessBoardRect,
});

const origin = draggedPiece.parentElement.id;

if (!destination || !origin) {
return;
}

const isPromotionMove = isPromotion({
from: origin,
to: destination,
});

if (isPromotionMove) {
handlePawnPromotionDialogue({
from: origin,
to: destination,
});
return;
}

makeMove({ from: origin, to: destination });
};

document.addEventListener("mousemove", handleMoveEvent);
document.addEventListener("mouseup", handleEndEvent);
document.addEventListener("touchmove", handleMoveEvent);
document.addEventListener("touchend", handleEndEvent);
};

return boardState ? (
Expand All @@ -401,8 +546,6 @@ export default function Board({
id={value.id}
squareType={value.squareType}
piece={value.piece}
allowBothSidesMove={allowBothSideMoves}
isWhitePlayer={isWhitePlayer}
/>
))}
{showPawnPromotionDialogue && (
Expand Down
25 changes: 6 additions & 19 deletions components/Board/Square.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,15 @@ const getDraggable = ({ isWhitePlayer, allowBothSidesMove, piece }) => {
return "false";
};

export default function Square({
id,
squareType,
piece,
allowBothSidesMove,
isWhitePlayer,
}) {
export default function Square({ id, squareType, piece }) {
return (
<SquareBox id={id} squareType={squareType} className="square">
{piece && (
<>
<Piece
className={`piece ${piece[0] === "w" ? "piece-w" : "piece-b"}`}
id={`${id}-${piece}`}
src={`/images/pieces/${piece}.png`}
draggable={getDraggable({
isWhitePlayer,
allowBothSidesMove,
piece,
})}
/>
</>
<Piece
className={`piece ${piece[0] === "w" ? "piece-w" : "piece-b"}`}
id={`${id}-${piece}`}
src={`/images/pieces/${piece}.png`}
/>
)}
</SquareBox>
);
Expand Down
2 changes: 1 addition & 1 deletion components/Dialogs/CreateChallenge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState } from "react";

import DialogCloseButton from "../Buttons/DialogCloseButton";
import Button from "../Buttons/Button";
import { getRandomInteger } from "../../utils/commanFunctions";
import { getRandomInteger } from "../../utils/commonFunctions";
import { useSocketState } from "../../store/socket";
import { useUserState } from "../../store/user";
import { BREAK_POINTS } from "../../styles/Responsive";
Expand Down
Loading

1 comment on commit 9096f7f

@vercel
Copy link

@vercel vercel bot commented on 9096f7f Jan 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

chesskhelo-in – ./

chesskhelo-in-adxy.vercel.app
chesskhelo.in
chesskhelo-in-git-master-adxy.vercel.app
www.chesskhelo.in

Please sign in to comment.