lila/ui/site/src/coordinate.ts

234 lines
7.1 KiB
TypeScript

import * as xhr from 'common/xhr';
import sparkline from '@fnando/sparkline';
import throttle from 'common/throttle';
import resizeHandle from 'common/resize';
import * as cg from 'chessground/types';
lichess.load.then(() => {
$('#trainer').each(function (this: HTMLElement) {
const $trainer = $(this);
const $board = $('.coord-trainer__board .cg-wrap');
const $coordsSvg = $('.coords-svg');
const $coords = $coordsSvg.find('.coord');
let ground;
const $side = $('.coord-trainer__side');
const $right = $('.coord-trainer__table');
const $bar = $trainer.find('.progress_bar');
const $start = $right.find('.start');
const $explanation = $right.find('.explanation');
const $score = $('.coord-trainer__score');
const $timer = $('.coord-trainer__timer');
const scoreUrl = $trainer.data('score-url');
const duration = 30 * 1000;
const tickDelay = 50;
const resizePref = $trainer.data('resize-pref');
let colorPref = $trainer.data('color-pref');
let color;
let startAt, score;
let wrongTimeout;
let ply = 0;
const showColor = function () {
color = colorPref == 'random' ? ['white', 'black'][Math.round(Math.random())] : colorPref;
if (!ground)
ground = window.Chessground($board[0], {
coordinates: false,
drawable: { enabled: false },
movable: {
free: false,
color: null,
},
orientation: color,
addPieceZIndex: $('#main-wrap').hasClass('is3d'),
events: {
insert(elements: cg.Elements) {
resizeHandle(elements, resizePref, ply);
},
},
});
else if (color !== ground.state.orientation) ground.toggleOrientation();
$trainer.removeClass('white black').addClass(color);
};
showColor();
$trainer.find('form.color').each(function (this: HTMLFormElement) {
const form = this,
$form = $(this);
$form.find('input').on('change', function () {
const selected = $form.find('input:checked').val() as string;
const c = {
1: 'white',
2: 'random',
3: 'black',
}[selected];
if (c !== colorPref) xhr.formToXhr(form);
colorPref = c;
showColor();
return false;
});
});
const setZen = throttle(1000, zen =>
xhr.text('/pref/zen', {
method: 'post',
body: xhr.form({ zen: zen ? 1 : 0 }),
})
);
lichess.pubsub.on('zen', () => {
const zen = $('body').toggleClass('zen').hasClass('zen');
window.dispatchEvent(new Event('resize'));
setZen(zen);
requestAnimationFrame(showCharts);
});
window.Mousetrap.bind('z', () => lichess.pubsub.emit('zen'));
$('#zentog').on('click', () => lichess.pubsub.emit('zen'));
function showCharts() {
$side.find('.user_chart').each(function (this: HTMLElement) {
const $svg = $('<svg class="sparkline" height="80px" stroke-width="3">')
.attr('width', $(this).width() + 'px')
.prependTo($(this).empty());
sparkline($svg[0] as unknown as SVGSVGElement, $(this).data('points'), {
interactive: true,
/* onmousemove(event, datapoint) { */
/* var svg = findClosest(event.target, "svg"); */
/* var tooltip = svg.nextElementSibling; */
/* var date = new Date(datapoint.date).toUTCString().replace(/^.*?, (.*?) \d{2}:\d{2}:\d{2}.*?$/, "$1"); */
/* tooltip.hidden = false; */
/* tooltip.textContent = `${date}: $${datapoint.value.toFixed(2)} USD`; */
/* tooltip.style.top = `${event.offsetY}px`; */
/* tooltip.style.left = `${event.offsetX + 20}px`; */
/* }, */
/* onmouseout() { */
/* var svg = findClosest(event.target, "svg"); */
/* var tooltip = svg.nextElementSibling; */
/* tooltip.hidden = true; */
/* } */
/* }; */
});
});
}
requestAnimationFrame(showCharts);
const centerRight = function () {
$right.css('top', 256 - $right.height() / 2 + 'px');
};
centerRight();
const clearCoords = function () {
$coords.text('');
};
const newCoord = function (prevCoord) {
// disallow the previous coordinate's row or file from being selected
let files = 'abcdefgh';
const fileIndex = files.indexOf(prevCoord[0]);
files = files.slice(0, fileIndex) + files.slice(fileIndex + 1, 8);
let rows = '12345678';
const rowIndex = rows.indexOf(prevCoord[1]);
rows = rows.slice(0, rowIndex) + rows.slice(rowIndex + 1, 8);
return (
files[Math.round(Math.random() * (files.length - 1))] + rows[Math.round(Math.random() * (rows.length - 1))]
);
};
const currentCoordEl = function () {
return $coordsSvg.find('.current-coord');
};
const nextCoordEl = function () {
return $coordsSvg.find('.next-coord');
};
const advanceCoords = function () {
$coords.toggleClass('current-coord').toggleClass('next-coord');
const currentCoordValue = currentCoordEl().text();
nextCoordEl().text(newCoord(currentCoordValue));
};
const stop = function () {
clearCoords();
$trainer.removeClass('play');
centerRight();
$trainer.removeClass('wrong');
ground.set({
events: {
select: false,
},
});
if (scoreUrl)
xhr
.text(scoreUrl, {
method: 'post',
body: xhr.form({ color, score }),
})
.then(charts => {
$side.find('.scores').html(charts);
showCharts();
});
};
const tick = function () {
const spent = Math.min(duration, new Date().getTime() - startAt);
const left = ((duration - spent) / 1000).toFixed(1);
if (+left < 10) {
$timer.addClass('hurry');
}
$timer.text(left);
$bar.css('width', (100 * spent) / duration + '%');
if (spent < duration) setTimeout(tick, tickDelay);
else stop();
};
$start.on('click', () => {
$explanation.remove();
$trainer.addClass('play').removeClass('init');
$timer.removeClass('hurry');
ply = 2;
ground.redrawAll();
showColor();
clearCoords();
centerRight();
score = 0;
$score.text(score);
$bar.css('width', 0);
setTimeout(function () {
startAt = new Date();
ground.set({
events: {
select(key) {
const hit = key == currentCoordEl().text();
if (hit) {
score++;
$score.text(score);
advanceCoords();
} else {
clearTimeout(wrongTimeout);
$trainer.addClass('wrong');
wrongTimeout = setTimeout(function () {
$trainer.removeClass('wrong');
}, 500);
}
},
},
});
const initialCoordValue = newCoord('a1');
currentCoordEl().text(initialCoordValue);
nextCoordEl().text(newCoord(initialCoordValue));
tick();
}, 1000);
});
});
});