Merge branch 'master' into ScalaEvaluator
* master: improve game widgets and sides, and TV history fix pt translation fix hook config color break lobby API BC for the lulz translate Q&A title show chess960 position number - closes #214 catch pov priority sort errors disallow rated white seeks for some variants protect round xhr and websocket against theft protect round sockets - WIP Conflicts: modules/chess
This commit is contained in:
commit
6072b18c49
|
@ -41,6 +41,17 @@ private[controllers] trait LilaController
|
|||
protected def Socket[A: FrameFormatter](f: Context => Fu[(Iteratee[A, _], Enumerator[A])]) =
|
||||
WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f map scala.util.Right.apply }
|
||||
|
||||
protected def SocketEither[A: FrameFormatter](f: Context => Fu[Either[Result, (Iteratee[A, _], Enumerator[A])]]) =
|
||||
WebSocket.tryAccept[A] { req => reqToCtx(req) flatMap f }
|
||||
|
||||
protected def SocketOption[A: FrameFormatter](f: Context => Fu[Option[(Iteratee[A, _], Enumerator[A])]]) =
|
||||
WebSocket.tryAccept[A] { req =>
|
||||
reqToCtx(req) flatMap f map {
|
||||
case None => Left(NotFound(Json.obj("error" -> "socket resource not found")))
|
||||
case Some(pair) => Right(pair)
|
||||
}
|
||||
}
|
||||
|
||||
protected def Open(f: Context => Fu[Result]): Action[AnyContent] =
|
||||
Open(BodyParsers.parse.anyContent)(f)
|
||||
|
||||
|
|
|
@ -48,9 +48,9 @@ object Lobby extends LilaController {
|
|||
)
|
||||
}
|
||||
|
||||
def socket(apiVersion: Int) = Socket[JsValue] { implicit ctx =>
|
||||
def socket(apiVersion: Int) = SocketOption[JsValue] { implicit ctx =>
|
||||
get("sri") ?? { uid =>
|
||||
Env.lobby.socketHandler(uid = uid, user = ctx.me)
|
||||
Env.lobby.socketHandler(uid = uid, user = ctx.me) map some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,9 +34,9 @@ object Main extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def websocket = Socket { implicit ctx =>
|
||||
def websocket = SocketOption { implicit ctx =>
|
||||
get("sri") ?? { uid =>
|
||||
Env.site.socketHandler(uid, ctx.userId, get("flag"))
|
||||
Env.site.socketHandler(uid, ctx.userId, get("flag")) map some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ object Monitor extends LilaController {
|
|||
Ok(views.html.monitor.monitor())
|
||||
}
|
||||
|
||||
def websocket = Socket[JsValue] { implicit ctx =>
|
||||
get("sri") ?? env.socketHandler.apply
|
||||
def websocket = SocketOption[JsValue] { implicit ctx =>
|
||||
get("sri") ?? { sri =>
|
||||
env.socketHandler(sri) map some
|
||||
}
|
||||
}
|
||||
|
||||
def status = Action.async { implicit req =>
|
||||
|
|
|
@ -22,7 +22,7 @@ object Round extends LilaController with TheftPrevention {
|
|||
private def bookmarkApi = Env.bookmark.api
|
||||
private def analyser = Env.analyse.analyser
|
||||
|
||||
def websocketWatcher(gameId: String, color: String) = Socket[JsValue] { implicit ctx =>
|
||||
def websocketWatcher(gameId: String, color: String) = SocketOption[JsValue] { implicit ctx =>
|
||||
(get("sri") |@| getInt("version")).tupled ?? {
|
||||
case (uid, version) => env.socketHandler.watcher(
|
||||
gameId = gameId,
|
||||
|
@ -35,13 +35,21 @@ object Round extends LilaController with TheftPrevention {
|
|||
}
|
||||
}
|
||||
|
||||
def websocketPlayer(fullId: String, apiVersion: Int) = Socket[JsValue] { implicit ctx =>
|
||||
private lazy val theftResponse = Unauthorized(Json.obj(
|
||||
"error" -> "This game requires authentication"
|
||||
)) as JSON
|
||||
|
||||
def websocketPlayer(fullId: String, apiVersion: Int) = SocketEither[JsValue] { implicit ctx =>
|
||||
GameRepo pov fullId flatMap {
|
||||
_ ?? { pov =>
|
||||
(get("sri") |@| getInt("version")).tupled ?? {
|
||||
case (uid, version) => env.socketHandler.player(pov, version, uid, ~get("ran"), ctx.me, ctx.ip)
|
||||
case Some(pov) =>
|
||||
if (isTheft(pov)) fuccess(Left(theftResponse))
|
||||
else (get("sri") |@| getInt("version")).tupled match {
|
||||
case Some((uid, version)) => env.socketHandler.player(
|
||||
pov, version, uid, ~get("ran"), ctx.me, ctx.ip
|
||||
) map Right.apply
|
||||
case None => fuccess(Left(NotFound))
|
||||
}
|
||||
}
|
||||
case None => fuccess(Left(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +73,8 @@ object Round extends LilaController with TheftPrevention {
|
|||
)
|
||||
},
|
||||
api = apiVersion => {
|
||||
if (pov.game.playableByAi) env.roundMap ! Tell(pov.game.id, AiPlay)
|
||||
if (isTheft(pov)) theftResponse
|
||||
else if (pov.game.playableByAi) env.roundMap ! Tell(pov.game.id, AiPlay)
|
||||
Env.api.roundApi.player(pov, apiVersion, Nil) map { Ok(_) }
|
||||
}
|
||||
)
|
||||
|
@ -163,9 +172,11 @@ object Round extends LilaController with TheftPrevention {
|
|||
}
|
||||
|
||||
private def side(pov: Pov, isPlayer: Boolean)(implicit ctx: Context) =
|
||||
pov.game.tournamentId ?? TournamentRepo.byId map { tour =>
|
||||
Ok(html.game.side(pov, tour, withTourStanding = isPlayer))
|
||||
}
|
||||
(pov.game.tournamentId ?? TournamentRepo.byId) zip
|
||||
GameRepo.initialFen(pov.game) map {
|
||||
case (tour, initialFen) =>
|
||||
Ok(html.game.side(pov, initialFen, tour, withTourStanding = isPlayer))
|
||||
}
|
||||
|
||||
def continue(id: String, mode: String) = Open { implicit ctx =>
|
||||
OptionResult(GameRepo game id) { game =>
|
||||
|
|
|
@ -6,8 +6,8 @@ import play.api.mvc._
|
|||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
import lila.game.{ Pov, GameRepo }
|
||||
import lila.common.HTTPRequest
|
||||
import lila.game.{ Pov, GameRepo }
|
||||
import lila.tournament.{ System, TournamentRepo, Created, Started, Finished, Tournament => Tourney }
|
||||
import lila.user.UserRepo
|
||||
import views._
|
||||
|
@ -119,10 +119,10 @@ object Tournament extends LilaController {
|
|||
}
|
||||
}
|
||||
|
||||
def websocket(id: String, apiVersion: Int) = Socket[JsValue] { implicit ctx =>
|
||||
~(getInt("version") |@| get("sri") apply {
|
||||
def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx =>
|
||||
(getInt("version") |@| get("sri")).tupled ?? {
|
||||
case (version, uid) => env.socketHandler.join(id, version, uid, ctx.me)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private def chatOf(tour: lila.tournament.Tournament)(implicit ctx: Context) =
|
||||
|
|
|
@ -45,7 +45,7 @@ i18n: @round.jsI18n()
|
|||
|
||||
@analyse.layout(
|
||||
title = title,
|
||||
side = views.html.game.side(pov, tour, withTourStanding = false, userTv = userTv).some,
|
||||
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, withTourStanding = false, userTv = userTv).some,
|
||||
chat = base.chatDom(trans.spectatorRoom.str(), ctx.isAuth).some,
|
||||
underchat = underchat.some,
|
||||
moreCss = moreCss,
|
||||
|
|
8
app/views/game/gameIcon.scala.html
Normal file
8
app/views/game/gameIcon.scala.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
@(game: Game)(implicit ctx: Context)
|
||||
@game.perfType match {
|
||||
case _ if game.fromPosition => {*}
|
||||
case _ if game.hasAi => {:}
|
||||
case _ if game.imported => {/}
|
||||
case Some(p) => {@p.iconChar}
|
||||
case _ => {8}
|
||||
}
|
|
@ -1,20 +1,17 @@
|
|||
@(pov: Pov, tour: Option[lila.tournament.Tournament], withTourStanding: Boolean, userTv: Option[User] = None)(implicit ctx: Context)
|
||||
@(pov: Pov, initialFen: Option[String], tour: Option[lila.tournament.Tournament], withTourStanding: Boolean, userTv: Option[User] = None)(implicit ctx: Context)
|
||||
|
||||
@import pov._
|
||||
@import lila.tournament.arena
|
||||
|
||||
<div class="side">
|
||||
<div class="side_box padded">
|
||||
<div class="game_infos" data-icon="@game.perfType match {
|
||||
case _ if game.fromPosition => {*}
|
||||
case _ if game.hasAi => {:}
|
||||
case _ if game.imported => {/}
|
||||
case Some(p) => {@p.iconChar}
|
||||
case _ => {8}
|
||||
}">
|
||||
<div class="game_infos" data-icon="@gameIcon(game)">
|
||||
<div class="header">
|
||||
<span class="setup">
|
||||
@bookmark.toggle(game)
|
||||
@if(game.imported) {
|
||||
<a class="hint--top" href="@routes.Importer.importGame" data-hint="@trans.importGame()">IMPORT</a>
|
||||
} else {
|
||||
@game.clock.map(_.show).getOrElse {
|
||||
@game.daysPerTurn.map { days =>
|
||||
<span data-hint="@trans.correspondence()" class="hint--top">@{(days == 1).fold(trans.oneDay(), trans.nbDays(days))}</span>
|
||||
|
@ -29,6 +26,7 @@
|
|||
<span class="hint--top" data-hint="@pt.title">@pt.name.toUpperCase</span>
|
||||
}
|
||||
} • @game.rated.fold(trans.rated.str(), trans.casual.str()).toUpperCase
|
||||
}
|
||||
</span>
|
||||
@game.pgnImport.flatMap(_.date).getOrElse(
|
||||
game.isBeingPlayed.fold(trans.playingRightNow(), momentFormat(game.createdAt))
|
||||
|
@ -67,6 +65,13 @@
|
|||
<span class="player is color-icon white">@game.checkCount.black</span>
|
||||
</div>
|
||||
}
|
||||
@if(game.variant.chess960) {
|
||||
@initialFen.map { fen =>
|
||||
@chess.variant.Chess960.positionNumber(fen).map { number =>
|
||||
Chess960 start position: <strong>@number</strong>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@userTv.map { u =>
|
||||
|
|
|
@ -27,16 +27,16 @@
|
|||
@defining(user flatMap g.player) { fromPlayer =>
|
||||
@defining(fromPlayer | g.firstPlayer ) { firstPlayer =>
|
||||
@gameFen(g, firstPlayer.color, ownerLink, withTitle = false)
|
||||
<div class="infos" data-icon="@g.perfType match {
|
||||
case _ if g.fromPosition => {*}
|
||||
case _ if g.hasAi => {:}
|
||||
case _ if g.imported => {/}
|
||||
case Some(p) => {@p.iconChar}
|
||||
case _ => {8}
|
||||
}">
|
||||
<div class="infos" data-icon="@gameIcon(g)">
|
||||
@bookmark.toggle(g)
|
||||
<div class="header">
|
||||
<strong>
|
||||
@if(g.imported) {
|
||||
<span>IMPORT</span>
|
||||
@g.pgnImport.flatMap(_.user).map { user =>
|
||||
@trans.by(userIdLink(user.some, None, false))
|
||||
}
|
||||
} else {
|
||||
@g.clock.map(_.show).getOrElse {
|
||||
@g.daysPerTurn.map { days =>
|
||||
<span data-hint="@trans.correspondence()" class="hint--top">@if(days == 1) {@trans.oneDay()} else {@trans.nbDays(days)}</span>
|
||||
|
@ -46,6 +46,7 @@
|
|||
}
|
||||
• @g.perfType.map(_.name).getOrElse {@chess.variant.FromPosition.name}
|
||||
• @g.rated.fold(trans.rated(), trans.casual())
|
||||
}
|
||||
</strong>
|
||||
@g.pgnImport.flatMap(_.date).getOrElse(momentFormat(g.createdAt))
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@ moreJs = jsTag("vendor/jquery.infinitescroll.min.js"),
|
|||
side = side.some) {
|
||||
|
||||
<div class="content_box_top">
|
||||
<h1 data-icon="&" class="is4 lichess_title"> Questions & Answers</h1>
|
||||
<h1 data-icon="&" class="is4 text lichess_title">@trans.questionsAndAnswers()</h1>
|
||||
</div>
|
||||
<div class="content_box_inter meta">
|
||||
<div class="big_search">
|
||||
|
|
|
@ -19,7 +19,7 @@ i18n: @jsI18n()
|
|||
|
||||
@round.layout(
|
||||
title = title,
|
||||
side = views.html.game.side(pov, tour, withTourStanding = true),
|
||||
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, withTourStanding = true),
|
||||
chat = pov.game.hasChat.option(base.chatDom(trans.chatRoom.str(), ctx.isAuth)),
|
||||
underchat = views.html.game.watchers().some,
|
||||
moreJs = moreJs,
|
||||
|
|
|
@ -17,7 +17,7 @@ i18n: @jsI18n()
|
|||
|
||||
@round.layout(
|
||||
title = title,
|
||||
side = views.html.game.side(pov, tour, withTourStanding = false, userTv = userTv),
|
||||
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, withTourStanding = false, userTv = userTv),
|
||||
chat = base.chatDom(trans.spectatorRoom.str()).some,
|
||||
underchat = views.html.game.watchers().some,
|
||||
moreJs = moreJs,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@(form: Form[_], typ: String, title: Html, route: Call, fields: Html, error: Option[Html] = None)(implicit ctx: Context)
|
||||
|
||||
<div class="lichess_overboard game_config game_config_@typ"
|
||||
data-white-variants="@lila.game.Game.variantsWhereWhiteIsBetter.map(_.id).mkString(",")"
|
||||
@if(ctx.isAnon){data-anon="1"}>
|
||||
<a href="@routes.Lobby.home" class="close icon" title="@trans.cancel()" data-icon="L"></a>
|
||||
<h2>@title</h2>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<tbody>
|
||||
@games.map { g =>
|
||||
<tr>
|
||||
<td><a class="view" href="@routes.Round.watcher(g.id, g.firstPlayer.color.name)" data-icon="v"></a></td>
|
||||
<td><a class="icon" href="@routes.Round.watcher(g.id, g.firstPlayer.color.name)" data-icon="@game.gameIcon(g)"></a></td>
|
||||
<td>
|
||||
@playerLink(g.firstPlayer, withOnline = false, withDiff = true)<br />
|
||||
@playerLink(g.secondPlayer, withOnline = false, withDiff = true)
|
||||
|
|
|
@ -403,7 +403,7 @@ whenTimeRemainingLessThanThirtySeconds=Quando o tempo restante for menor do que
|
|||
difficultyEasy=Fácil
|
||||
difficultyNormal=Normal
|
||||
difficultyHard=Difícil
|
||||
xLeftANoteOnY=% deixou uma nota para %s
|
||||
xLeftANoteOnY=%s deixou uma nota para %s
|
||||
xCompetesInY=%s compete em %s
|
||||
xAskedY=%s perguntou %s
|
||||
xAnsweredY=%s respondeu %s
|
||||
|
|
|
@ -54,7 +54,7 @@ final class LobbyApi(
|
|||
"id" -> pov.opponent.userId,
|
||||
"username" -> lila.game.Namer.playerString(pov.opponent, withRating = false)(lightUser),
|
||||
"rating" -> pov.opponent.rating,
|
||||
"aiLevel" -> pov.opponent.aiLevel).noNull,
|
||||
"ai" -> pov.opponent.aiLevel).noNull,
|
||||
"isMyTurn" -> pov.isMyTurn,
|
||||
"secondsLeft" -> pov.remainingSeconds)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 116abbb200fd913934c76574930cff967d00e991
|
||||
Subproject commit cd73bd5c14ba3fd6ba84384a64789196ca6da5cc
|
|
@ -422,8 +422,14 @@ object Game {
|
|||
chess.variant.Standard,
|
||||
chess.variant.Chess960,
|
||||
chess.variant.KingOfTheHill)
|
||||
|
||||
val unanalysableVariants: Set[Variant] = Variant.all.toSet -- analysableVariants
|
||||
|
||||
val variantsWhereWhiteIsBetter: Set[Variant] = Set(
|
||||
chess.variant.ThreeCheck,
|
||||
chess.variant.Atomic,
|
||||
chess.variant.Antichess)
|
||||
|
||||
val gameIdSize = 8
|
||||
val playerIdSize = 4
|
||||
val fullIdSize = 12
|
||||
|
|
|
@ -121,8 +121,16 @@ object GameRepo {
|
|||
}
|
||||
|
||||
def nowPlaying(user: User): Fu[List[Pov]] =
|
||||
$find(Query nowPlaying user.id) map {
|
||||
_ flatMap { Pov(_, user) } sortWith Pov.priority
|
||||
$find(Query nowPlaying user.id) map { games =>
|
||||
val povs = games flatMap { Pov(_, user) }
|
||||
try {
|
||||
povs sortWith Pov.priority
|
||||
}
|
||||
catch {
|
||||
case e: IllegalArgumentException =>
|
||||
logerr(s"GameRepo.nowPlaying(${user.id}) ${povs.size} ${e.getMessage}")
|
||||
povs
|
||||
}
|
||||
}
|
||||
|
||||
// gets most urgent game to play
|
||||
|
|
|
@ -91,9 +91,9 @@ private[round] final class SocketHandler(
|
|||
uid: String,
|
||||
user: Option[User],
|
||||
ip: String,
|
||||
userTv: Option[String]): Fu[JsSocketHandler] =
|
||||
userTv: Option[String]): Fu[Option[JsSocketHandler]] =
|
||||
GameRepo.pov(gameId, colorName) flatMap {
|
||||
_ ?? { join(_, none, version, uid, "", user, ip, userTv = userTv) }
|
||||
_ ?? { join(_, none, version, uid, "", user, ip, userTv = userTv) map some }
|
||||
}
|
||||
|
||||
def player(
|
||||
|
|
|
@ -17,6 +17,11 @@ case class HookConfig(
|
|||
color: Color,
|
||||
ratingRange: RatingRange) extends HumanConfig {
|
||||
|
||||
def fixColor = copy(
|
||||
color = if (mode == Mode.Rated &&
|
||||
lila.game.Game.variantsWhereWhiteIsBetter(variant) &&
|
||||
color == Color.White) Color.Random else color)
|
||||
|
||||
// allowAnons -> membersOnly
|
||||
def >> = (variant.id, timeMode.id, time, increment, days, mode.id.some, !allowAnon, ratingRange.toString.some, color.name).some
|
||||
|
||||
|
|
|
@ -50,11 +50,12 @@ private[setup] final class Processor(
|
|||
}
|
||||
|
||||
def hook(
|
||||
config: HookConfig,
|
||||
configBase: HookConfig,
|
||||
uid: String,
|
||||
sid: Option[String],
|
||||
blocking: Set[String])(implicit ctx: UserContext): Fu[String] =
|
||||
saveConfig(_ withHook config) >> {
|
||||
blocking: Set[String])(implicit ctx: UserContext): Fu[String] = {
|
||||
val config = configBase.fixColor
|
||||
saveConfig(_ withHook config) >> {
|
||||
config.hook(uid, ctx.me, sid, blocking) match {
|
||||
case Left(hook) => fuccess {
|
||||
lobby ! AddHook(hook)
|
||||
|
@ -67,6 +68,7 @@ private[setup] final class Processor(
|
|||
case _ => fufail("Can't create seek")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def saveConfig(map: UserConfig => UserConfig)(implicit ctx: UserContext): Funit =
|
||||
ctx.me.fold(AnonConfigRepo.update(ctx.req) _)(user => UserConfigRepo.update(user) _)(map)
|
||||
|
|
|
@ -41,17 +41,8 @@ object Handler {
|
|||
).map(_ => socket ! Quit(uid))
|
||||
}
|
||||
|
||||
(socket ? join map connecter map {
|
||||
socket ? join map connecter map {
|
||||
case (controller, enum) => iteratee(controller) -> enum
|
||||
}) recover {
|
||||
case t: Exception => errorHandler(t.getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
def errorHandler(err: String): JsSocketHandler =
|
||||
Iteratee.skipToEof[JsValue] ->
|
||||
Enumerator[JsValue](Json.obj(
|
||||
"error" -> "Socket handler error: %s".format(err)
|
||||
)).andThen(Enumerator.eof)
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,4 @@ trait WithSocket {
|
|||
type JsEnumerator = Enumerator[JsValue]
|
||||
type JsIteratee = Iteratee[JsValue, _]
|
||||
type JsSocketHandler = (JsIteratee, JsEnumerator)
|
||||
|
||||
implicit val LilaJsSocketHandlerZero: Zero[JsSocketHandler] =
|
||||
Zero.instance(Handler errorHandler "default error handler used")
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ private[tournament] final class SocketHandler(
|
|||
tourId: String,
|
||||
version: Int,
|
||||
uid: String,
|
||||
user: Option[User]): Fu[JsSocketHandler] =
|
||||
user: Option[User]): Fu[Option[JsSocketHandler]] =
|
||||
TournamentRepo.exists(tourId) flatMap {
|
||||
_ ?? {
|
||||
for {
|
||||
|
@ -36,7 +36,7 @@ private[tournament] final class SocketHandler(
|
|||
case Connected(enum, member) =>
|
||||
controller(socket, tourId, uid, member) -> enum
|
||||
}
|
||||
} yield handler
|
||||
} yield handler.some
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1614,12 +1614,18 @@ lichess.storage = {
|
|||
var $daysInput = $form.find('.days_choice input');
|
||||
var isHook = $form.hasClass('game_config_hook');
|
||||
var $ratings = $form.find('.ratings > div');
|
||||
var whiteVariants = $form.data('white-variants').split(',');
|
||||
var toggleButtons = function() {
|
||||
var timeMode = $timeModeSelect.val();
|
||||
var rated = $rated.prop('checked');
|
||||
var timeOk = timeMode != '1' || $timeInput.val() > 0 || $incrementInput.val() > 0;
|
||||
var ratedOk = !isHook || !rated || timeMode != '0';
|
||||
$form.find('.color_submits button').toggle(timeOk && ratedOk);
|
||||
if (timeOk && ratedOk) {
|
||||
$form.find('.color_submits button').show();
|
||||
$form.find('.color_submits button.white').toggle(
|
||||
!rated || whiteVariants.indexOf($variantSelect.val()) === -1);
|
||||
} else
|
||||
$form.find('.color_submits button').hide();
|
||||
};
|
||||
var showRating = function() {
|
||||
var timeMode = $timeModeSelect.val();
|
||||
|
@ -1783,6 +1789,7 @@ lichess.storage = {
|
|||
$modeChoicesWrap.toggle(!fen);
|
||||
if (fen) $casual.click();
|
||||
showRating();
|
||||
toggleButtons();
|
||||
}).trigger('change');
|
||||
|
||||
$form.find('div.level').each(function() {
|
||||
|
|
|
@ -17,12 +17,18 @@
|
|||
font-weight: bold;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
#tv_history a.view {
|
||||
padding-left: 5px;
|
||||
#tv_history a.icon {
|
||||
font-size: 26px;
|
||||
padding-left: 10px;
|
||||
text-decoration: none;
|
||||
opacity: 0.7;
|
||||
transition: 0.3s;
|
||||
}
|
||||
#tv_history tr:hover a.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
#tv_history td {
|
||||
padding: 6px 5px 6px 5px;
|
||||
padding: 4px 0px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
}
|
||||
#tv_history tr:first-child td {
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = function(ctrl) {
|
|||
},
|
||||
boardContent),
|
||||
m('span.meta', [
|
||||
pov.opponent.aiLevel ? ctrl.trans('aiNameLevelAiLevel', 'Stockfish', pov.opponent.aiLevel) : pov.opponent.username,
|
||||
pov.opponent.ai ? ctrl.trans('aiNameLevelAiLevel', 'Stockfish', pov.opponent.aiLevel) : pov.opponent.username,
|
||||
m('span.indicator',
|
||||
pov.isMyTurn ? (pov.secondsLeft ? timer(pov) : ctrl.trans('yourTurn')) : m.trust(' '))
|
||||
])
|
||||
|
|
Loading…
Reference in a new issue