analysis UI WIP

es2016
Thibault Duplessis 2019-02-23 10:57:09 +07:00
parent 3b9b24a16e
commit 099b7d3ce9
13 changed files with 311 additions and 176 deletions

View File

@ -36,7 +36,7 @@ object replay {
a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?literate=1")(trans.downloadAnnotated()),
a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?evals=0&clocks=0")(trans.downloadRaw()),
game.isPgnImport option a(dataIcon := "x", cls := "text", rel := "nofollow", href := s"${routes.Game.exportOne(game.id)}?imported=1")(trans.downloadImported()),
ctx.noBlind option a(dataIcon := "=", cls := "text embed_howto", target := "_blank")(trans.embedInYourWebsite())
ctx.noBlind option a(dataIcon := "=", cls := "text embed-howto", target := "_blank")(trans.embedInYourWebsite())
)
bits.layout(
@ -65,12 +65,12 @@ explorer:{endpoint:"$explorerEndpoint",tablebaseEndpoint:"$tablebaseEndpoint"}}"
h2("PGN downloads"),
pgnLinks
)
else div(cls := "underboard_content none")(
div(cls := "analysis_panels")(
game.analysable option div(cls := "panel computer_analysis")(
if (analysis.isDefined || analysisStarted) div(id := "adv_chart")
else div(cls := "analyse__underboard none")(
div(cls := "analyse__underboard__panels")(
game.analysable option div(cls := "computer-analysis")(
if (analysis.isDefined || analysisStarted) div(id := "adv-chart")
else form(
cls := s"future_game_analysis${ctx.isAnon ?? " must_login"}",
cls := s"future-game-analysis${ctx.isAnon ?? " must-login"}",
action := routes.Analyse.requestAnalysis(gameId),
method := "post"
)(
@ -79,38 +79,38 @@ explorer:{endpoint:"$explorerEndpoint",tablebaseEndpoint:"$tablebaseEndpoint"}}"
)
)
),
div(cls := "panel fen_pgn")(
div(cls := "fen-pgn")(
div(
strong("FEN"),
input(readonly := true, spellcheck := false, cls := "copyable autoselect fen")
input(readonly := true, spellcheck := false, cls := "copyable autoselect analyse__underboard__fen")
),
div(cls := "pgn_options")(
div(cls := "pgn-options")(
strong("PGN"),
pgnLinks
),
div(cls := "pgn")(pgn)
),
div(cls := "panel move_times")(
game.turns > 1 option div(id := "movetimes_chart")
div(cls := "move-times")(
game.turns > 1 option div(id := "movetimes-chart")
),
cross.map { c =>
div(cls := "panel crosstable")(
div(cls := "crosstable")(
views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov), pov.gameId.some)
)
}
),
div(cls := "analysis_menu")(
div(cls := "analyse__underboard__menu")(
game.analysable option
a(
dataPanel := "computer_analysis",
cls := "computer_analysis",
span(
dataPanel := "computer-analysis",
cls := "computer-analysis",
title := analysis.map { a => s"Provided by ${usernameOrId(a.providedBy)}" }
)(trans.computerAnalysis()),
!game.isPgnImport option frag(
game.turns > 1 option a(dataPanel := "move_times", cls := "move_times")(trans.moveTimes()),
cross.isDefined option a(dataPanel := "crosstable", cls := "crosstable")(trans.crosstable())
game.turns > 1 option span(dataPanel := "move-times", cls := "move-times")(trans.moveTimes()),
cross.isDefined option span(dataPanel := "crosstable", cls := "crosstable")(trans.crosstable())
),
a(dataPanel := "fen_pgn", cls := "fen_pgn")(raw("FEN & PGN"))
span(dataPanel := "fen-pgn", cls := "fen-pgn")(raw("FEN & PGN"))
)
)
))

View File

@ -6,7 +6,7 @@ lichess.movetimeChart = function(data, trans) {
lichess.loadScript('javascripts/chart/division.js').done(function() {
lichess.chartCommon('highchart').done(function() {
lichess.movetimeChart.render = function() {
$('#movetimes_chart:not(.rendered)').each(function() {
$('#movetimes-chart:not(.rendered)').each(function() {
var $this = $(this).addClass('rendered');
var series = {

View File

@ -59,6 +59,8 @@ interface Lichess {
render(ctrl: any): any;
}
playMusic(): void;
spinnerHtml: string;
movetimeChart: any;
}
interface Cookie {

View File

@ -9,4 +9,5 @@ $analyse-controls-height: 2.5rem;
@import 'retro';
@import 'fork';
@import 'side';
@import 'underboard';
// @import 'side-clock';

View File

@ -169,7 +169,6 @@
cursor: pointer;
display: block;
font-size: 1.2em;
color: #fff;
width: 1.5em;
line-height: 1.5em;
text-align: center;

View File

@ -20,7 +20,7 @@ $board-width: minmax($board-min-width, $board-max-width);
&__board { grid-area: board; }
&__tools { grid-area: tools; }
&__controls { grid-area: controls; }
&__underboard { grid-area: underboard; }
&__underboard { grid-area: under; }
&__acpl { grid-area: acpl; }
&__side { grid-area: side; }
& .eval-gauge { grid-area: gauge; }
@ -33,7 +33,7 @@ $board-width: minmax($board-min-width, $board-max-width);
grid-template-rows: fit-content(0);
grid-template-areas:
'. board gauge . tools .'
'. underboard . . controls .'
'. under under . controls .'
'. side side side acpl .';
grid-row-gap: $block-gap;
&__moves { display: block; }
@ -46,9 +46,9 @@ $board-width: minmax($board-min-width, $board-max-width);
@include columns-with-gauge-col3($board-width, 1rem);
}
grid-template-areas:
'. side . board gauge . tools .'
'. chat . board gauge . tools .'
'. . . underboard . . controls .'
'. . . underboard . . acpl .';
'. side . board gauge . tools .'
'. chat . board gauge . tools .'
'. . . under under . controls .'
'. . . under under . acpl .';
}
}

View File

@ -9,8 +9,7 @@
.title {
font-size: .9rem;
line-height: 1.9em;
background: mix($c-secondary, $c-bg-box, 60%);
color: $c-secondary-over;
background: mix($c-secondary, $c-bg-box, 40%);
padding-left: 7px;
}
}

View File

@ -0,0 +1,127 @@
$panel-height: 220px;
.analyse__underboard {
&__menu {
@extend %flex-center;
justify-content: center;
border-top: $border;
height: 21px;
& > span {
@extend %roboto, %box-radius-bottom;
text-transform: uppercase;
font-size: .9em;
padding: .4em 1em;
cursor: pointer;
border-bottom: 2px solid transparent;
&:hover {
background: mix($c-bg-box, $c-bg-page, 50%);
}
&.active {
background: $c-bg-box;
border-color: $c-accent;
}
}
}
&__panels {
& > div {
height: $panel-height;
display: none;
overflow-x: hidden;
overflow-y: auto;
text-align: left;
}
& > div.active {
display: block;
}
& .crosstable table {
margin-top: 60px;
}
}
&.comp-off .computer-analysis {
display: none;
}
& .computer-analysis {
position: relative;
}
& .game-analysis {
padding: 10px;
width: 492px;
}
& .future-game-analysis {
margin-top: 84px;
text-align: center;
& .button {
display: inline-block;
line-height: 24px;
}
}
& .fen-pgn {
font-size: .9em;
& > div {
margin: 0.6em 0;
display: flex;
align-items: center;
}
& .pgn {
white-space: pre-wrap;
font-family: monospace;
}
& strong {
display: inline-block;
margin-right: 1em;
}
& .pgn-options {
& div {
@extend %flex-wrap;
}
& a {
flex: 0 0 50%;
line-height: 1.6em;
}
}
}
&__fen {
width: 100%;
border: 0;
background: none;
padding: .3em;
}
}
#adv-chart,
#movetimes-chart {
height: $panel-height;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 20%, rgba(128, 128, 128, 0.2) 50%, rgba(255, 255, 255, 0) 80%, rgba(255, 255, 255, 0) 100%);
overflow: hidden;
}
#adv-chart-loader {
position: absolute;
top: 76px;
left: -1px;
background: #fff;
padding: 7px 10px 7px 0;
line-height: 20px;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.3);
display: flex;
border: 1px solid #ccc;
border-radius: 0 50px 50px 0;
& span {
margin-left: 7px;
opacity: 1;
transition: 0.5s;
white-space: nowrap;
overflow: hidden;
width: 90px;
display: block;
.analyse__underboard:hover & {
margin-left: 0;
width: 0;
opacity: 0;
}
}
& .spinner {
width: 32px;
height: 32px;
display:inline-block;
margin-left: 10px;
}
}

View File

@ -1,10 +1,8 @@
var defined = require('common').defined;
module.exports = function(cfg) {
var element = document.getElementById('main-wrap');
var data = cfg.data;
var $watchers = $('#site_header div.watchers').watchers();
var analyse, $panels;
var analyse;
lichess.socket = lichess.StrongSocket(
data.url.socket,
data.player.version, {
@ -24,7 +22,7 @@ module.exports = function(cfg) {
else if (lichess.advantageChart.update) lichess.advantageChart.update(data, partial);
if (!partial) {
lichess.pubsub.emit('analysis.server.complete')();
$("#adv_chart_loader").remove();
$("#adv-chart-loader").remove();
}
},
crowd: function(event) {
@ -32,151 +30,12 @@ module.exports = function(cfg) {
}
}
});
var $timeChart = $("#movetimes_chart");
var inputFen = element.querySelector('input.fen');
var unselect = function(chart) {
chart.getSelectedPoints().forEach(function(point) {
point.select(false);
});
};
var lastFen;
if (!window.lichess.AnalyseNVUI) lichess.pubsub.on('analysis.change', function(fen, path, mainlinePly) {
var chart, point, $chart = $("#adv_chart");
if (fen && fen !== lastFen) {
inputFen.value = fen;
lastFen = fen;
}
if ($chart.length) {
chart = window.Highcharts && $chart.highcharts();
if (chart) {
if (mainlinePly != chart.lastPly) {
if (mainlinePly === false) unselect(chart);
else {
point = chart.series[0].data[mainlinePly - 1 - cfg.data.game.startedAtTurn];
if (defined(point)) point.select();
else unselect(chart);
}
}
chart.lastPly = mainlinePly;
}
}
if ($timeChart.length) {
chart = window.Highcharts && $timeChart.highcharts();
if (chart) {
if (mainlinePly != chart.lastPly) {
if (mainlinePly === false) unselect(chart);
else {
var white = mainlinePly % 2 !== 0;
var serie = white ? 0 : 1;
var turn = Math.floor((mainlinePly - 1 - cfg.data.game.startedAtTurn) / 2);
point = chart.series[serie].data[turn];
if (defined(point)) point.select();
else unselect(chart);
}
}
chart.lastPly = mainlinePly;
}
}
});
cfg.$side = $('.analyse__side').clone();
cfg.onToggleComputer = function(v) {
setTimeout(function() {
if (v) $('div.analysis_menu a.computer_analysis').mousedown();
else $('div.analysis_menu a:eq(1)').mousedown();
}, 50);
};
cfg.trans = lichess.trans(cfg.i18n);
cfg.initialPly = 'url';
cfg.element = element.querySelector('main.analyse');
cfg.socketSend = lichess.socket.send;
analyse = LichessAnalyse.start(cfg);
cfg.jumpToIndex = analyse.jumpToIndex;
$('.underboard_content', element).appendTo($('.underboard .center', element)).removeClass('none');
var chartLoader = function() {
return '<div id="adv_chart_loader">' +
'<span>' + lichess.engineName + '<br>server analysis</span>' +
lichess.spinnerHtml +
'</div>'
};
var startAdvantageChart = function() {
if (lichess.advantageChart || lichess.AnalyseNVUI) return;
var loading = !data.treeParts[0].eval || !Object.keys(data.treeParts[0].eval).length;
var $panel = $panels.filter('.computer_analysis');
if (!$("#adv_chart").length) $panel.html('<div id="adv_chart"></div>' + (loading ? chartLoader() : ''));
else if (loading && !$("#adv_chart_loader").length) $panel.append(chartLoader());
lichess.loadScript('javascripts/chart/acpl.js').then(function() {
lichess.advantageChart(data, cfg.trans, $("#adv_chart")[0]);
});
};
$panels = $('div.analysis_panels > div');
var $menu = $('div.analysis_menu');
var storage = lichess.storage.make('analysis.panel');
var setPanel = function(panel) {
$menu.children('.active').removeClass('active').end().find('.' + panel).addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
if (panel === 'move_times' && !lichess.movetimeChart) try {
lichess.loadScript('javascripts/chart/movetime.js').then(function() {
lichess.movetimeChart(data, cfg.trans);
});
} catch (e) {}
if (panel === 'computer_analysis' && $("#adv_chart").length)
setTimeout(startAdvantageChart, 200);
};
$menu.on('mousedown', 'a', function() {
var panel = $(this).data('panel');
storage.set(panel);
setPanel(panel);
});
var stored = storage.get();
if (stored && $menu.children('.' + stored).length) setPanel(stored);
else {
var $ct = $menu.children('.crosstable');
($ct.length ? $ct : $menu.children(':first-child')).trigger('mousedown');
}
if (!cfg.data.analysis) {
$panels.find('form.future_game_analysis').submit(function() {
if ($(this).hasClass('must_login')) {
if (confirm(cfg.trans('youNeedAnAccountToDoThat'))) location.href = '/signup';
return false;
}
$.ajax({
method: 'post',
url: $(this).attr('action'),
success: startAdvantageChart,
error: lichess.reload
});
return false;
});
}
$panels.on('click', 'div.pgn', function() {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText($(this)[0]);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents($(this)[0]);
selection.removeAllRanges();
selection.addRange(range);
}
});
$panels.on('click', '.embed_howto', function() {
var url = 'https://lichess.org/embed/' + data.game.id + location.hash;
var iframe = '<iframe src="' + url + '?theme=auto&bg=auto"\nwidth=600 height=397 frameborder=0></iframe>';
$.modal($(
'<strong style="font-size:1.5em">' + $(this).html() + '</strong><br /><br />' +
'<pre>' + lichess.escapeHtml(iframe) + '</pre><br />' +
iframe + '<br /><br />' +
'<a class="text" data-icon="" href="/developers#embed-game">Read more about embedding games</a>'
));
});
lichess.topMenuIntent();
};

View File

@ -719,8 +719,8 @@ export default class AnalyseCtrl {
const value = !this.showComputer();
this.showComputer(value);
if (!value && this.practice) this.togglePractice();
if (this.opts.onToggleComputer) this.opts.onToggleComputer(value);
this.onToggleComputer();
li.pubsub.emit('analysis.comp.toggle')(value);
}
mergeAnalysisData(data: ServerEvalData): void {

View File

@ -47,6 +47,7 @@ export interface Game {
status: Status;
player: Color;
turns: number;
startedAtTurn: number;
source: Source;
speed: Speed;
variant: Variant;
@ -98,7 +99,6 @@ export interface AnalyseOpts {
study?: any;
tagTypes?: string;
practice?: StudyPracticeData;
onToggleComputer?: (v: boolean) => void;
relay?: RelayData;
$side: JQuery;
chat: any;

View File

@ -0,0 +1,143 @@
import AnalyseCtrl from './ctrl';
import { defined } from 'common';
export default function(element: HTMLElement, ctrl: AnalyseCtrl) {
const li = window.lichess;
$(element).replaceWith($('.analyse__underboard.none').removeClass('none'));
const data = ctrl.data,
$panels = $('.analyse__underboard__panels > div'),
$menu = $('.analyse__underboard__menu'),
$timeChart = $("#movetimes-chart"),
inputFen = document.querySelector('.analyse__underboard__fen') as HTMLInputElement,
unselect = chart => {
chart.getSelectedPoints().forEach(function(point) {
point.select(false);
});
};
let lastFen: string;
if (!li.AnalyseNVUI) {
li.pubsub.on('analysis.comp.toggle', (v: boolean) => {
setTimeout(function() {
if (v) $menu.find('a.computer-analysis').mousedown();
else $menu.find('a:eq(1)').mousedown();
}, 50);
});
li.pubsub.on('analysis.change', (fen: Fen, _, mainlinePly: Ply | false) => {
var chart, point, $chart = $("#adv-chart");
if (fen && fen !== lastFen) {
inputFen.value = fen;
lastFen = fen;
}
if ($chart.length) {
chart = window.Highcharts && $chart.highcharts();
if (chart) {
if (mainlinePly != chart.lastPly) {
if (mainlinePly === false) unselect(chart);
else {
point = chart.series[0].data[mainlinePly - 1 - data.game.startedAtTurn];
if (defined(point)) point.select();
else unselect(chart);
}
}
chart.lastPly = mainlinePly;
}
}
if ($timeChart.length) {
console.log(window.Highcharts);
chart = window.Highcharts && $timeChart.highcharts();
if (chart) {
if (mainlinePly != chart.lastPly) {
if (mainlinePly === false) unselect(chart);
else {
var white = mainlinePly % 2 !== 0;
var serie = white ? 0 : 1;
var turn = Math.floor((mainlinePly - 1 - data.game.startedAtTurn) / 2);
point = chart.series[serie].data[turn];
if (defined(point)) point.select();
else unselect(chart);
}
}
chart.lastPly = mainlinePly;
}
}
});
}
var chartLoader = function() {
return '<div id="adv-chart-loader">' +
'<span>' + li.engineName + '<br>server analysis</span>' +
li.spinnerHtml +
'</div>'
};
var startAdvantageChart = function() {
if (li.advantageChart || li.AnalyseNVUI) return;
var loading = !data.treeParts[0].eval || !Object.keys(data.treeParts[0].eval).length;
var $panel = $panels.filter('.computer-analysis');
if (!$("#adv-chart").length) $panel.html('<div id="adv-chart"></div>' + (loading ? chartLoader() : ''));
else if (loading && !$("#adv-chart-loader").length) $panel.append(chartLoader());
li.loadScript('javascripts/chart/acpl.js').then(function() {
li.advantageChart(data, ctrl.trans, $("#adv-chart")[0] as HTMLElement);
});
};
var storage = li.storage.make('analysis.panel');
var setPanel = function(panel) {
$menu.children('.active').removeClass('active').end().find('.' + panel).addClass('active');
$panels.removeClass('active').filter('.' + panel).addClass('active');
if (panel == 'move-times' && !li.movetimeChart) try {
li.loadScript('javascripts/chart/movetime.js').then(function() {
li.movetimeChart(data, ctrl.trans);
});
} catch (e) {}
if (panel == 'computer-analysis' && $("#adv-chart").length)
setTimeout(startAdvantageChart, 200);
};
$menu.on('mousedown', 'span', function(this: HTMLElement) {
var panel = $(this).data('panel');
storage.set(panel);
setPanel(panel);
});
var stored = storage.get();
if (stored && $menu.children('.' + stored).length) setPanel(stored);
else {
var $ct = $menu.children('.crosstable');
($ct.length ? $ct : $menu.children(':first-child')).trigger('mousedown');
}
if (!data.analysis) {
$panels.find('form.future-game-analysis').submit(function(this: HTMLElement) {
if ($(this).hasClass('must-login')) {
if (confirm(ctrl.trans('youNeedAnAccountToDoThat'))) location.href = '/signup';
return false;
}
$.ajax({
method: 'post',
url: $(this).attr('action'),
success: startAdvantageChart,
error: li.reload
});
return false;
});
}
$panels.on('click', '.pgn', function(this: HTMLElement) {
let selection = window.getSelection();
let range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
});
$panels.on('click', '.embed-howto', function(this: HTMLElement) {
var url = 'https://lichess.org/embed/' + data.game.id + location.hash;
var iframe = '<iframe src="' + url + '?theme=auto&bg=auto"\nwidth=600 height=397 frameborder=0></iframe>';
$.modal($(
'<strong style="font-size:1.5em">' + $(this).html() + '</strong><br /><br />' +
'<pre>' + li.escapeHtml(iframe) + '</pre><br />' +
iframe + '<br /><br />' +
'<a class="text" data-icon="" href="/developers#embed-game">Read more about embedding games</a>'
));
});
}

View File

@ -28,6 +28,7 @@ import AnalyseCtrl from './ctrl';
import { ConcealOf } from './interfaces';
import relayManager from './study/relay/relayManagerView';
import renderPlayerBars from './study/playerBars';
import underboard from './underboard';
const li = window.lichess;
@ -283,7 +284,6 @@ export default function(ctrl: AnalyseCtrl): VNode {
},
class: {
'gauge-on': gaugeOn,
'no_computer': !ctrl.showComputer(),
'gb_edit': !!gamebookEditView,
'gb_play': !!gamebookPlayView,
'relay_edit': !!relayEdit,
@ -316,7 +316,12 @@ export default function(ctrl: AnalyseCtrl): VNode {
]),
controls(ctrl),
ctrl.embed ? null : h('div.analyse__underboard', {
class: { no_computer: !ctrl.showComputer() }
class: { 'comp-off': !ctrl.showComputer() },
hook: {
insert(vnode) {
underboard(vnode.elm as HTMLElement, ctrl);
}
}
}, ctrl.study ? studyView.underboard(ctrl) : [inputs(ctrl)]),
h('div.analyse__acpl', [acplView(ctrl)]),
ctrl.embed || synthetic(ctrl.data) ? null : h('aside.analyse__side', {