UNPKG

cm-chessboard

Version:

A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.

173 lines (155 loc) 5.99 kB
/** * Authors and copyright: Barak Michener (@barakmich) and Stefan Haack (@shaack) * Repository: https://github.com/shaack/cm-chessboard * License: MIT, see file 'LICENSE' */ import {Extension, EXTENSION_POINT} from "../../model/Extension.js" import {Svg} from "../../lib/Svg.js" import {Utils} from "../../lib/Utils.js" export const ARROW_TYPE = { default: {class: "arrow-success"}, success: {class: "arrow-success"}, secondary: {class: "arrow-secondary"}, warning: {class: "arrow-warning"}, info: {class: "arrow-info"}, danger: {class: "arrow-danger"} } export class Arrows extends Extension { /** @constructor */ constructor(chessboard, props = {}) { super(chessboard) this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, () => { this.onRedrawBoard() }) this.registerExtensionPoint(EXTENSION_POINT.destroy, () => { this.onDestroy() }) this.props = { sprite: "extensions/arrows/arrows.svg", slice: "arrowDefault", headSize: 7, offsetFrom: 0, offsetTo: 0.55 } Object.assign(this.props, props) if (this.chessboard.props.assetsCache) { this.chessboard.view.cacheSpriteToDiv("cm-chessboard-arrows", this.getSpriteUrl()) } chessboard.addArrow = this.addArrow.bind(this) chessboard.getArrows = this.getArrows.bind(this) chessboard.removeArrows = this.removeArrows.bind(this) this.arrowGroup = Svg.addElement(chessboard.view.markersTopLayer, "g", {class: "arrows"}) this.instanceId = Math.random().toString(36).slice(2, 10) this.arrows = [] } onDestroy() { this.arrows.length = 0 if (this.arrowGroup && this.arrowGroup.parentNode) { this.arrowGroup.parentNode.removeChild(this.arrowGroup) } delete this.chessboard.addArrow delete this.chessboard.getArrows delete this.chessboard.removeArrows } onRedrawBoard() { while (this.arrowGroup.firstChild) { this.arrowGroup.removeChild(this.arrowGroup.firstChild) } this.arrows.forEach((arrow) => { this.drawArrow(arrow) }) } drawArrow(arrow) { const view = this.chessboard.view const arrowsGroup = Svg.addElement(this.arrowGroup, "g") arrowsGroup.setAttribute("data-arrow", arrow.from + arrow.to) arrowsGroup.setAttribute("class", "arrow " + arrow.type.class) const ptFrom = view.squareToPoint(arrow.from) const ptTo = view.squareToPoint(arrow.to) const spriteUrl = this.chessboard.props.assetsCache ? "" : this.getSpriteUrl() const defs = Svg.addElement(arrowsGroup, "defs") const id = "arrow-" + this.instanceId + "-" + arrow.from + arrow.to const marker = Svg.addElement(defs, "marker", { id: id, markerWidth: this.props.headSize, markerHeight: this.props.headSize, refX: 20, refY: 20, viewBox: "0 0 40 40", orient: "auto", class: "arrow-head", }) Svg.addElement(marker, "use", { href: `${spriteUrl}#${this.props.slice}`, }) // Compute centers of start and end squares const cx1 = ptFrom.x + view.squareWidth / 2 const cy1 = ptFrom.y + view.squareHeight / 2 const cx2 = ptTo.x + view.squareWidth / 2 const cy2 = ptTo.y + view.squareHeight / 2 // Offset the line so it starts/ends at the edge of an invisible circle inside each square const dx = cx2 - cx1 const dy = cy2 - cy1 const len = Math.hypot(dx, dy) || 1 const ux = dx / len const uy = dy / len // compute radii from props: factor relative to half of the square size const halfMin = Math.min(view.squareWidth, view.squareHeight) / 2 const clamp01 = (v) => Math.max(0, Math.min(1, v)) const rFrom = halfMin * clamp01(this.props.offsetFrom) const rTo = halfMin * clamp01(this.props.offsetTo) const x1 = cx1 + ux * rFrom const y1 = cy1 + uy * rFrom const x2 = cx2 - ux * rTo const y2 = cy2 - uy * rTo const width = ((view.scalingX + view.scalingY) / 2) * 8 let lineFill = Svg.addElement(arrowsGroup, "line") lineFill.setAttribute('x1', x1.toString()) lineFill.setAttribute('x2', x2.toString()) lineFill.setAttribute('y1', y1.toString()) lineFill.setAttribute('y2', y2.toString()) lineFill.setAttribute('class', 'arrow-line') lineFill.setAttribute("marker-end", "url(#" + id + ")") lineFill.setAttribute('stroke-width', width + "px") } addArrow(type, from, to) { this.arrows.push(new Arrow(from, to, type)) this.onRedrawBoard() } getArrows(type = undefined, from = undefined, to = undefined) { let arrows = [] this.arrows.forEach((arrow) => { if (arrow.matches(from, to, type)) { arrows.push(arrow) } }) return arrows } removeArrows(type = undefined, from = undefined, to = undefined) { this.arrows = this.arrows.filter((arrow) => !arrow.matches(from, to, type)) this.onRedrawBoard() } getSpriteUrl() { if(Utils.isAbsoluteUrl(this.props.sprite)) { return this.props.sprite } else { return this.chessboard.props.assetsUrl + this.props.sprite } } } class Arrow { constructor(from, to, type) { this.from = from this.to = to this.type = type } matches(from = undefined, to = undefined, type = undefined) { if (from && from !== this.from) { return false } if (to && to !== this.to) { return false } return !(type && type !== this.type) } }