UNPKG

@wirelineio/shogiboardjsx

Version:

Shogiboardjsx is a shogiboard for React. Inspired by Chessboardjsx

560 lines (487 loc) 21.2 kB
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } import React, { Component } from 'react'; import Board from './Board'; import PropTypes from 'prop-types'; import isEqual from 'lodash.isequal'; import { DragDropContext } from 'react-dnd'; import MultiBackend from 'react-dnd-multi-backend'; import HTML5toTouch from 'react-dnd-multi-backend/lib/HTML5toTouch'; import SparePieces from './SparePieces'; import { fenToObj, validFen, validPositionObject, constructPositionAttributes } from './helpers'; import CustomDragLayer from './CustomDragLayer'; import defaultPieces from './svg/shogipieces/standard'; import ErrorBoundary from './ErrorBoundary'; var ShogiboardContext = React.createContext(); var getPositionObject = function getPositionObject(position) { if (position === 'start') return fenToObj('lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL'); // TODO(burdon): Hack. position = position.replace(/\+/g, ''); if (validFen(position)) return fenToObj(position); if (validPositionObject(position)) return position; console.warn('Invalid:', position); return {}; }; var Shogiboard = /*#__PURE__*/ function (_Component) { _inherits(Shogiboard, _Component); function Shogiboard() { var _getPrototypeOf2; var _temp, _this; _classCallCheck(this, Shogiboard); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _possibleConstructorReturn(_this, (_temp = _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(Shogiboard)).call.apply(_getPrototypeOf2, [this].concat(args))), _this.state = { previousPositionFromProps: getPositionObject(_this.props.position), currentPosition: getPositionObject(_this.props.position), sourceSquare: '', targetSquare: '', sourcePiece: '', waitForTransition: false, phantomPiece: null, wasPieceTouched: false, manualDrop: false, squareClicked: false, firstMove: false, pieces: _objectSpread({}, defaultPieces, {}, _this.props.pieces), undoMove: _this.props.undo }, _this.updateWindowDimensions = function () { _this.setState({ screenWidth: window.innerWidth, screenHeight: window.innerHeight }); }, _this.wasManuallyDropped = function (bool) { return _this.setState({ manualDrop: bool }); }, _this.wasSquareClicked = function (bool) { return _this.setState({ squareClicked: bool }); }, _this.setPosition = function (_ref) { var sourceSquare = _ref.sourceSquare, targetSquare = _ref.targetSquare, piece = _ref.piece; var currentPosition = _this.state.currentPosition; var _this$props = _this.props, getPosition = _this$props.getPosition, dropOffBoard = _this$props.dropOffBoard; if (sourceSquare === targetSquare) return; if (dropOffBoard === 'trash' && !targetSquare) { var _newPosition = currentPosition; delete _newPosition[sourceSquare]; _this.setState({ currentPosition: _newPosition, manualDrop: true }); // get board position for user return getPosition(currentPosition); } var newPosition = currentPosition; sourceSquare !== 'spare' && delete newPosition[sourceSquare]; newPosition[targetSquare] = piece; _this.setState({ currentPosition: newPosition, manualDrop: true }); // get board position for user getPosition(currentPosition); }, _this.setTouchState = function (e) { return _this.setState({ wasPieceTouched: e.isTrusted }); }, _this.getWidth = function () { var _this$props2 = _this.props, calcWidth = _this$props2.calcWidth, width = _this$props2.width; var _this$state = _this.state, screenWidth = _this$state.screenWidth, screenHeight = _this$state.screenHeight; return calcWidth({ screenWidth: screenWidth, screenHeight: screenHeight }) ? calcWidth({ screenWidth: screenWidth, screenHeight: screenHeight }) : width; }, _temp)); } _createClass(Shogiboard, [{ key: "componentDidMount", value: function componentDidMount() { this.updateWindowDimensions(); window.addEventListener('resize', this.updateWindowDimensions); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { window.removeEventListener('resize', this.updateWindowDimensions); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps) { var _this2 = this; var _this$props3 = this.props, position = _this$props3.position, transitionDuration = _this$props3.transitionDuration, getPosition = _this$props3.getPosition; var _this$state2 = this.state, waitForTransition = _this$state2.waitForTransition, undoMove = _this$state2.undoMove; var positionFromProps = getPositionObject(position); var previousPositionFromProps = getPositionObject(prevProps.position); // Check if there is a new position coming from props or undo is called if (!isEqual(positionFromProps, previousPositionFromProps) || undoMove) { this.setState({ previousPositionFromProps: previousPositionFromProps, undoMove: false }); // get board position for user getPosition(positionFromProps); // Give piece time to transition. if (waitForTransition) { return new Promise(function (resolve) { _this2.setState({ currentPosition: positionFromProps }, function () { return setTimeout(function () { _this2.setState({ waitForTransition: false }); resolve(); }, transitionDuration); }); }).then(function () { return setTimeout(function () { return _this2.setState({ phantomPiece: null }); }, transitionDuration); }); } } } }, { key: "render", value: function render() { var _this$props4 = this.props, sparePieces = _this$props4.sparePieces, id = _this$props4.id, orientation = _this$props4.orientation, dropOffBoard = _this$props4.dropOffBoard; var _this$state3 = this.state, sourceSquare = _this$state3.sourceSquare, targetSquare = _this$state3.targetSquare, sourcePiece = _this$state3.sourcePiece, waitForTransition = _this$state3.waitForTransition, phantomPiece = _this$state3.phantomPiece, wasPieceTouched = _this$state3.wasPieceTouched, currentPosition = _this$state3.currentPosition, manualDrop = _this$state3.manualDrop, screenWidth = _this$state3.screenWidth, screenHeight = _this$state3.screenHeight, pieces = _this$state3.pieces; var getScreenDimensions = screenWidth && screenHeight; return React.createElement(ErrorBoundary, null, React.createElement(ShogiboardContext.Provider, { value: _objectSpread({}, this.props, { pieces: pieces, orientation: orientation.toLowerCase(), dropOffBoard: dropOffBoard.toLowerCase() }, { width: this.getWidth(), sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece, waitForTransition: waitForTransition, phantomPiece: phantomPiece, setPosition: this.setPosition, manualDrop: manualDrop, setTouchState: this.setTouchState, currentPosition: currentPosition, screenWidth: screenWidth, screenHeight: screenHeight, wasManuallyDropped: this.wasManuallyDropped, wasSquareClicked: this.wasSquareClicked }) }, React.createElement("div", null, getScreenDimensions && sparePieces && React.createElement(SparePieces.Top, null), getScreenDimensions && React.createElement(Board, null), getScreenDimensions && sparePieces && React.createElement(SparePieces.Bottom, null)), React.createElement(CustomDragLayer, { width: this.getWidth(), pieces: pieces, id: id, wasPieceTouched: wasPieceTouched, sourceSquare: targetSquare }))); } }], [{ key: "getDerivedStateFromProps", value: function getDerivedStateFromProps(props, state) { var position = props.position, undo = props.undo; var currentPosition = state.currentPosition, previousPositionFromProps = state.previousPositionFromProps, manualDrop = state.manualDrop, squareClicked = state.squareClicked; var positionFromProps = getPositionObject(position); // If positionFromProps is a new position then execute, else return null if (!isEqual(positionFromProps, previousPositionFromProps) && !isEqual(positionFromProps, currentPosition)) { // Position attributes from the diff between currentPosition and positionFromProps var _constructPositionAtt = constructPositionAttributes(currentPosition, positionFromProps), sourceSquare = _constructPositionAtt.sourceSquare, targetSquare = _constructPositionAtt.targetSquare, sourcePiece = _constructPositionAtt.sourcePiece, squaresAffected = _constructPositionAtt.squaresAffected; if (manualDrop) { return { sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece, currentPosition: positionFromProps, waitForTransition: false, manualDrop: false }; } /* If the new position involves many pieces, then disregard the transition effect. Possible to add functionality for transitioning of multiple pieces later */ if (squaresAffected && squaresAffected !== 2) { return { currentPosition: positionFromProps, waitForTransition: false, manualDrop: false, sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece }; } // Check if currentPosition has a piece occupying the target square if (currentPosition[targetSquare]) { // Temporarily delete the target square from the new position delete positionFromProps[targetSquare]; return { sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece, // Set the current position to the new position minus the targetSquare currentPosition: positionFromProps, waitForTransition: squareClicked ? false : true, phantomPiece: squareClicked ? null : _defineProperty({}, targetSquare, currentPosition[targetSquare]), manualDrop: false, squareClicked: false }; } // allows for taking back a move if (undo) { return { sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece, currentPosition: positionFromProps, waitForTransition: true, manualDrop: false, squareClicked: false, undoMove: true }; } return { sourceSquare: sourceSquare, targetSquare: targetSquare, sourcePiece: sourcePiece, currentPosition: positionFromProps, waitForTransition: squareClicked ? false : true, manualDrop: false, squareClicked: false }; } // default case return null; } }]); return Shogiboard; }(Component); Shogiboard.propTypes = { /** * The id prop is necessary if more than one board is mounted. * Drag and drop will not work as expected if not provided. */ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), /** * The position to display on the board. Can be either a FEN string or a position object. * See https://www.Shogiboardjsx.com/basics/fen and https://www.Shogiboardjsx.com/basics/position-object * for examples. */ position: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), /** * An object with functions returning jsx as values(render prop). * See https://www.Shogiboardjsx.com/custom * Signature: { wK: * function({ isDragging, squareWidth, droppedPiece, targetSquare, sourceSquare }) => jsx } */ pieces: PropTypes.object, /** * The width in pixels. For a responsive width, use calcWidth. */ width: PropTypes.number, /** * Orientation of the board. */ orientation: PropTypes.oneOf(['white', 'black']), /** * If false, notation will not be shown on the board. */ showNotation: PropTypes.bool, /** * If true, spare pieces will appear above and below the board. */ sparePieces: PropTypes.bool, /** * If false, the pieces will not be draggable */ draggable: PropTypes.bool, /** * The behavior of the piece when dropped off the board. 'snapback' brings the piece * back to it's original square and 'trash' deletes the piece from the board */ dropOffBoard: PropTypes.oneOf(['snapback', 'trash']), /** * The time it takes for a piece to slide to the target square. Only used * when the next position comes from the position prop. See Shogiboardjsx.com/integrations/random for an example */ transitionDuration: PropTypes.number, /** * The style object for the board. */ boardStyle: PropTypes.object, /** * The style object for the light square. */ lightSquareStyle: PropTypes.object, /** * The style object for the dark square. */ darkSquareStyle: PropTypes.object, /** * An object containing custom styles for squares. For example {'e4': {backgroundColor: 'orange'}, * 'd4': {backgroundColor: 'blue'}}. See Shogiboardjsx.com/integrations/move-validation for an example */ squareStyles: PropTypes.object, /** * The style object for the current drop square. { backgroundColor: 'sienna' } */ dropSquareStyle: PropTypes.object, /** * A function for responsive size control, returns the width of the board. * * Signature: function({ screenWidth: number, screenHeight: number }) => number */ calcWidth: PropTypes.func, /** * A function that gives access to the underlying square element. It * allows for customizations with rough.js. See Shogiboardjsx.com/custom for an * example. * * Signature: function({ squareElement: node, squareWidth: number }) => void */ roughSquare: PropTypes.func, /** * A function to call when the mouse is over a square. * See Shogiboardjsx.com/integrations/move-validation for an example. * * Signature: function(square: string) => void */ onMouseOverSquare: PropTypes.func, /** * A function to call when the mouse has left the square. * See Shogiboardjsx.com/integrations/move-validation for an example. * * Signature: function(square: string) => void */ onMouseOutSquare: PropTypes.func, /** * The logic to be performed on piece drop. See Shogiboardjsx.com/integrations for examples. * * Signature: function({ sourceSquare: string, targetSquare: string, piece: string }) => void */ onDrop: PropTypes.func, /** * A function that gives access to the current position object. * For example, getPosition = position => this.setState({ myPosition: position }). * * Signature: function(currentPosition: object) => void */ getPosition: PropTypes.func, /** * A function to call when a piece is dragged over a specific square. * * Signature: function(square: string) => void */ onDragOverSquare: PropTypes.func, /** * A function to call when a square is clicked. * * Signature: function(square: string) => void */ onSquareClick: PropTypes.func, /** * A function to call when a piece is clicked. * * Signature: function(piece: string) => void */ onPieceClick: PropTypes.func, /** * A function to call when a square is right clicked. * * Signature: function(square: string) => void */ onSquareRightClick: PropTypes.func, /** * A function to call when a piece drag is initiated. Returns true if the piece is draggable, * false if not. * * Signature: function( { piece: string, sourceSquare: string } ) => bool */ allowDrag: PropTypes.func, /** When set to true it undos previous move */ undo: PropTypes.bool }; Shogiboard.defaultProps = { id: '0', position: '', pieces: {}, width: 560, orientation: 'white', showNotation: true, sparePieces: false, draggable: true, undo: false, dropOffBoard: 'snapback', transitionDuration: 300, boardStyle: {}, lightSquareStyle: { backgroundColor: 'rgb(240, 217, 181)', boxShadow: 'inset 0 0 1px 1px rgba(0,0,0,.1)' }, darkSquareStyle: { backgroundColor: 'rgba(181, 136, 99, .1)' }, squareStyles: {}, dropSquareStyle: { boxShadow: 'inset 0 0 1px 2px #333' }, calcWidth: function calcWidth() {}, roughSquare: function roughSquare() {}, onMouseOverSquare: function onMouseOverSquare() {}, onMouseOutSquare: function onMouseOutSquare() {}, onDrop: function onDrop() {}, getPosition: function getPosition() {}, onDragOverSquare: function onDragOverSquare() {}, onSquareClick: function onSquareClick() {}, onPieceClick: function onPieceClick() {}, onSquareRightClick: function onSquareRightClick() {}, allowDrag: function allowDrag() { return true; } }; Shogiboard.Consumer = ShogiboardContext.Consumer; export default DragDropContext(MultiBackend(HTML5toTouch))(Shogiboard);