diff --git a/app/templating/SetupHelper.scala b/app/templating/SetupHelper.scala
index 229539ad9fa27..15548b570e4ec 100644
--- a/app/templating/SetupHelper.scala
+++ b/app/templating/SetupHelper.scala
@@ -184,6 +184,13 @@ trait SetupHelper:
(Pref.Animation.SLOW, trans.slow.txt())
)
+ def translatedZenChoices(using Lang) =
+ List(
+ (Pref.Zen.NO, trans.no.txt()),
+ (Pref.Zen.YES, trans.yes.txt()),
+ (Pref.Zen.GAME_AUTO, trans.preferences.inGameOnly.txt())
+ )
+
def translatedBoardCoordinateChoices(using Lang) =
List(
(Pref.Coords.NONE, trans.no.txt()),
diff --git a/app/views/account/bits.scala b/app/views/account/bits.scala
index 89c3bd7abd8c3..49deed4267968 100644
--- a/app/views/account/bits.scala
+++ b/app/views/account/bits.scala
@@ -31,10 +31,10 @@ object bits:
def setting(name: Frag, body: Frag) = st.section(h2(name), body)
- def radios[A](field: play.api.data.Field, options: Iterable[(A, String)], prefix: String = "ir") =
+ def radios[A](field: play.api.data.Field, options: Iterable[(A, String)]) =
st.group(cls := "radio")(
options.map { (key, value) =>
- val id = s"$prefix${field.id}_$key"
+ val id = s"ir${field.id}_$key"
val checked = field.value has key.toString
div(
input(
@@ -49,12 +49,12 @@ object bits:
}.toList
)
- def bitCheckboxes(field: play.api.data.Field, options: Iterable[(Int, String)], prefix: String = "ir") =
+ def bitCheckboxes(field: play.api.data.Field, options: Iterable[(Int, String)]) =
st.group(cls := "radio")(
/// Will hold the value being calculated with the various checkboxes when sending
div(
input(
- st.id := s"$prefix${field.id}_hidden",
+ st.id := s"ir${field.id}_hidden",
true option st.checked,
tpe := "hidden",
st.value := "",
@@ -63,7 +63,7 @@ object bits:
st.style := "display: none;"
) :: options
.map: (key, value) =>
- val id = s"$prefix${field.id}_$key"
+ val id = s"ir${field.id}_$key"
val intVal = ~field.value.flatMap(_.toIntOption)
val checked = (intVal & key) == key
div(
diff --git a/app/views/account/pref.scala b/app/views/account/pref.scala
index 244ba80cc6918..9a17ca9b88a55 100644
--- a/app/views/account/pref.scala
+++ b/app/views/account/pref.scala
@@ -56,7 +56,7 @@ object pref:
),
setting(
zenMode(),
- radios(form("display.zen"), booleanChoices)
+ radios(form("display.zen"), translatedZenChoices)
),
setting(
displayBoardResizeHandle(),
diff --git a/app/views/base/layout.scala b/app/views/base/layout.scala
index db517a18035f2..9093b2cf38018 100644
--- a/app/views/base/layout.scala
+++ b/app/views/base/layout.scala
@@ -294,13 +294,14 @@ object layout:
baseClass -> true,
"dark-board" -> (pref.bg == lila.pref.Pref.Bg.DARKBOARD),
"piece-letter" -> pref.pieceNotationIsLetter,
- "zen" -> pref.isZen,
"blind-mode" -> ctx.blind,
"kid" -> ctx.kid,
"mobile" -> lila.common.HTTPRequest.isMobileBrowser(ctx.req),
"playing fixed-scroll" -> playing,
+ "no-rating" -> !pref.showRatings,
+ "zen" -> (pref.isZen || (playing && pref.isZenAuto)),
"zenable" -> zenable,
- "no-rating" -> !pref.showRatings
+ "zen-auto" -> (zenable && pref.isZenAuto)
)
},
dataDev,
diff --git a/app/views/round/player.scala b/app/views/round/player.scala
index 58b5686f53168..220555e2135b9 100644
--- a/app/views/round/player.scala
+++ b/app/views/round/player.scala
@@ -44,7 +44,8 @@ object player:
bits.layout(
variant = pov.game.variant,
- title = s"${trans.play.txt()} ${if ctx.pref.isZen then "ZEN" else playerText(pov.opponent)}",
+ title = s"${trans.play
+ .txt()} ${if ctx.pref.isZen || ctx.pref.isZenAuto then "ZEN" else playerText(pov.opponent)}",
moreJs = frag(
roundNvuiTag,
jsModuleInit(
diff --git a/modules/i18n/src/main/I18nKeys.scala b/modules/i18n/src/main/I18nKeys.scala
index 8ac6ded918dbb..d13d67989176a 100644
--- a/modules/i18n/src/main/I18nKeys.scala
+++ b/modules/i18n/src/main/I18nKeys.scala
@@ -1678,6 +1678,7 @@ object I18nKeys:
val `explainShowPlayerRatings` = I18nKey("preferences:explainShowPlayerRatings")
val `displayBoardResizeHandle` = I18nKey("preferences:displayBoardResizeHandle")
val `onlyOnInitialPosition` = I18nKey("preferences:onlyOnInitialPosition")
+ val `inGameOnly` = I18nKey("preferences:inGameOnly")
val `blindfoldChess` = I18nKey("preferences:blindfoldChess")
val `chessClock` = I18nKey("preferences:chessClock")
val `tenthsOfSeconds` = I18nKey("preferences:tenthsOfSeconds")
diff --git a/modules/pref/src/main/Pref.scala b/modules/pref/src/main/Pref.scala
index 93f6685300b0f..0f2d27ad713cc 100644
--- a/modules/pref/src/main/Pref.scala
+++ b/modules/pref/src/main/Pref.scala
@@ -90,7 +90,7 @@ case class Pref(
SoundSet.allByKey get value map { s =>
copy(soundSet = s.key)
}
- case "zen" => copy(zen = if value == "1" then 1 else 0).some
+ case "zen" => copy(zen = ~value.toIntOption.filter(Zen.choices.map(_._1).contains)).some
case "voice" => copy(voice = if value == "1" then 1.some else 0.some).some
case "keyboardMove" => copy(keyboardMove = if value == "1" then 1 else 0).some
case _ => none
@@ -115,7 +115,8 @@ case class Pref(
def pieceNotationIsLetter = pieceNotation == PieceNotation.LETTER
- def isZen = zen == Zen.YES
+ def isZen = zen == Zen.YES
+ def isZenAuto = zen == Zen.GAME_AUTO
val showRatings = ratings == Ratings.YES
@@ -436,7 +437,17 @@ object Pref:
val changedAt = instantOf(2021, 12, 28, 8, 0)
val showPrompt = changedAt.isAfter(nowInstant minusMonths 6)
- object Zen extends BooleanPref
+ object Zen:
+ val NO = 0
+ val YES = 1
+ val GAME_AUTO = 2
+
+ val choices = Seq(
+ NO -> "No",
+ YES -> "Yes",
+ GAME_AUTO -> "In-game only"
+ )
+
object Ratings extends BooleanPref
val darkByDefaultSince = instantOf(2021, 11, 7, 8, 0)
diff --git a/modules/pref/src/main/PrefForm.scala b/modules/pref/src/main/PrefForm.scala
index f9d2f529957f6..169d237e959d6 100644
--- a/modules/pref/src/main/PrefForm.scala
+++ b/modules/pref/src/main/PrefForm.scala
@@ -35,7 +35,7 @@ object PrefForm:
"coords" -> checkedNumber(Pref.Coords.choices),
"replay" -> checkedNumber(Pref.Replay.choices),
"pieceNotation" -> optional(booleanNumber),
- "zen" -> optional(booleanNumber),
+ "zen" -> optional(checkedNumber(Pref.Zen.choices)),
"resizeHandle" -> optional(checkedNumber(Pref.ResizeHandle.choices)),
"blindfold" -> checkedNumber(Pref.Blindfold.choices)
)(DisplayData.apply)(unapply),
@@ -245,7 +245,7 @@ object PrefForm:
val zen = Form(
single(
- "zen" -> text.verifying(Set("0", "1") contains _)
+ "zen" -> text.verifying(Set("0", "1", "2") contains _)
)
)
diff --git a/translation/source/preferences.xml b/translation/source/preferences.xml
index c5f1d6a580226..f9f0e03f3f435 100644
--- a/translation/source/preferences.xml
+++ b/translation/source/preferences.xml
@@ -18,6 +18,7 @@
This allows hiding all ratings from the website, to help focus on the chess. Games can still be rated, this is only about what you get to see.
Show board resize handle
Only on initial position
+ In-game only
Blindfold chess (invisible pieces)
Chess clock
Tenths of seconds
diff --git a/ui/puzzle/src/ctrl.ts b/ui/puzzle/src/ctrl.ts
index 6c7cc760a039a..8fa13fa96b71f 100644
--- a/ui/puzzle/src/ctrl.ts
+++ b/ui/puzzle/src/ctrl.ts
@@ -611,7 +611,9 @@ export default function (opts: PuzzleOpts, redraw: Redraw): Controller {
lichess.pubsub.on('zen', () => {
const zen = $('body').toggleClass('zen').hasClass('zen');
window.dispatchEvent(new Event('resize'));
- xhr.setZen(zen);
+ if (!$('body').hasClass('zen-auto')) {
+ xhr.setZen(zen);
+ }
});
$('body').addClass('playing'); // for zen
$('#zentog').on('click', () => lichess.pubsub.emit('zen'));
diff --git a/ui/round/src/ctrl.ts b/ui/round/src/ctrl.ts
index 16dccead5ffd8..b222a13b1c8db 100644
--- a/ui/round/src/ctrl.ts
+++ b/ui/round/src/ctrl.ts
@@ -154,7 +154,9 @@ export default class RoundController {
lichess.pubsub.on('zen', () => {
const zen = $('body').toggleClass('zen').hasClass('zen');
window.dispatchEvent(new Event('resize'));
- xhr.setZen(zen);
+ if (!$('body').hasClass('zen-auto')) {
+ xhr.setZen(zen);
+ }
});
if (!this.opts.noab && this.isPlaying()) ab.init(this);
@@ -566,6 +568,12 @@ export default class RoundController {
)
this.opts.chat?.instance?.then(c => c.post('Good game, well played'));
}
+
+ if ($('body').hasClass('zen-auto') && $('body').hasClass('zen')) {
+ $('body').toggleClass('zen');
+ window.dispatchEvent(new Event('resize'));
+ }
+
if (d.crazyhouse) crazyEndHook();
this.clearJust();
this.setTitle();
diff --git a/ui/storm/src/ctrl.ts b/ui/storm/src/ctrl.ts
index 74880de29e104..ac7aa108b0ee1 100644
--- a/ui/storm/src/ctrl.ts
+++ b/ui/storm/src/ctrl.ts
@@ -67,7 +67,9 @@ export default class StormCtrl implements PuzCtrl {
lichess.pubsub.on('zen', () => {
const zen = $('body').toggleClass('zen').hasClass('zen');
window.dispatchEvent(new Event('resize'));
- xhr.setZen(zen);
+ if (!$('body').hasClass('zen-auto')) {
+ xhr.setZen(zen);
+ }
});
$('#zentog').on('click', this.toggleZen);
lichess.sound.move();