diff --git a/modules/coreI18n/src/main/key.scala b/modules/coreI18n/src/main/key.scala
index 9cd961a98e6b..83b8ce92adbb 100644
--- a/modules/coreI18n/src/main/key.scala
+++ b/modules/coreI18n/src/main/key.scala
@@ -2396,6 +2396,7 @@ object I18nKey:
val `allSyncMembersRemainOnTheSamePosition`: I18nKey = "study:allSyncMembersRemainOnTheSamePosition"
val `shareChanges`: I18nKey = "study:shareChanges"
val `playing`: I18nKey = "study:playing"
+ val `showResults`: I18nKey = "study:showResults"
val `showEvalBar`: I18nKey = "study:showEvalBar"
val `first`: I18nKey = "study:first"
val `previous`: I18nKey = "study:previous"
diff --git a/translation/source/study.xml b/translation/source/study.xml
index 1c121d8022f2..26f595f0d811 100644
--- a/translation/source/study.xml
+++ b/translation/source/study.xml
@@ -53,6 +53,7 @@
All SYNC members remain on the same position
Share changes with spectators and save them on the server
Playing
+ Results
Evaluation bars
First
Previous
diff --git a/ui/@types/lichess/i18n.d.ts b/ui/@types/lichess/i18n.d.ts
index 4fe2d2fee00b..8701c5cd40dd 100644
--- a/ui/@types/lichess/i18n.d.ts
+++ b/ui/@types/lichess/i18n.d.ts
@@ -4867,6 +4867,8 @@ interface I18n {
shareChanges: string;
/** Evaluation bars */
showEvalBar: string;
+ /** Results */
+ showResults: string;
/** Spectator */
spectator: string;
/** Start */
diff --git a/ui/analyse/css/study/panel/_multiboard.scss b/ui/analyse/css/study/panel/_multiboard.scss
index d27605c2e77c..6f293821c512 100644
--- a/ui/analyse/css/study/panel/_multiboard.scss
+++ b/ui/analyse/css/study/panel/_multiboard.scss
@@ -47,12 +47,14 @@
}
.playing,
- .eval {
+ .eval,
+ .results {
cursor: pointer;
}
.playing input,
- .eval input {
+ .eval input,
+ .results input {
vertical-align: middle;
margin-inline-end: 3px;
}
@@ -164,3 +166,8 @@
border-radius: 4px 0 0 4px;
}
}
+
+.empty-boards-note {
+ margin-bottom: 0.5em;
+ color: var(--c-font-dim);
+}
diff --git a/ui/analyse/src/study/multiBoard.ts b/ui/analyse/src/study/multiBoard.ts
index 85ce3b0d0e03..27d9d9f858ad 100644
--- a/ui/analyse/src/study/multiBoard.ts
+++ b/ui/analyse/src/study/multiBoard.ts
@@ -12,11 +12,12 @@ import { type StudyChapters, gameLinkAttrs, gameLinksListener } from './studyCha
import { playerFed } from './playerBars';
import { userTitle } from 'common/userLink';
import { h } from 'snabbdom';
-import { storage } from 'common/storage';
+import { storage, storedBooleanProp, StoredProp } from 'common/storage';
import { Chessground as makeChessground } from 'chessground';
export class MultiBoardCtrl {
playing: Toggle;
+ showResults: StoredProp;
teamSelect: Prop = prop('');
page: number = 1;
maxPerPageStorage = storage.make('study.multiBoard.maxPerPage');
@@ -28,6 +29,7 @@ export class MultiBoardCtrl {
readonly redraw: () => void,
) {
this.playing = toggle(false, this.redraw);
+ this.showResults = storedBooleanProp('study.showResults', true);
if (this.initialTeamSelect) this.onChapterChange(this.initialTeamSelect);
}
@@ -102,8 +104,16 @@ export function view(ctrl: MultiBoardCtrl, study: StudyCtrl): MaybeVNode {
ctrl.multiCloudEval &&
h('label.eval', [renderEvalToggle(ctrl.multiCloudEval), i18n.study.showEvalBar]),
renderPlayingToggle(ctrl),
+ renderShowResultsToggle(ctrl),
]),
]),
+ !ctrl.showResults()
+ ? h(
+ 'div.empty-boards-note',
+ { attrs: { 'data-icon': licon.InfoCircle } },
+ ' Since you chose to hide the results, all the preview boards are empty to avoid spoilers.',
+ )
+ : undefined,
h(
'div.now-playing',
{
@@ -111,7 +121,7 @@ export function view(ctrl: MultiBoardCtrl, study: StudyCtrl): MaybeVNode {
insert: gameLinksListener(study.chapterSelect),
},
},
- pager.currentPageResults.map(makePreview(baseUrl, study.vm.chapterId, cloudEval)),
+ pager.currentPageResults.map(makePreview(baseUrl, study.vm.chapterId, cloudEval, ctrl.showResults())),
),
]);
}
@@ -176,6 +186,15 @@ const renderPlayingToggle = (ctrl: MultiBoardCtrl): MaybeVNode =>
i18n.study.playing,
]);
+const renderShowResultsToggle = (ctrl: MultiBoardCtrl): MaybeVNode =>
+ h('label.results', [
+ h('input', {
+ attrs: { type: 'checkbox', checked: ctrl.showResults() },
+ hook: bind('change', e => ctrl.showResults((e.target as HTMLInputElement).checked), ctrl.redraw),
+ }),
+ i18n.study.showResults,
+ ]);
+
const previewToCgConfig = (cp: ChapterPreview): CgConfig => ({
fen: cp.fen,
lastMove: uciToMove(cp.lastMove),
@@ -184,8 +203,18 @@ const previewToCgConfig = (cp: ChapterPreview): CgConfig => ({
});
const makePreview =
- (roundPath: string, current: ChapterId, cloudEval?: MultiCloudEval) => (preview: ChapterPreview) => {
+ (roundPath: string, current: ChapterId, cloudEval?: MultiCloudEval, showResults?: boolean) =>
+ (preview: ChapterPreview) => {
const orientation = preview.orientation || 'white';
+ const baseConfig = {
+ coordinates: false,
+ viewOnly: true,
+ orientation,
+ drawable: {
+ enabled: false,
+ visible: false,
+ },
+ };
return h(
`a.mini-game.is2d.chap-${preview.id}`,
{
@@ -193,39 +222,45 @@ const makePreview =
attrs: gameLinkAttrs(roundPath, preview),
},
[
- boardPlayer(preview, CgOpposite(orientation)),
+ boardPlayer(preview, CgOpposite(orientation), showResults),
h('span.cg-gauge', [
- cloudEval && verticalEvalGauge(preview, cloudEval),
+ showResults ? cloudEval && verticalEvalGauge(preview, cloudEval) : undefined,
h(
'span.mini-game__board',
h('span.cg-wrap', {
hook: {
insert(vnode) {
const el = vnode.elm as HTMLElement;
- vnode.data!.cg = makeChessground(el, {
- ...previewToCgConfig(preview),
- coordinates: false,
- viewOnly: true,
- orientation,
- drawable: {
- enabled: false,
- visible: false,
- },
- });
+ vnode.data!.cg = showResults
+ ? makeChessground(el, {
+ ...previewToCgConfig(preview),
+ ...baseConfig,
+ })
+ : makeChessground(el, {
+ fen: '8/8/8/8/8/8/8/8',
+ ...baseConfig,
+ });
vnode.data!.fen = preview.fen;
},
postpatch(old, vnode) {
if (old.data!.fen !== preview.fen) {
old.data!.cg?.set(previewToCgConfig(preview));
}
+ // In this case, showResults was set to true but the cg fen is still on the initial pos
+ if (showResults && old.data!.cg.fen != old.data!.cg.getFen()) {
+ old.data!.cg.set(previewToCgConfig(preview));
+ }
vnode.data!.fen = preview.fen;
- vnode.data!.cg = old.data!.cg;
+ const el = vnode.elm as HTMLElement;
+ vnode.data!.cg = showResults
+ ? old.data!.cg
+ : makeChessground(el, { fen: '8/8/8/8/8/8/8/8', ...baseConfig });
},
},
}),
),
]),
- boardPlayer(preview, orientation),
+ boardPlayer(preview, orientation, showResults),
],
);
};
@@ -300,12 +335,12 @@ const computeTimeLeft = (preview: ChapterPreview, color: Color): number | undefi
} else return;
};
-const boardPlayer = (preview: ChapterPreview, color: Color) => {
+const boardPlayer = (preview: ChapterPreview, color: Color, showResults?: boolean) => {
const outcome = preview.status && preview.status !== '*' ? preview.status : undefined;
const player = preview.players?.[color],
score = outcome?.split('-')[color === 'white' ? 0 : 1];
return h('span.mini-game__player', [
player && renderUser(player),
- score ? h('span.mini-game__result', score) : renderClock(preview, color),
+ showResults ? (score ? h('span.mini-game__result', score) : renderClock(preview, color)) : undefined,
]);
};
diff --git a/ui/analyse/src/study/relay/relayGames.ts b/ui/analyse/src/study/relay/relayGames.ts
index 5d7869b6ff82..ed50e447e6ab 100644
--- a/ui/analyse/src/study/relay/relayGames.ts
+++ b/ui/analyse/src/study/relay/relayGames.ts
@@ -12,6 +12,7 @@ export const gamesList = (study: StudyCtrl, relay: RelayCtrl) => {
const chapters = study.chapters.list.all();
const cloudEval = study.multiCloudEval?.thisIfShowEval();
const roundPath = relay.roundPath();
+ const showResults = study.multiBoard.showResults();
return h(
'div.relay-games',
{
@@ -45,7 +46,7 @@ export const gamesList = (study: StudyCtrl, relay: RelayCtrl) => {
class: { 'relay-game--current': c.id === study.data.chapter.id },
},
[
- cloudEval && verticalEvalGauge(c, cloudEval),
+ showResults ? cloudEval && verticalEvalGauge(c, cloudEval) : undefined,
h(
'span.relay-game__players',
players.map((p, i) => {
@@ -58,7 +59,7 @@ export const gamesList = (study: StudyCtrl, relay: RelayCtrl) => {
playerFed(p.fed),
h('span.name', [userTitle(p), p.name]),
]),
- h(s === '1' ? 'good' : s === '0' ? 'bad' : 'status', [s]),
+ showResults ? h(s === '1' ? 'good' : s === '0' ? 'bad' : 'status', [s]) : null,
]
: [h('span.mini-game__user', h('span.name', 'Unknown player'))],
);
diff --git a/ui/analyse/src/study/studyCtrl.ts b/ui/analyse/src/study/studyCtrl.ts
index e93fd1fdd800..87368f07abd5 100644
--- a/ui/analyse/src/study/studyCtrl.ts
+++ b/ui/analyse/src/study/studyCtrl.ts
@@ -361,7 +361,9 @@ export default class StudyCtrl {
} else {
nextPath = sameChapter
? prevPath
- : this.data.chapter.relayPath || this.chapters.localPaths[this.vm.chapterId] || treePath.root;
+ : this.relay && !this.multiBoard.showResults()
+ ? treePath.root
+ : this.data.chapter.relayPath || this.chapters.localPaths[this.vm.chapterId] || treePath.root;
}
// path could be gone (because of subtree deletion), go as far as possible