progress on game UI with mithril

This commit is contained in:
Thibault Duplessis 2014-10-02 00:19:49 +02:00
parent 7028225d05
commit 2a293bfc8a
21 changed files with 375 additions and 154 deletions

View file

@ -55,28 +55,18 @@ object Round extends LilaController with TheftPrevention {
negotiate(
html = pov.game.started.fold(
PreventTheft(pov) {
env.version(pov.gameId) zip
pov.opponent.userId.??(UserRepo.isEngine) zip
(pov.game.tournamentId ?? TournamentRepo.byId) zip
(pov.game.tournamentId ?? TournamentRepo.byId) zip
Env.game.crosstableApi(pov.game) zip
(pov.game.hasChat optionFu {
Env.chat.api.playerChat find pov.gameId map (_ forUser ctx.me)
}) zip
(pov.game.playable ?? env.takebacker.isAllowedByPrefs(pov.game)) map {
case (((((v, engine), tour), crosstable), chat), takebackable) =>
Ok(html.round.player(pov, v, engine,
chat = chat, tour = tour, cross = crosstable, takebackable = takebackable))
(pov.game.playable ?? env.takebacker.isAllowedByPrefs(pov.game)) flatMap {
case ((tour, crosstable), takebackable) =>
env.jsonView.playerJson(pov, ctx.pref, Env.api.version, ctx.me) map { data =>
Ok(html.round.player(pov, data, tour = tour, cross = crosstable, takebackable = takebackable))
}
}
},
Redirect(routes.Setup.await(fullId)).fuccess
),
api = apiVersion => (Env.round version pov.gameId) zip
(pov.game.hasChat optionFu {
Env.chat.api.playerChat find pov.gameId map (_ forUser ctx.me)
}) map {
case (v, chat) =>
Ok(Env.round.jsonView.playerJson(pov, v, ctx.pref, chat, apiVersion))
}
api = apiVersion => env.jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me) map { Ok(_) }
)
}
}
@ -102,7 +92,7 @@ object Round extends LilaController with TheftPrevention {
}
},
api = apiVersion => Env.round version pov.gameId map { v =>
Ok(Env.round.jsonView.watcherJson(pov, v, tv = false, pref = ctx.pref))
Ok(env.jsonView.watcherJson(pov, v, tv = false, pref = ctx.pref))
})
private def join(pov: Pov)(implicit ctx: Context): Fu[Result] =

View file

