@wirelineio/shogiboardjsx
Version:
Shogiboardjsx is a shogiboard for React. Inspired by Chessboardjsx
560 lines (487 loc) • 21.2 kB
JavaScript
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);