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();