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),