@ -177,8 +177,8 @@ object Setup extends LilaController with TheftPrevention with play.api.http.Cont
config => op(config)(ctx) flatMap {
case (pov, call) => negotiate(
html = fuccess(redirectPov(pov, call)),
api = apiVersion => Env.round version pov.gameId map { v =>
Created(Env.round.jsonView.playerJson(pov, v, ctx.pref, chat = none, apiVersion)) as JSON
api = apiVersion => Env.round.jsonView.playerJson(pov, ctx.pref, apiVersion, ctx.me) map { data =>
Created(data) as JSON
}
)
}

View file

@ -1,29 +1,32 @@
@(pov: Pov, version: Int, engine: Boolean, chat: Option[lila.chat.MixedChat], tour: Option[lila.tournament.Tournament], cross: Option[lila.game.Crosstable], takebackable: Boolean)(implicit ctx: Context)
@(pov: Pov, data: play.api.libs.json.JsObject, tour: Option[lila.tournament.Tournament], cross: Option[lila.game.Crosstable], takebackable: Boolean)(implicit ctx: Context)
@import pov._
@title = @{ s"${trans.play.str()} ${playerText(pov.opponent)} in $gameId" }
@moreJs = {
@*
@jsAt(s"compiled/lichess.round${isProd??(".min")}.js")
*@
@jsAt(s"compiled/lichess.round.js")
@helper.javascriptRouter("roundRoutes")(
routes.javascript.Auth.signup
)(ctx.req)
@embedJs {
LichessRound(
document.getElementById('lichess'),
@Html(play.api.libs.json.Json.stringify(roundPlayerJsData(pov, version, ctx.pref, apiVersion))),
roundRoutes.controllers,
@Html(J.stringify(i18nJsObject(
lichess = lichess || {};
lichess.round = {
data: @Html(play.api.libs.json.Json.stringify(data)),
routes: roundRoutes.controllers,
i18n: @Html(J.stringify(i18nJsObject(
trans.premoveEnabledClickAnywhereToCancel)))
);
};
}
}
@round.layout(
title = title,
side = views.html.game.side(pov, tour, withTourStanding = true),
chat = chat.map(c => base.chat(c, trans.chatRoom.str())),
chat = none,
underchat = views.html.game.watchers().some,
moreJs = moreJs,
openGraph = povOpenGraph(pov)) {

View file

@ -42,5 +42,7 @@ signedJs = routes.Round.signedJs(pov.gameId).toString.some) {
</div>
</div>
</div>
@*
@embedJs("var _ld_ = " + roundPlayerJsData(pov, version, ctx.pref, apiVersion))
*@
}

View file

@ -3,15 +3,15 @@
mkdir -p public/compiled
cd ui/editor
npm install
gulp prod
cd -
# cd ui/editor
# npm install
# gulp prod
# cd -
cd ui/puzzle
npm install
gulp prod
cd -
# cd ui/puzzle
# npm install
# gulp prod
# cd -
for file in tv.js common.js big.js chart2.js user.js coordinate.js pgn4hacks.js; do
orig=public/javascripts/$file

View file

@ -6,7 +6,7 @@ import reactivemongo.bson.BSONDocument
import lila.db.Types.Coll
import lila.user.{ User, UserRepo }
private[chat] final class ChatApi(
final class ChatApi(
coll: Coll,
flood: lila.security.Flood,
maxLinesPerChat: Int,

View file

@ -22,9 +22,10 @@ object PlayApp {
enabled = enableScheduler && isServer,
debug = loadConfig getBoolean "app.scheduler.debug")
def isDev = isMode(_.Dev)
def isTest = isMode(_.Test)
def isProd = isMode(_.Prod)
lazy val isDev = isMode(_.Dev)
lazy val isTest = isMode(_.Test)
lazy val isProd = isMode(_.Prod) && !loadConfig.getBoolean("forcedev")
def isServer = !isTest
def isMode(f: Mode.type => Mode.Mode) = withApp { _.mode == f(Mode) }
}

View file

@ -44,7 +44,6 @@ object Event {
case class Move(orig: Pos, dest: Pos, color: Color) extends Event {
def typ = "move"
def data = Json.obj(
"type" -> "move",
"from" -> orig.key,
"to" -> dest.key,
"color" -> color.name)

View file

@ -20,11 +20,13 @@ final class Env(
aiPerfApi: lila.ai.AiPerfApi,
crosstableApi: lila.game.CrosstableApi,
lightUser: String => Option[lila.common.LightUser],
userJsonView: lila.user.JsonView,
uciMemo: lila.game.UciMemo,
rematch960Cache: lila.memo.ExpireSetMemo,
onStart: String => Unit,
i18nKeys: lila.i18n.I18nKeys,
prefApi: lila.pref.PrefApi,
chatApi: lila.chat.ChatApi,
historyApi: lila.history.HistoryApi,
scheduler: lila.common.Scheduler) {
@ -153,7 +155,11 @@ final class Env(
private[round] def moretimeSeconds = Moretime.toSeconds
lazy val jsonView = new JsonView(AnimationDuration)
lazy val jsonView = new JsonView(
chatApi = chatApi,
userJsonView = userJsonView,
getVersion = version,
baseAnimationDuration = AnimationDuration)
{
import scala.concurrent.duration._
@ -192,11 +198,13 @@ object Env {
aiPerfApi = lila.ai.Env.current.aiPerfApi,
crosstableApi = lila.game.Env.current.crosstableApi,
lightUser = lila.user.Env.current.lightUser,
userJsonView = lila.user.Env.current.jsonView,
uciMemo = lila.game.Env.current.uciMemo,
rematch960Cache = lila.game.Env.current.cached.rematch960,
onStart = lila.game.Env.current.onStart,
i18nKeys = lila.i18n.Env.current.keys,
prefApi = lila.pref.Env.current.api,
chatApi = lila.chat.Env.current.api,
historyApi = lila.history.Env.current.api,
scheduler = lila.common.PlayApp.scheduler)
}

View file

@ -8,78 +8,90 @@ import play.api.libs.json._
import lila.common.PimpedJson._
import lila.game.{ Pov, Game, PerfPicker }
import lila.pref.Pref
import lila.user.{ User, UserRepo }
import chess.format.Forsyth
import chess.{ Color, Clock }
final class JsonView(baseAnimationDuration: Duration) {
final class JsonView(
chatApi: lila.chat.ChatApi,
userJsonView: lila.user.JsonView,
getVersion: String => Fu[Int],
baseAnimationDuration: Duration) {
def playerJson(pov: Pov, version: Int, pref: Pref, chat: Option[lila.chat.MixedChat], apiVersion: Int) = {
import pov._
Json.obj(
"game" -> Json.obj(
"id" -> gameId,
"variant" -> game.variant.key,
"speed" -> game.speed.key,
"perf" -> PerfPicker.key(game),
"rated" -> game.rated,
"fen" -> (Forsyth >> game.toChess),
"moves" -> game.pgnMoves.mkString(" "),
"started" -> game.started,
"finished" -> game.finishedOrAborted,
"clock" -> game.hasClock,
"clockRunning" -> game.isClockRunning,
"player" -> game.turnColor.name,
"turns" -> game.turns,
"startedAtTurn" -> game.startedAtTurn,
"lastMove" -> game.castleLastMoveTime.lastMoveString),
"clock" -> game.clock.map(clockJson),
"player" -> Json.obj(
"id" -> playerId,
"color" -> player.color.name,
"version" -> version,
"spectator" -> false,
"isOfferingRematch" -> player.isOfferingRematch.option(true),
"isOfferingDraw" -> player.isOfferingDraw.option(true),
"isProposingTakeback" -> player.isProposingTakeback.option(true)
).noNull,
"opponent" -> Json.obj(
"color" -> opponent.color.name,
"ai" -> opponent.aiLevel,
"userId" -> opponent.userId,
"isOfferingRematch" -> opponent.isOfferingRematch.option(true),
"isOfferingDraw" -> opponent.isOfferingDraw.option(true),
"isProposingTakeback" -> opponent.isProposingTakeback.option(true)
).noNull,
"url" -> Json.obj(
"pov" -> s"/$fullId",
"socket" -> s"/$fullId/socket/v$apiVersion",
"end" -> s"/$fullId/end",
"table" -> s"/$fullId/table"
),
"pref" -> Json.obj(
"animationDuration" -> animationDuration(pov, pref),
"autoQueen" -> pref.autoQueen,
"autoThreefold" -> pref.autoThreefold,
"clockTenths" -> pref.clockTenths,
"clockBar" -> pref.clockBar,
"enablePremove" -> pref.premove
),
"chat" -> chat.map { c =>
JsArray(c.lines map {
case lila.chat.UserLine(username, text, _) => Json.obj(
"u" -> username,
"t" -> text)
case lila.chat.PlayerLine(color, text) => Json.obj(
"c" -> color.name,
"t" -> text)
})
},
"possibleMoves" -> possibleMoves(pov),
"tournamentId" -> game.tournamentId,
"poolId" -> game.poolId
)
}
def playerJson(
pov: Pov,
pref: Pref,
apiVersion: Int,
playerUser: Option[User]): Fu[JsObject] =
getVersion(pov.game.id) zip
(pov.opponent.userId ?? UserRepo.byId) zip
getChat(pov.game, playerUser) map {
case ((version, opponentUser), chat) =>
import pov._
Json.obj(
"game" -> Json.obj(
"id" -> gameId,
"variant" -> game.variant.key,
"speed" -> game.speed.key,
"perf" -> PerfPicker.key(game),
"rated" -> game.rated,
"fen" -> (Forsyth >> game.toChess),
"moves" -> game.pgnMoves.mkString(" "),
"started" -> game.started,
"finished" -> game.finishedOrAborted,
"clock" -> game.hasClock,
"clockRunning" -> game.isClockRunning,
"player" -> game.turnColor.name,
"turns" -> game.turns,
"startedAtTurn" -> game.startedAtTurn,
"lastMove" -> game.castleLastMoveTime.lastMoveString),
"clock" -> game.clock.map(clockJson),
"player" -> Json.obj(
"id" -> playerId,
"color" -> player.color.name,
"version" -> version,
"spectator" -> false,
"user" -> playerUser.map { userJsonView(_, true) },
"isOfferingRematch" -> player.isOfferingRematch.option(true),
"isOfferingDraw" -> player.isOfferingDraw.option(true),
"isProposingTakeback" -> player.isProposingTakeback.option(true)
).noNull,
"opponent" -> Json.obj(
"color" -> opponent.color.name,
"ai" -> opponent.aiLevel,
"user" -> opponentUser.map { userJsonView(_, true) },
"isOfferingRematch" -> opponent.isOfferingRematch.option(true),
"isOfferingDraw" -> opponent.isOfferingDraw.option(true),
"isProposingTakeback" -> opponent.isProposingTakeback.option(true)
).noNull,
"url" -> Json.obj(
"socket" -> s"/$fullId/socket/v$apiVersion"
),
"pref" -> Json.obj(
"animationDuration" -> animationDuration(pov, pref),
"highlight" -> pref.highlight,
"destination" -> pref.destination,
"autoQueen" -> pref.autoQueen,
"autoThreefold" -> pref.autoThreefold,
"clockTenths" -> pref.clockTenths,
"clockBar" -> pref.clockBar,
"enablePremove" -> pref.premove
),
"chat" -> chat.map { c =>
JsArray(c.lines map {
case lila.chat.UserLine(username, text, _) => Json.obj(
"u" -> username,
"t" -> text)
case lila.chat.PlayerLine(color, text) => Json.obj(
"c" -> color.name,
"t" -> text)
})
},
"possibleMoves" -> possibleMoves(pov),
"tournamentId" -> game.tournamentId,
"poolId" -> game.poolId)
}
def watcherJson(pov: Pov, version: Int, tv: Boolean, pref: Pref) = {
import pov._
@ -121,6 +133,14 @@ final class JsonView(baseAnimationDuration: Duration) {
)
}
private def getChat(game: Game, forUser: Option[User]) = game.hasChat optionFu {
chatApi.playerChat find game.id map (_ forUser forUser)
}
private def getUsers(game: Game) = UserRepo.pair(
game.whitePlayer.userId,
game.blackPlayer.userId)
private def clockJson(clock: Clock) = Json.obj(
"initial" -> clock.limit,
"increment" -> clock.increment,

View file

@ -12,9 +12,6 @@ trait RoundHelper {
def moretimeSeconds = roundEnv.moretimeSeconds
def roundPlayerJsData(pov: Pov, version: Int, pref: Pref, apiVersion: Int) =
roundEnv.jsonView.playerJson(pov, version, pref, chat = none, apiVersion = apiVersion)
def roundWatcherJsData(pov: Pov, version: Int, tv: Boolean, pref: Pref) =
roundEnv.jsonView.watcherJson(pov, version, tv, pref)
}

View file

@ -338,8 +338,7 @@ var storage = {
}
// Start game
var $game = $('div.lichess_game').orNot();
if ($game && false) $game.game(_ld_);
if (lichess.round) $('#lichess').game(lichess.round);
setTimeout(function() {
if (lichess.socket === null) {
@ -650,6 +649,23 @@ var storage = {
$.widget("lichess.game", {
_init: function() {
var cfg = this.options;
lichess.socket = new lichess.StrongSocket(
cfg.data.url.socket,
cfg.data.player.version,
{
options: {
name: "round"
},
params: {
ran: "--ranph--",
userTv: $('.user_tv').data('user-tv')
},
receive: function(t, d) { round.socketReceive(t, d); }
});
var round = LichessRound(this.element[0], cfg.data, cfg.routes, cfg.i18n, lichess.socket.send.bind(lichess.socket));
},
_old_init: function() {
var self = this;
self.$board = self.element.find("div.lichess_board");
self.$table = self.element.find("div.lichess_table_wrap");

View file

@ -111,9 +111,15 @@ lichess.StrongSocket.prototype = {
}
self.scheduleConnect(self.options.pingMaxLag);
},
send: function(t, d) {
send: function(t, d, o) {
var self = this;
var data = d || {};
var data = d || {},
options = o || {};
if (options && options.ackable)
self.ackableMessages.push({
t: t,
d: d
});
var message = JSON.stringify({
t: t,
d: data
@ -126,11 +132,9 @@ lichess.StrongSocket.prototype = {
}
},
sendAckable: function(t, d) {
this.ackableMessages.push({
t: t,
d: d
this.send(t, d, {
ackable: true
});
this.send(t, d);
},
scheduleConnect: function(delay) {
var self = this;
@ -202,10 +206,12 @@ lichess.StrongSocket.prototype = {
self.ackableMessages = [];
break;
default:
var h = self.settings.events[m.t];
if ($.isFunction(h)) h(m.d || null);
else if (!self.options.ignoreUnknownMessages) {
self.debug('Message not supported ' + JSON.stringify(m));
if (!self.settings.receive || !self.settings.receive(m.t, m.d)) {
var h = self.settings.events[m.t];
if ($.isFunction(h)) h(m.d || null);
else if (!self.options.ignoreUnknownMessages) {
self.debug('Message not supported ' + JSON.stringify(m));
}
}
}
},

View file

@ -30,8 +30,8 @@
"watchify": "^1.0.2"
},
"dependencies": {
"chessground": "git://github.com/ornicar/chessground#4821c08",
"chessground": "git://github.com/ornicar/chessground.git",
"lodash-node": "^2.4.1",
"mithril": "git://github.com/lhorie/mithril.js.git#c16350de5ab64118"
"mithril": "^0.1.22"
}
}

View file

@ -0,0 +1,28 @@
module.exports = function(data) {
var lastUpdate;
this.data = data;
this.data.barTime = Math.max(this.data.initial, 2) + 5 * this.data.increment;
function setLastUpdate() {
lastUpdate = {
white: data.white,
black: data.black,
at: new Date()
};
}
setLastUpdate();
this.update = function(white, black) {
this.data.white = white;
this.data.black = black;
setLastUpdate();
};
this.tick = function(color) {
m.startComputation();
this.data[color] = lastUpdate[color] - (new Date() - lastUpdate.at) / 1000;
m.endComputation();
}.bind(this);
}

View file

@ -0,0 +1,44 @@
var classSet = require('chessground').util.classSet;
function prefixInteger(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
function bold(x) {
return '<b>' + x + '</b>';
}
function formatClockTime(ctrl, time) {
var date = new Date(time);
var minutes = prefixInteger(date.getUTCMinutes(), 2);
var seconds = prefixInteger(date.getSeconds(), 2);
if (ctrl.data.showTenths && time < 10000) {
tenths = Math.floor(date.getMilliseconds() / 100);
return bold(minutes) + ':' + bold(seconds) + '<span>.' + bold(tenths) + '</span>';
} else if (time >= 3600000) {
var hours = prefixInteger(date.getUTCHours(), 2);
return bold(hours) + ':' + bold(minutes) + ':' + bold(seconds);
} else {
return bold(minutes) + ':' + bold(seconds);
}
}
module.exports = function(ctrl, color, position, runningColor) {
var time = ctrl.data[color];
return m('div', {
class: 'clock clock_' + color + ' clock_' + position + ' ' + classSet({
'outoftime': !time,
'running': runningColor === color,
'emerg': time < ctrl.data.emerg
})
}, [
ctrl.data.showBar ? m('div.bar',
m('span', {
style: {
width: Math.max(0, Math.min(100, (time / ctrl.data.barTime) * 100)) + 'px'
}
})
) : null,
m('div.time', m.trust(formatClockTime(ctrl, time * 1000)))
]);
}

View file

@ -4,21 +4,41 @@ var chessground = require('chessground');
var data = require('./data');
var round = require('./round');
var socket = require('./socket');
var clockCtrl = require('./clock/ctrl');
var util = require('./util');
module.exports = function(cfg, router, i18n) {
module.exports = function(cfg, router, i18n, socketSend) {
this.data = data(cfg);
this.socket = new socket(socketSend, this);
this.userMove = function(orig, dest) {
var move = {
from: orig,
to: dest,
};
if (this.clock) move.lag = Math.round(lichess.socket.averageLag);
this.socket.send('move', move, {
ackable: true
});
}.bind(this);
this.chessground = new chessground.controller({
fen: cfg.game.fen,
orientation: this.data.player.color,
turnColor: this.data.game.player,
lastMove: util.str2move(this.data.game.lastMove),
highlight: {
lastMove: this.data.pref.highlight,
check: this.data.pref.highlight,
dragOver: true
},
movable: {
free: false,
color: round.isPlayerPlaying(this.data) ? this.data.player.color : null,
dests: round.parsePossibleMoves(this.data.possibleMoves),
showDests: this.data.pref.destination,
events: {
after: this.userMove
},
@ -28,24 +48,25 @@ module.exports = function(cfg, router, i18n) {
duration: this.data.pref.animationDuration
},
premovable: {
enabled: this.data.pref.enablePremove
enabled: this.data.pref.enablePremove,
showDests: this.data.pref.destination
}
});
this.socket = window.lichess.socket = socket.make(this.data);
this.clock = this.data.clock ? new clockCtrl(this.data.clock) : false;
this.userMove = function(orig, dest) {
console.log('userMove', [orig, dest]);
this.isClockRunning = function() {
return !this.data.game.finished && ((this.data.game.turns - this.data.game.startedAtTurn) > 1 || this.data.game.clockRunning);
}.bind(this);
this.clockTick = function() {
if (this.isClockRunning()) this.clock.tick(this.data.game.player);
}.bind(this);
if (this.clock) setInterval(this.clockTick, 100);
this.router = router;
this.costly = function(cell) {
return (this.chessground.data.draggable.current.orig || this.chessground.data.animation.current.start) ? {
subtree: 'retain'
} : cell();
}.bind(this);
this.trans = function() {
var str = i18n[arguments[0]]
Array.prototype.slice.call(arguments, 1).forEach(function(arg) {

View file

@ -7,5 +7,10 @@ module.exports = function(cfg) {
merge(data, cfg);
if (data.clock) {
data.clock.showTenths = data.pref.clockTenths;
data.clock.showBar = data.pref.clockBar;
}
return data;
};

View file

@ -1,10 +1,16 @@
var ctrl = require('./ctrl');
var view = require('./view');
module.exports = function(element, config, router, i18n) {
var controller = new ctrl(config, router, i18n);
module.exports = function(element, config, router, i18n, socketSend) {
var controller = new ctrl(config, router, i18n, socketSend);
m.module(element, {
controller: function () { return controller; },
view: view
});
return {
socketReceive: controller.socket.receive
};
};

View file

@ -1,17 +1,77 @@
function make(data) {
var m = require('mithril');
var round = require('./round');
var ground = require('chessground');
return new lichess.StrongSocket(data.url.socket, data.player.version, {
options: {
name: "game"
module.exports = function(send, ctrl) {
this.send = send;
var handlers = {
possibleMoves: function(o) {
ctrl.chessground.reconfigure({
movable: {
dests: round.parsePossibleMoves(o)
}
});
},
params: {
ran: "--ranph--",
userTv: $('.user_tv').data('user-tv')
state: function(o) {
ctrl.chessground.reconfigure({
turnColor: o.color
});
ctrl.data.game.player = o.color;
ctrl.data.game.turns = o.turns;
},
events: {}
});
move: function(o) {
ctrl.chessground.apiMove(o.from, o.to);
},
premove: function() {
ctrl.chessground.playPremove();
},
castling: function(o) {
var pieces = {};
pieces[o.king[0]] = null;
pieces[o.king[1]] = {
role: 'king',
color: o.color
};
pieces[o.rook[0]] = null;
pieces[o.rook[1]] = {
role: 'rook',
color: o.color
};
ctrl.chessground.setPieces(pieces);
},
check: function(o) {
ctrl.chessground.reconfigure({
check: o
});
},
enpassant: function(o) {
var pieces = {};
pieces.o = null;
ctrl.chessground.setPieces(pieces);
},
// still used by rematch join
redirect: function(o) {
setTimeout(function() {
lichess.hasToReload = true;
$.redirect(o);
}, 400);
},
threefoldRepetition: function() {
// ???
},
clock: function(o) {
if (ctrl.clock) ctrl.clock.update(o.white, o.black);
}
};
this.receive = function(type, data) {
console.log(type, data);
if (handlers[type]) {
handlers[type](data);
return true;
}
return false;
}.bind(this);
}
module.exports = {
make: make
};

View file

@ -1,9 +1,13 @@
var partial = require('lodash-node/modern/functions/partial');
var map = require('lodash-node/modern/collections/map');
var chessground = require('chessground');
var opposite = chessground.util.opposite;
var classSet = chessground.util.classSet;
var partial = chessground.util.partial;
var clockView = require('./clock/view');
var m = require('mithril');
module.exports = function(ctrl) {
var clockRunningColor = ctrl.isClockRunning() ? ctrl.data.game.player : null;
return m('div', {
class: 'lichess_game clearfix not_spectator pov_' + ctrl.data.player.color
}, [
@ -11,6 +15,17 @@ module.exports = function(ctrl) {
m('div.lichess_board_wrap', ctrl.data.blindMode ? null : [
m('div.lichess_board.' + ctrl.data.game.variant, chessground.view(ctrl.chessground)),
m('div#premove_alert', ctrl.trans('premoveEnabledClickAnywhereToCancel'))
])
]),
m('div.lichess_ground',
m('div.lichess_table_wrap', [
(ctrl.clock && !ctrl.data.blindMode) ? clockView(ctrl.clock, opposite(ctrl.data.player.color), "top", clockRunningColor) : null,
m('div', {
class: 'lichess_table onbg ' + classSet({
'table_with_clock': ctrl.clock,
'finished': ctrl.data.game.finished
})
}), (ctrl.clock && !ctrl.data.blindMode) ? clockView(ctrl.clock, ctrl.data.player.color, "bottom", clockRunningColor) : null,
])
)
]);
};