// Test adapted from the ReactDnD chess game tutorial: // http://gaearon.github.io/react-dnd/docs-tutorial.html import * as React from "react"; import * as DOM from "react-dom-factories"; import * as ReactDnd from "react-dnd"; const r = DOM; import DragSource = ReactDnd.DragSource; import DropTarget = ReactDnd.DropTarget; import DragLayer = ReactDnd.DragLayer; import DragDropContext = ReactDnd.DragDropContext; import HTML5Backend, { getEmptyImage } from 'react-dnd-html5-backend'; // Game Component // ---------------------------------------------------------------------- namespace Game { let knightPosition = [0, 0]; let observer: any = null; function emitChange() { observer(knightPosition); } export function observe(o: any) { if (observer) { throw new Error("Multiple observers not implemented."); } observer = o; emitChange(); } export function moveKnight(toX: number, toY: number) { knightPosition = [toX, toY]; emitChange(); } export function canMoveKnight(toX: number, toY: number) { const x = knightPosition[0]; const y = knightPosition[1]; const dx = toX - x; const dy = toY - y; return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2); } } const ItemTypes = { KNIGHT: "knight" }; // Knight Component // ---------------------------------------------------------------------- namespace Knight { interface KnightP extends React.Props { connectDragSource: ReactDnd.ConnectDragSource; connectDragPreview: ReactDnd.ConnectDragPreview; isDragging: boolean; } const knightSource: ReactDnd.DragSourceSpec = { beginDrag: (props) => { return {}; } }; function knightCollect(connect: ReactDnd.DragSourceConnector, monitor: ReactDnd.DragSourceMonitor) { return { connectDragSource: connect.dragSource(), connectDragPreview: connect.dragPreview(), isDragging: monitor.isDragging() }; } export class Knight extends React.Component { static defaultProps: KnightP; static create = React.createFactory(Knight); componentDidMount() { const img = getEmptyImage(); img.onload = () => this.props.connectDragPreview(img); } render() { return this.props.connectDragSource( r.div({ style: { opacity: this.props.isDragging ? 0.5 : 1, fontSize: 25, fontWeight: 'bold', cursor: 'move' } }, "♘") ); } } export const DndKnight = DragSource(ItemTypes.KNIGHT, knightSource, knightCollect)(Knight); export const create = React.createFactory(DndKnight); } // Square Component // ---------------------------------------------------------------------- namespace Square { interface SquareP extends React.Props { black: boolean; } export class Square extends React.Component { render() { const fill = this.props.black ? 'black' : 'white'; return r.div({ style: { backgroundColor: fill } }); } } export const create = React.createFactory(Square); } // BoardSquare Component // ---------------------------------------------------------------------- namespace BoardSquare { interface BoardSquareP extends React.Props { x: number; y: number; connectDropTarget?: ReactDnd.ConnectDropTarget; isOver?: boolean; canDrop?: boolean; } const boardSquareTarget: ReactDnd.DropTargetSpec = { canDrop: (props) => Game.canMoveKnight(props.x, props.y), drop: (props) => Game.moveKnight(props.x, props.y) }; function boardSquareCollect(connect: ReactDnd.DropTargetConnector, monitor: ReactDnd.DropTargetMonitor) { return { connectDropTarget: connect.dropTarget(), isOver: monitor.isOver(), canDrop: monitor.canDrop() }; } export class BoardSquare extends React.Component { static defaultProps: BoardSquareP; private readonly _renderOverlay = (color: string) => { return r.div({ style: { position: 'absolute', top: 0, left: 0, height: '100%', width: '100%', zIndex: 1, opacity: 0.5, backgroundColor: color } }); } render() { const black = (this.props.x + this.props.y) % 2 === 1; const isOver = this.props.isOver; const canDrop = this.props.canDrop; return this.props.connectDropTarget( r.div({ style: { position: 'relative', width: '100%', height: '100%' }, children: [ Square.create({ black }), isOver && !canDrop ? this._renderOverlay('red') : null, !isOver && canDrop ? this._renderOverlay('yellow') : null, isOver && canDrop ? this._renderOverlay('green') : null ] }) ); } } export const DndBoardSquare = DropTarget(ItemTypes.KNIGHT, boardSquareTarget, boardSquareCollect)(BoardSquare); export const create = React.createFactory(DndBoardSquare); } // Custom Drag Layer Component // ---------------------------------------------------------------------- namespace CustomDragLayer { interface CustomDragLayerP extends React.Props { isDragging?: boolean; item?: {}; } function dragLayerCollect(monitor: ReactDnd.DragLayerMonitor) { return { isDragging: monitor.isDragging(), item: monitor.getItem(), }; } export class CustomDragLayer extends React.Component { render() { return r.div(null, this.props.isDragging ? this.props.item : null); } } export const dragLayer = DragLayer(dragLayerCollect)(CustomDragLayer); export const create = React.createFactory(dragLayer); } // Board Component // ---------------------------------------------------------------------- namespace Board { interface BoardP extends React.Props { knightPosition: number[]; } export class Board extends React.Component { private readonly _renderPiece = (x: number, y: number) => { const knightX = this.props.knightPosition[0]; const knightY = this.props.knightPosition[1]; return x === knightX && y === knightY ? Knight.create() : null; } private readonly _renderSquare = (i: number) => { const x = i % 8; const y = Math.floor(i / 8); return r.div({ key: i, style: { width: '12.5%', height: '12.5%' } }, BoardSquare.create({ x, y }, this._renderPiece(x, y))); } render() { const squares: Array> = []; for (let i = 0; i < 64; i++) { squares.push(this._renderSquare(i)); } return r.div({ children: [ CustomDragLayer.create(), r.div({ style: { width: '100%', height: '100%', display: 'flex', flexWrap: 'wrap' }, children: squares }) ] }); } } export const createWithHTMLBackend = React.createFactory(DragDropContext(HTML5Backend)(Board)); } // Render the Board Component // ---------------------------------------------------------------------- Board.createWithHTMLBackend({ knightPosition: [0, 0] });