lila/ui/editor/src/chessground.ts

155 lines
4.8 KiB
TypeScript

import { h, VNode } from 'snabbdom';
import { Chessground } from 'chessground';
import { Config as CgConfig } from 'chessground/config';
import { MouchEvent } from 'chessground/types';
import * as util from 'chessground/util';
import EditorCtrl from './ctrl';
export default function (ctrl: EditorCtrl): VNode {
return h('div.cg-wrap', {
hook: {
insert: vnode => {
const el = vnode.elm as HTMLElement;
ctrl.chessground = Chessground(el, makeConfig(ctrl));
bindEvents(el, ctrl);
},
destroy: _ => ctrl.chessground!.destroy(),
},
});
}
function bindEvents(el: HTMLElement, ctrl: EditorCtrl): void {
const handler = onMouseEvent(ctrl);
['touchstart', 'touchmove', 'mousedown', 'mousemove', 'contextmenu'].forEach(function (ev) {
el.addEventListener(ev, handler);
});
}
function isLeftButton(e: MouchEvent): boolean {
return e.buttons === 1 || e.button === 1;
}
function isLeftClick(e: MouchEvent): boolean {
return isLeftButton(e) && !e.ctrlKey;
}
function isRightClick(e: MouchEvent): boolean {
return util.isRightButton(e) || (!!e.ctrlKey && isLeftButton(e));
}
let downKey: Key | undefined;
let lastKey: Key | undefined;
let placeDelete: boolean | undefined;
function onMouseEvent(ctrl: EditorCtrl): (e: MouchEvent) => void {
return function (e: MouchEvent): void {
const sel = ctrl.selected();
// do not generate corresponding mouse event
// (https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent)
if (sel !== 'pointer' && e.cancelable !== false && (e.type === 'touchstart' || e.type === 'touchmove'))
e.preventDefault();
if (isLeftClick(e) || e.type === 'touchstart' || e.type === 'touchmove') {
if (
sel === 'pointer' ||
(ctrl.chessground &&
ctrl.chessground.state.draggable.current &&
ctrl.chessground.state.draggable.current.newPiece)
)
return;
const pos = util.eventPosition(e);
if (!pos) return;
const key = ctrl.chessground!.getKeyAtDomPos(pos);
if (!key) return;
if (e.type === 'mousedown' || e.type === 'touchstart') downKey = key;
if (sel === 'trash') deleteOrHidePiece(ctrl, key, e);
else {
const existingPiece = ctrl.chessground!.state.pieces.get(key);
const piece = {
color: sel[0],
role: sel[1],
};
const samePiece = existingPiece && piece.color == existingPiece.color && piece.role == existingPiece.role;
if ((e.type === 'mousedown' || e.type === 'touchstart') && samePiece) {
deleteOrHidePiece(ctrl, key, e);
placeDelete = true;
const endEvents = { mousedown: 'mouseup', touchstart: 'touchend' };
document.addEventListener(endEvents[e.type], () => (placeDelete = false), { once: true });
} else if (!placeDelete && (e.type === 'mousedown' || e.type === 'touchstart' || key !== lastKey)) {
ctrl.chessground!.setPieces(new Map([[key, piece]]));
ctrl.onChange();
ctrl.chessground!.cancelMove();
}
}
lastKey = key;
} else if (isRightClick(e)) {
if (sel !== 'pointer') {
ctrl.chessground!.state.drawable.current = undefined;
ctrl.chessground!.state.drawable.shapes = [];
if (e.type === 'contextmenu' && sel != 'trash') {
ctrl.chessground!.cancelMove();
sel[0] = util.opposite(sel[0]);
ctrl.redraw();
}
}
}
};
}
function deleteOrHidePiece(ctrl: EditorCtrl, key: Key, e: Event): void {
if (e.type === 'touchstart') {
if (ctrl.chessground!.state.pieces.has(key)) {
(ctrl.chessground!.state.draggable.current!.element as HTMLElement).style.display = 'none';
ctrl.chessground!.cancelMove();
}
document.addEventListener('touchend', () => deletePiece(ctrl, key), { once: true });
} else if (e.type === 'mousedown' || key !== downKey) {
deletePiece(ctrl, key);
}
}
function deletePiece(ctrl: EditorCtrl, key: Key): void {
ctrl.chessground!.setPieces(new Map([[key, undefined]]));
ctrl.onChange();
}
function makeConfig(ctrl: EditorCtrl): CgConfig {
return {
fen: ctrl.initialFen,
orientation: ctrl.options.orientation || 'white',
coordinates: !ctrl.cfg.embed,
autoCastle: false,
addPieceZIndex: ctrl.cfg.is3d,
movable: {
free: true,
color: 'both',
},
animation: {
duration: ctrl.cfg.animation.duration,
},
premovable: {
enabled: false,
},
drawable: {
enabled: true,
defaultSnapToValidMove: (lichess.storage.get('arrow.snap') || 1) != '0',
},
draggable: {
showGhost: true,
deleteOnDropOff: true,
},
selectable: {
enabled: false,
},
highlight: {
lastMove: false,
},
events: {
change: ctrl.onChange.bind(ctrl),
},
};
}