diff --git a/app/views/round/jsI18n.scala.html b/app/views/round/jsI18n.scala.html index fcbbe08443..8cd139212b 100644 --- a/app/views/round/jsI18n.scala.html +++ b/app/views/round/jsI18n.scala.html @@ -19,7 +19,6 @@ trans.accept, trans.decline, trans.takebackPropositionSent, trans.yourOpponentProposesATakeback, -trans.youHaveNbSecondsToMakeYourFirstMove, trans.thisPlayerUsesChessComputerAssistance, trans.gameAborted, trans.checkmate, diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index 35edc4e1a2..33af55890c 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -507,7 +507,8 @@ case class Game( else base } - def expirable = playable && !bothPlayersHaveMoved && nonAi && hasClock + def expirable = + source.exists(Source.expirable.contains) && playable && !bothPlayersHaveMoved && nonAi && hasClock def timeBeforeExpiration: Option[Centis] = expirable option { Centis.ofMillis(movedAt.getMillis - nowMillis + timeForFirstMove.millis).nonNeg diff --git a/modules/game/src/main/Source.scala b/modules/game/src/main/Source.scala index 9065bce110..3a44a6f873 100644 --- a/modules/game/src/main/Source.scala +++ b/modules/game/src/main/Source.scala @@ -23,6 +23,7 @@ object Source { val byId = all map { v => (v.id, v) } toMap val searchable = List(Lobby, Friend, Ai, Position, Import, Tournament, Simul, Pool) + val expirable: Set[Source] = Set(Lobby, Tournament, Pool) def apply(id: Int): Option[Source] = byId get id } diff --git a/modules/i18n/src/main/I18nKeys.scala b/modules/i18n/src/main/I18nKeys.scala index b25b3e2793..9e2fb931ff 100644 --- a/modules/i18n/src/main/I18nKeys.scala +++ b/modules/i18n/src/main/I18nKeys.scala @@ -667,7 +667,6 @@ val `nbFollowers` = new Translated("nbFollowers", Site) val `nbFollowing` = new Translated("nbFollowing", Site) val `lessThanNbMinutes` = new Translated("lessThanNbMinutes", Site) val `playedXTimes` = new Translated("playedXTimes", Site) -val `youHaveNbSecondsToMakeYourFirstMove` = new Translated("youHaveNbSecondsToMakeYourFirstMove", Site) val `nbGamesInPlay` = new Translated("nbGamesInPlay", Site) val `maximumNbCharacters` = new Translated("maximumNbCharacters", Site) val `blocks` = new Translated("blocks", Site) diff --git a/package.json b/package.json index c6d83b96d3..9e23f240b0 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,9 @@ "bugs": { "url": "https://github.com/ornicar/lila/issues" }, - "homepage": "https://lichess.org" + "homepage": "https://lichess.org", + "dependencies": { + "fs-extra": "^4.0.2", + "xml2js": "^0.4.19" + } } diff --git a/public/stylesheets/board.css b/public/stylesheets/board.css index 28bebb4d36..766527fcd3 100644 --- a/public/stylesheets/board.css +++ b/public/stylesheets/board.css @@ -443,10 +443,6 @@ body.piece_letter .lichess_ground .replay move { color: #d85000!important; font-weight: bold; } -.lichess_ground .expiration { - margin: 10px 0; - text-align: center; -} .lichess_ground .result_wrap { width: 100%; } @@ -764,6 +760,7 @@ div.control .button.disabled { .lichess_ground .negotiation { padding: 10px 7px; background: #d0d0d0; + text-align: center; } .lichess_ground .suggestion p { margin-bottom: 7px; @@ -771,6 +768,10 @@ div.control .button.disabled { .lichess_ground .suggestion .button { display: block; } +.lichess_ground .expiration.emerg { + background: #dc322f!important; + color: #fff!important; +} .lichess_ground .pending { display: flex; align-items: center; diff --git a/translation/source/site.xml b/translation/source/site.xml index c89ae7483e..85f9b0249e 100644 --- a/translation/source/site.xml +++ b/translation/source/site.xml @@ -451,10 +451,6 @@ a computer analysis, a game chat and a shareable URL. Retry this puzzle This puzzle is correct and interesting This puzzle is wrong or boring - - You have %s second to make your first move! - You have %s seconds to make your first move! - %s game in play %s games in play diff --git a/ui/round/src/view/expiration.ts b/ui/round/src/view/expiration.ts index bd01b55c2c..9713c0b045 100644 --- a/ui/round/src/view/expiration.ts +++ b/ui/round/src/view/expiration.ts @@ -1,13 +1,24 @@ import { h } from 'snabbdom' import { VNode } from 'snabbdom/vnode' import RoundController from '../ctrl'; +import { game } from 'game'; -export default function(ctrl: RoundController): VNode | undefined { +export default function(ctrl: RoundController): [VNode, boolean] | undefined { const d = ctrl.data.expiration; if (!d) return; - const timeLeft = Math.max(0, d.movedAt - Date.now() + d.millisToMove); - return h('div.expiration', [ - Math.round(timeLeft / 1000), - ' seconds to move' - ]); + const timeLeft = Math.max(0, d.movedAt - Date.now() + d.millisToMove), + myTurn = game.isPlayerTurn(ctrl.data), + emerg = myTurn && timeLeft < 8000; + return [ + h('div.expiration.suggestion', { + class: { emerg } + }, [ + h('div.text', { + }, [ + h('strong', '' + Math.round(timeLeft / 1000)), + ' seconds to move' + ]) + ]), + myTurn + ]; } diff --git a/ui/round/src/view/table.ts b/ui/round/src/view/table.ts index 2dabaa4c04..ad7a9c9889 100644 --- a/ui/round/src/view/table.ts +++ b/ui/round/src/view/table.ts @@ -56,13 +56,6 @@ function renderTableWatch(ctrl: RoundController) { ]); } -function tournamentStartWarning(ctrl: RoundController) { - return h('div.suggestion', [ - h('div.text', { attrs: {'data-icon': 'j'} }, - ctrl.trans('youHaveNbSecondsToMakeYourFirstMove', ctrl.data.tournament!.nbSecondsForFirstMove)) - ]); -} - function renderTablePlay(ctrl: RoundController) { const d = ctrl.data, loading = isLoading(ctrl), @@ -73,6 +66,7 @@ function renderTablePlay(ctrl: RoundController) { ctrl.drawConfirm ? button.drawConfirm(ctrl) : button.standard(ctrl, ctrl.canOfferDraw, '2', 'offerDraw', 'draw-yes', () => ctrl.offerDraw(true)), ctrl.resignConfirm ? button.resignConfirm(ctrl) : button.standard(ctrl, game.resignable, 'b', 'resign', 'resign-confirm', () => ctrl.resign(true)) ], + expiration = renderExpiration(ctrl), buttons: MaybeVNodes = loading ? [loader()] : (submit ? [submit] : [ button.forceResign(ctrl), button.threefoldClaimDraw(ctrl), @@ -80,10 +74,11 @@ function renderTablePlay(ctrl: RoundController) { button.answerOpponentDrawOffer(ctrl), button.cancelTakebackProposition(ctrl), button.answerOpponentTakebackProposition(ctrl), - (d.tournament && game.nbMoves(d, d.player.color) === 0) ? tournamentStartWarning(ctrl) : null + expiration && expiration[1] ? expiration[0] : null ]); return [ - renderExpiration(ctrl) || renderReplay(ctrl), + expiration && !expiration[1] ? expiration[0] : null, + renderReplay(ctrl), h('div.control.icons', { class: { 'confirm': !!(ctrl.drawConfirm || ctrl.resignConfirm) } }, icons),