lila/ui/analyse/src/study/multiBoard.ts

175 lines
5.0 KiB
TypeScript

import { h, VNode } from 'snabbdom';
import { opposite } from 'chessground/util';
import { StudyCtrl, ChapterPreview, ChapterPreviewPlayer, Position } from './interfaces';
import { MaybeVNodes } from '../interfaces';
import { multiBoard as xhrLoad } from './studyXhr';
import { bind, spinner } from '../util';
import * as domData from 'common/data';
export class MultiBoardCtrl {
loading = false;
page = 1;
pager?: Paginator<ChapterPreview>;
playing = false;
constructor(readonly studyId: string, readonly redraw: () => void, readonly trans: Trans) {}
addNode(pos: Position, node: Tree.Node) {
const cp = this.pager && this.pager.currentPageResults.find(cp => cp.id == pos.chapterId);
if (cp && cp.playing) {
cp.fen = node.fen;
cp.lastMove = node.uci;
this.redraw();
}
}
reload(onInsert?: boolean) {
if (this.pager && !onInsert) {
this.loading = true;
this.redraw();
}
xhrLoad(this.studyId, this.page, this.playing).then(p => {
this.pager = p;
if (p.nbPages < this.page) {
if (!p.nbPages) this.page = 1;
else this.setPage(p.nbPages);
}
this.loading = false;
this.redraw();
});
}
setPage = (page: number) => {
if (this.page != page) {
this.page = page;
this.reload();
}
};
nextPage = () => this.setPage(this.page + 1);
prevPage = () => this.setPage(this.page - 1);
lastPage = () => {
if (this.pager) this.setPage(this.pager.nbPages);
};
setPlaying = (v: boolean) => {
this.playing = v;
this.reload();
};
}
export function view(ctrl: MultiBoardCtrl, study: StudyCtrl): VNode | undefined {
return h(
'div.study__multiboard',
{
class: { loading: ctrl.loading, nopager: !ctrl.pager },
hook: {
insert() {
ctrl.reload(true);
},
},
},
ctrl.pager ? renderPager(ctrl.pager, study) : [spinner()]
);
}
function renderPager(pager: Paginator<ChapterPreview>, study: StudyCtrl): MaybeVNodes {
const ctrl = study.multiBoard;
return [
h('div.top', [renderPagerNav(pager, ctrl), renderPlayingToggle(ctrl)]),
h('div.now-playing', pager.currentPageResults.map(makePreview(study))),
];
}
function renderPlayingToggle(ctrl: MultiBoardCtrl): VNode {
return h('label.playing', [
h('input', {
attrs: { type: 'checkbox' },
hook: bind('change', e => {
ctrl.setPlaying((e.target as HTMLInputElement).checked);
}),
}),
ctrl.trans.noarg('playing'),
]);
}
function renderPagerNav(pager: Paginator<ChapterPreview>, ctrl: MultiBoardCtrl): VNode {
const page = ctrl.page,
from = Math.min(pager.nbResults, (page - 1) * pager.maxPerPage + 1),
to = Math.min(pager.nbResults, page * pager.maxPerPage);
return h('div.pager', [
pagerButton(ctrl.trans.noarg('first'), 'W', () => ctrl.setPage(1), page > 1, ctrl),
pagerButton(ctrl.trans.noarg('previous'), 'Y', ctrl.prevPage, page > 1, ctrl),
h('span.page', `${from}-${to} / ${pager.nbResults}`),
pagerButton(ctrl.trans.noarg('next'), 'X', ctrl.nextPage, page < pager.nbPages, ctrl),
pagerButton(ctrl.trans.noarg('last'), 'V', ctrl.lastPage, page < pager.nbPages, ctrl),
]);
}
function pagerButton(text: string, icon: string, click: () => void, enable: boolean, ctrl: MultiBoardCtrl): VNode {
return h('button.fbt', {
attrs: {
'data-icon': icon,
disabled: !enable,
title: text,
},
hook: bind('mousedown', click, ctrl.redraw),
});
}
function makePreview(study: StudyCtrl) {
return (preview: ChapterPreview) => {
const contents = preview.players
? [
makePlayer(preview.players[opposite(preview.orientation)]),
makeCg(preview),
makePlayer(preview.players[preview.orientation]),
]
: [h('div.name', preview.name), makeCg(preview)];
return h(
'a.' + preview.id,
{
attrs: { title: preview.name },
class: {
active:
!study.multiBoard.loading &&
study.vm.chapterId == preview.id &&
(!study.relay || !study.relay.intro.active),
},
hook: bind('mousedown', _ => study.setChapter(preview.id)),
},
contents
);
};
}
function makePlayer(player: ChapterPreviewPlayer): VNode {
return h('span.player', [
player.title ? `${player.title} ${player.name}` : player.name,
player.rating && h('span', '' + player.rating),
]);
}
function makeCg(preview: ChapterPreview): VNode {
return h('span.mini-board.cg-wrap.is2d', {
attrs: {
'data-state': `${preview.fen},${preview.orientation},${preview.lastMove}`,
},
hook: {
insert(vnode) {
lichess.miniBoard.init(vnode.elm as HTMLElement);
vnode.data!.fen = preview.fen;
},
postpatch(old, vnode) {
if (old.data!.fen !== preview.fen) {
const lm = preview.lastMove!;
domData.get(vnode.elm as HTMLElement, 'chessground').set({
fen: preview.fen,
lastMove: [lm[0] + lm[1], lm[2] + lm[3]],
});
}
vnode.data!.fen = preview.fen;
},
},
});
}