move all round accessibility to a ui/round plugin
parent
9bccda5637
commit
bd600f1404
|
@ -240,21 +240,6 @@ object Round extends LilaController with TheftPrevention {
|
|||
}
|
||||
}
|
||||
|
||||
def playerNvui(fullId: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo pov fullId)(nvui(true))
|
||||
}
|
||||
|
||||
def watcherNvui(gameId: String, color: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo.pov(gameId, color))(nvui(false))
|
||||
}
|
||||
|
||||
private def nvui(playing: Boolean)(pov: Pov)(implicit ctx: Context): Fu[Result] =
|
||||
if (ctx.blind) negotiate(
|
||||
html = Ok(html.game.nvui.html(pov, playing)).fuccess,
|
||||
api = _ => Ok(html.game.nvui.json(pov)).fuccess
|
||||
)
|
||||
else BadRequest.fuccess
|
||||
|
||||
def sides(gameId: String, color: String) = Open { implicit ctx =>
|
||||
OptionFuResult(GameRepo.pov(gameId, color)) { pov =>
|
||||
(pov.game.tournamentId ?? lila.tournament.TournamentRepo.byId) zip
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
package views.html
|
||||
package game
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.game.Pov
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object nvui {
|
||||
|
||||
def json(pov: Pov)(implicit ctx: Context) = Json.obj(
|
||||
"pgn" -> pov.game.pgnMoves.mkString(" "),
|
||||
"fen" -> chess.format.Forsyth.>>(pov.game.chess),
|
||||
"status" -> povStatus(pov).toString,
|
||||
"lastMove" -> pov.game.pgnMoves.lastOption
|
||||
)
|
||||
|
||||
def html(pov: Pov, playing: Boolean)(implicit ctx: Context) = frag(
|
||||
h1("Textual representation"),
|
||||
dl(
|
||||
if (playing) frag(
|
||||
dt("Your color"),
|
||||
dd(pov.color.name),
|
||||
dt("Opponent"),
|
||||
dd(playerLink(pov.opponent))
|
||||
)
|
||||
else frag(
|
||||
dt("White player"),
|
||||
dd(playerLink(pov.game.whitePlayer)),
|
||||
dt("Black player"),
|
||||
dd(playerLink(pov.game.blackPlayer))
|
||||
),
|
||||
dt("PGN"),
|
||||
dd(
|
||||
cls := "pgn",
|
||||
role := "log",
|
||||
aria.live := "off",
|
||||
aria.relevant := "additions text"
|
||||
)(raw(pov.game.pgnMoves.mkString(" "))),
|
||||
dt("FEN"),
|
||||
dd(cls := "fen", aria.live := "off")(chess.format.Forsyth.>>(pov.game.chess)),
|
||||
dt("Game status"),
|
||||
dd(
|
||||
cls := "status",
|
||||
role := "status",
|
||||
aria.live := "assertive",
|
||||
aria.atomic := true
|
||||
)(povStatus(pov)),
|
||||
dt("Last move"),
|
||||
dd(
|
||||
cls := "lastMove",
|
||||
aria.live := "assertive",
|
||||
aria.atomic := true
|
||||
)(pov.game.pgnMoves.lastOption),
|
||||
(playing && pov.game.playable) option form(
|
||||
label(
|
||||
"Your move",
|
||||
input(name := "move", cls := "move", `type` := "text", cls := "", autocomplete := "off", autofocus := true)
|
||||
)
|
||||
),
|
||||
div(cls := "notify", aria.live := "assertive", aria.atomic := true)
|
||||
)
|
||||
)
|
||||
|
||||
private def povStatus(pov: Pov)(implicit ctx: Context) =
|
||||
if (pov.game.finishedOrAborted) gameEndStatus(pov.game)
|
||||
else "playing"
|
||||
}
|
|
@ -217,10 +217,6 @@ POST /$gameId<\w{8}>/delete controllers.Game.delete(gameId:
|
|||
GET /round-next/$gameId<\w{8}> controllers.Round.next(gameId: String)
|
||||
GET /whats-next/$fullId<\w{12}> controllers.Round.whatsNext(fullId: String)
|
||||
|
||||
# Round accessibility: text representation
|
||||
GET /$fullId<\w{12}>/nvui controllers.Round.playerNvui(fullId: String)
|
||||
GET /$gameId<\w{8}>/$color<white|black>/nvui controllers.Round.watcherNvui(gameId: String, color: String)
|
||||
|
||||
# Tournament
|
||||
GET /tournament controllers.Tournament.home(page: Int ?= 1)
|
||||
GET /tournament/featured controllers.Tournament.featured
|
||||
|
|
|
@ -53,8 +53,8 @@ interface Lichess {
|
|||
dispatchEvent(el: HTMLElement, eventName: string): void;
|
||||
isTrident: boolean;
|
||||
isMS: boolean;
|
||||
NVUI(element: HTMLElement, roundCtrl: any): {
|
||||
reload(): void;
|
||||
RoundNVUI(): {
|
||||
render(ctrl: any): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const plugins = [
|
|||
{
|
||||
entries: ['src/plugins/nvui.ts'],
|
||||
standalone: 'NVUI',
|
||||
target: 'nvui.min.js'
|
||||
target: 'round.nvui.min.js'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { h } from 'snabbdom'
|
||||
import { VNode } from 'snabbdom/vnode'
|
||||
import { render as renderGround } from './ground';
|
||||
import { renderClock } from './clock/clockView';
|
||||
import { renderInner as tableInner } from './view/table';
|
||||
import renderCorresClock from './corresClock/corresClockView';
|
||||
import { Position } from './interfaces';
|
||||
import RoundController from './ctrl';
|
||||
|
||||
let handler: any;
|
||||
|
||||
export function view(ctrl: RoundController): VNode {
|
||||
return h('div.blind', [
|
||||
h('div.textual', {
|
||||
hook: {
|
||||
insert: vnode => init(vnode.elm as HTMLElement, ctrl)
|
||||
}
|
||||
}, [ renderGround(ctrl) ]),
|
||||
h('dt', 'Your clock'),
|
||||
h('dd.botc', anyClock(ctrl, 'bottom')),
|
||||
h('dt', 'Opponent clock'),
|
||||
h('dd.topc', anyClock(ctrl, 'top')),
|
||||
h('dt', 'Actions'),
|
||||
h('dd.actions', tableInner(ctrl)),
|
||||
h('dt', 'Board'),
|
||||
h('dd.board', h('pre'))
|
||||
]);
|
||||
}
|
||||
|
||||
function anyClock(ctrl: RoundController, position: Position) {
|
||||
const d = ctrl.data, player = ctrl.playerAt(position);
|
||||
return (ctrl.clock && renderClock(ctrl, player, position)) || (
|
||||
d.correspondence && renderCorresClock(ctrl.corresClock!, ctrl.trans, player.color, position, d.game.player)
|
||||
) || 'none';
|
||||
}
|
||||
|
||||
function init(el: HTMLElement, ctrl: RoundController) {
|
||||
if (window.lichess.NVUI) handler = window.lichess.NVUI(el, ctrl);
|
||||
else window.lichess.loadScript('compiled/nvui.min.js').then(() => init(el, ctrl));
|
||||
}
|
||||
export function reload() {
|
||||
if (handler) handler.reload();
|
||||
}
|
|
@ -5,7 +5,6 @@ import { make as makeSocket, RoundSocket } from './socket';
|
|||
import * as title from './title';
|
||||
import * as promotion from './promotion';
|
||||
import * as blur from './blur';
|
||||
import * as blind from './blind';
|
||||
import * as cg from 'chessground/types';
|
||||
import { Config as CgConfig } from 'chessground/config';
|
||||
import { Api as CgApi } from 'chessground/api';
|
||||
|
@ -22,7 +21,7 @@ import renderUser = require('./view/user');
|
|||
import cevalSub = require('./cevalSub');
|
||||
import * as keyboard from './keyboard';
|
||||
|
||||
import { RoundOpts, RoundData, ApiMove, ApiEnd, Redraw, SocketMove, SocketDrop, SocketOpts, MoveMetadata, Position } from './interfaces';
|
||||
import { RoundOpts, RoundData, ApiMove, ApiEnd, Redraw, SocketMove, SocketDrop, SocketOpts, MoveMetadata, Position, Blind } from './interfaces';
|
||||
|
||||
interface GoneBerserk {
|
||||
white?: boolean;
|
||||
|
@ -65,6 +64,7 @@ export default class RoundController {
|
|||
shouldSendMoveTime: boolean = false;
|
||||
preDrop?: cg.Role;
|
||||
lastDrawOfferAtPly?: Ply;
|
||||
blind?: Blind;
|
||||
|
||||
private music?: any;
|
||||
|
||||
|
@ -118,6 +118,10 @@ export default class RoundController {
|
|||
});
|
||||
if (li.ab && this.isPlaying()) li.ab.init(this);
|
||||
|
||||
if (d.blind) window.lichess.loadScript('compiled/round.nvui.min.js').then(() => {
|
||||
this.blind = window.lichess.RoundNVUI();
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
private showExpiration = () => {
|
||||
|
@ -313,7 +317,7 @@ export default class RoundController {
|
|||
});
|
||||
};
|
||||
|
||||
private playerByColor = (c: Color) =>
|
||||
playerByColor = (c: Color) =>
|
||||
this.data[c === this.data.player.color ? 'player' : 'opponent'];
|
||||
|
||||
apiMove = (o: ApiMove): void => {
|
||||
|
@ -404,7 +408,6 @@ export default class RoundController {
|
|||
else this.data.expiration.movedAt = Date.now();
|
||||
}
|
||||
this.redraw();
|
||||
if (d.blind) blind.reload();
|
||||
if (playing && playedColor === d.player.color) {
|
||||
this.moveOn.next();
|
||||
cevalSub.publish(d, o);
|
||||
|
@ -449,7 +452,6 @@ export default class RoundController {
|
|||
if (this.corresClock) this.corresClock.update(d.correspondence.white, d.correspondence.black);
|
||||
if (!this.replaying()) ground.reload(this);
|
||||
this.setTitle();
|
||||
if (d.blind) blind.reload();
|
||||
this.moveOn.next();
|
||||
this.setQuietMode();
|
||||
this.redraw();
|
||||
|
|
|
@ -159,6 +159,9 @@ export interface ApiEnd {
|
|||
export interface StepCrazy extends Untyped {
|
||||
}
|
||||
|
||||
export interface Blind extends Untyped {
|
||||
}
|
||||
|
||||
export interface Pref {
|
||||
animationDuration: number;
|
||||
autoQueen: 1 | 2 | 3;
|
||||
|
|
|
@ -1,71 +1,122 @@
|
|||
import { h } from 'snabbdom'
|
||||
import sanWriter from './sanWriter';
|
||||
import RoundController from '../ctrl';
|
||||
import { router } from 'game';
|
||||
import { throttle } from 'common';
|
||||
import { renderClock } from '../clock/clockView';
|
||||
import { renderInner as tableInner } from '../view/table';
|
||||
import renderCorresClock from '../corresClock/corresClockView';
|
||||
import { userHtml } from '../view/user';
|
||||
import { plyStep } from '../round';
|
||||
import { DecodedDests } from '../interfaces';
|
||||
import { DecodedDests, Position } from '../interfaces';
|
||||
import { files } from 'chessground/types';
|
||||
import { invRanks } from 'chessground/util';
|
||||
import * as xhr from '../xhr';
|
||||
import { view as gameView } from 'game';
|
||||
|
||||
type Sans = {
|
||||
[key: string]: Uci;
|
||||
}
|
||||
|
||||
window.lichess.NVUI = function(element: HTMLElement, ctrl: RoundController) {
|
||||
|
||||
let $form: JQuery;
|
||||
|
||||
const currentFen = () => plyStep(ctrl.data, ctrl.ply).fen
|
||||
|
||||
const reload = (first: boolean = false) => {
|
||||
$.ajax({
|
||||
url: (ctrl.data.player.spectator ?
|
||||
router.game(ctrl.data, ctrl.data.player.color) :
|
||||
router.player(ctrl.data)
|
||||
) + '/nvui',
|
||||
headers: first ? {} : xhr.headers,
|
||||
success(res) {
|
||||
if (first) {
|
||||
$(element).html(res);
|
||||
$form = $(element).find('form').submit(function() {
|
||||
const input = $form.find('.move').val();
|
||||
const legalUcis = destsToUcis(ctrl.chessground.state.movable.dests!);
|
||||
const sans: Sans = sanWriter(currentFen(), legalUcis) as Sans;
|
||||
const uci = sanToUci(input, sans) || input;
|
||||
if (legalUcis.indexOf(uci.toLowerCase()) >= 0) ctrl.socket.send("move", {
|
||||
from: uci.substr(0, 2),
|
||||
to: uci.substr(2, 2),
|
||||
promotion: uci.substr(4, 1)
|
||||
}, { ackable: true });
|
||||
else {
|
||||
$(element).find('.notify').text('Invalid move');
|
||||
}
|
||||
$form.find('.move').val('');
|
||||
return false;
|
||||
});
|
||||
$form.find('.move').val('').focus();
|
||||
}
|
||||
else {
|
||||
$(element).find('.notify').text('');
|
||||
['pgn', 'fen', 'status', 'lastMove'].forEach(r => {
|
||||
if ($(element).find('.' + r).text() != res[r]) {
|
||||
$(element).find('.' + r).text(res[r]);
|
||||
}
|
||||
});
|
||||
}
|
||||
$(element).siblings('.board').find('pre').text(textBoard(ctrl));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reload(true);
|
||||
window.lichess.RoundNVUI = function() {
|
||||
|
||||
return {
|
||||
reload: throttle(1000, reload)
|
||||
render(ctrl: RoundController) {
|
||||
const d = ctrl.data,
|
||||
step = plyStep(d, ctrl.ply);
|
||||
return h('div.nvui', [
|
||||
h('h1', 'Textual representation'),
|
||||
h('dl', [
|
||||
...(ctrl.isPlaying() ? [
|
||||
h('dt', 'Your color'),
|
||||
h('dd', d.player.color),
|
||||
h('dt', 'Opponent'),
|
||||
h('dd', userHtml(ctrl, d.player))
|
||||
] : [
|
||||
h('dt', 'White player'),
|
||||
h('dd', userHtml(ctrl, ctrl.playerByColor('white'))),
|
||||
h('dt', 'Black player'),
|
||||
h('dd', userHtml(ctrl, ctrl.playerByColor('black')))
|
||||
]),
|
||||
h('dt', 'PGN'),
|
||||
h('dd.pgn', {
|
||||
attrs: { role : 'log' }
|
||||
}, d.steps.map(s => s.san).join(' ')),
|
||||
h('dt', 'FEN'),
|
||||
// h('dd.fen')(step.fen),
|
||||
h('dt', 'Game status'),
|
||||
h('dd.status', {
|
||||
attrs: {
|
||||
role : 'status',
|
||||
'aria.live' : 'assertive',
|
||||
'aria.atomic' : true
|
||||
}
|
||||
}, ctrl.data.game.status.name === 'started' ? 'Playing' : gameView.status(ctrl)),
|
||||
h('dt', 'Last move'),
|
||||
h('dd.lastMove', {
|
||||
attrs: {
|
||||
'aria.live' : 'assertive',
|
||||
'aria.atomic' : true
|
||||
}
|
||||
}, step.san),
|
||||
ctrl.isPlaying() ? h('form', {
|
||||
hook: {
|
||||
insert(vnode) {
|
||||
const el = vnode.elm as HTMLFormElement;
|
||||
const d = ctrl.data;
|
||||
const $form = $(el).submit(function() {
|
||||
const input = $form.find('.move').val();
|
||||
const legalUcis = destsToUcis(ctrl.chessground.state.movable.dests!);
|
||||
const sans: Sans = sanWriter(plyStep(d, ctrl.ply).fen, legalUcis) as Sans;
|
||||
const uci = sanToUci(input, sans) || input;
|
||||
if (legalUcis.indexOf(uci.toLowerCase()) >= 0) ctrl.socket.send("move", {
|
||||
from: uci.substr(0, 2),
|
||||
to: uci.substr(2, 2),
|
||||
promotion: uci.substr(4, 1)
|
||||
}, { ackable: true });
|
||||
else {
|
||||
$(el).siblings('.notify').text('Invalid move');
|
||||
}
|
||||
$form.find('.move').val('');
|
||||
return false;
|
||||
});
|
||||
$form.find('.move').val('').focus();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
h('label', [
|
||||
'Your move',
|
||||
h('input.move', {
|
||||
attrs: {
|
||||
name : 'move',
|
||||
'type' : 'text',
|
||||
autocomplete : 'off'
|
||||
}
|
||||
})
|
||||
])
|
||||
]) : null,
|
||||
h('dt', 'Your clock'),
|
||||
h('dd.botc', anyClock(ctrl, 'bottom')),
|
||||
h('dt', 'Opponent clock'),
|
||||
h('dd.topc', anyClock(ctrl, 'top')),
|
||||
h('dt', 'Actions'),
|
||||
h('dd.actions', tableInner(ctrl)),
|
||||
h('dt', 'Board'),
|
||||
h('dd', h('pre', textBoard(ctrl))),
|
||||
h('div.notify', {
|
||||
'aria.live': "assertive",
|
||||
'aria.atomic' : true
|
||||
})
|
||||
])
|
||||
]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function anyClock(ctrl: RoundController, position: Position) {
|
||||
const d = ctrl.data, player = ctrl.playerAt(position);
|
||||
return (ctrl.clock && renderClock(ctrl, player, position)) || (
|
||||
d.correspondence && renderCorresClock(ctrl.corresClock!, ctrl.trans, player.color, position, d.game.player)
|
||||
) || undefined;
|
||||
}
|
||||
|
||||
function destsToUcis(dests: DecodedDests) {
|
||||
const ucis: string[] = [];
|
||||
Object.keys(dests).forEach(function(orig) {
|
||||
|
|
|
@ -6,7 +6,6 @@ import * as promotion from '../promotion';
|
|||
import { render as renderGround } from '../ground';
|
||||
import { read as fenRead } from 'chessground/fen';
|
||||
import * as util from '../util';
|
||||
import * as blind from '../blind';
|
||||
import * as keyboard from '../keyboard';
|
||||
import crazyView from '../crazy/crazyView';
|
||||
import { render as keyboardMove } from '../keyboardMove';
|
||||
|
@ -53,7 +52,7 @@ export function main(ctrl: RoundController): VNode {
|
|||
material = util.getMaterialDiff(pieces);
|
||||
score = util.getScore(pieces) * (bottomColor === 'white' ? 1 : -1);
|
||||
} else material = emptyMaterialDiff;
|
||||
return d.blind ? blind.view(ctrl) : h('div.round.cg-512', [
|
||||
return ctrl.blind ? ctrl.blind.render(ctrl) : h('div.round.cg-512', [
|
||||
h('div.lichess_game.gotomove.variant_' + d.game.variant.key + (ctrl.data.pref.blindfold ? '.blindfold' : ''), {
|
||||
hook: {
|
||||
insert: () => window.lichess.pubsub.emit('content_loaded')()
|
||||
|
|
Loading…
Reference in New Issue