From 9a379052e5bfb32b2bc20cdd76d107e69a35f743 Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Sun, 30 Nov 2025 10:54:52 +0100 Subject: [PATCH] Add user preference for black vs white display order - Gear icon in header opens settings modal - Preference stored in cookie for server-side Velocity rendering - ViewServlet reads blackFirst cookie into Velocity context - Velocity conditionals in pairing, results, and result-sheets templates --- CLAUDE.md | 19 ++++++++++++ .../org/jeudego/pairgoth/web/ViewServlet.kt | 5 ++++ view-webapp/src/main/sass/main.scss | 14 ++++++++- .../main/webapp/WEB-INF/layouts/standard.html | 20 +++++++++++++ view-webapp/src/main/webapp/js/main.js | 30 +++++++++++++++++++ .../src/main/webapp/result-sheets.html | 14 +++++++-- .../src/main/webapp/tour-pairing.inc.html | 18 ++++++++++- .../src/main/webapp/tour-results.inc.html | 13 +++++++- 8 files changed, 128 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ed31748..a34c2f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -158,3 +158,22 @@ Translations in `view-webapp/.../WEB-INF/translations/` - French (fr) - German (de) - Korean (ko) + +## Current Work + +### User Preferences (feature/user-preferences branch) +Implemented "black vs white" display order option: +- Gear icon in header opens settings modal +- Preference stored in cookie (`blackFirst`) for server-side Velocity rendering +- localStorage backup via store2 (`prefs.blackFirst`) +- Velocity conditionals in tour-pairing.inc.html, tour-results.inc.html, result-sheets.html +- ViewServlet reads cookie and sets `$blackFirst` in Velocity context + +Files modified: +- `view-webapp/.../layouts/standard.html` - gear icon + settings modal +- `view-webapp/.../sass/main.scss` - settings modal styles +- `view-webapp/.../js/main.js` - prefs object + modal handlers + cookie set +- `view-webapp/.../kotlin/.../ViewServlet.kt` - read blackFirst cookie +- `view-webapp/.../tour-pairing.inc.html` - `#if($blackFirst)` conditionals +- `view-webapp/.../tour-results.inc.html` - `#if($blackFirst)` conditionals + inverted result display +- `view-webapp/.../result-sheets.html` - `#if($blackFirst)` conditionals diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt index 166e737..e75ea37 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt @@ -43,6 +43,11 @@ class ViewServlet : VelocityViewServlet() { } } val lang = request.getAttribute("lang") as String + + // User preferences - read from cookie + val blackFirst = request.cookies?.find { it.name == "blackFirst" }?.value == "true" + context.put("blackFirst", blackFirst) + /* val menu = menuEntries!![uri] var title: String? = null diff --git a/view-webapp/src/main/sass/main.scss b/view-webapp/src/main/sass/main.scss index 9f61a22..5f3a59a 100644 --- a/view-webapp/src/main/sass/main.scss +++ b/view-webapp/src/main/sass/main.scss @@ -523,10 +523,22 @@ } } - #logout { + #logout, #settings { cursor: pointer; } + #settings-modal { + .setting { + margin: 0.5em 0; + label { + display: flex; + align-items: center; + gap: 0.5em; + cursor: pointer; + } + } + } + @media screen { #players-list { font-size: smaller; diff --git a/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html b/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html index 81c1e7d..19ecf05 100644 --- a/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html +++ b/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html @@ -44,6 +44,9 @@ #translate('tour-menu.inc.html') #end
+
+ +
@@ -84,6 +87,23 @@ #end #end
+ diff --git a/view-webapp/src/main/webapp/js/main.js b/view-webapp/src/main/webapp/js/main.js index 431cc26..d94d085 100644 --- a/view-webapp/src/main/webapp/js/main.js +++ b/view-webapp/src/main/webapp/js/main.js @@ -1,6 +1,21 @@ // Utilities const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +// User preferences +const prefs = { + get: function(key) { + return store('prefs.' + key); + }, + set: function(key, value) { + store('prefs.' + key, value); + }, + getAll: function() { + return { + blackFirst: this.get('blackFirst') || false + }; + } +}; function randomString(length) { let result = ''; const charactersLength = characters.length; @@ -349,6 +364,21 @@ onLoad(() => { if (!dialog) close_modal(); }); + // Settings modal handlers + $('#settings').on('click', e => { + modal('settings-modal'); + }); + + $('#settings-save').on('click', e => { + let blackFirst = $('#pref-black-first')[0].checked; + prefs.set('blackFirst', blackFirst); + // Set cookie for server-side rendering (expires in 1 year) + document.cookie = `blackFirst=${blackFirst}; path=/; max-age=31536000; SameSite=Lax`; + close_modal(); + // Reload page to apply new preference + window.location.reload(); + }); + if (isTouchDevice()) { $("[title]").on('click', e => { let item = e.target.closest('[title]'); diff --git a/view-webapp/src/main/webapp/result-sheets.html b/view-webapp/src/main/webapp/result-sheets.html index 966c119..003abf3 100644 --- a/view-webapp/src/main/webapp/result-sheets.html +++ b/view-webapp/src/main/webapp/result-sheets.html @@ -59,17 +59,27 @@ Surround winner's name or ½-½
+#if($blackFirst) +
+
Black
+
$black.name $!black.firstname #rank($black.rank)
#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end
+
+
½-½
+
+
White
+
$white.name $!white.firstname #rank($white.rank)
#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end
+
+#else
White
$white.name $!white.firstname #rank($white.rank)
#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end
-##
$white.egf
½-½
Black
$black.name $!black.firstname #rank($black.rank)
#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end
-##
$black.egf
+#end
Signature:
diff --git a/view-webapp/src/main/webapp/tour-pairing.inc.html b/view-webapp/src/main/webapp/tour-pairing.inc.html index 0601e18..5b1804d 100644 --- a/view-webapp/src/main/webapp/tour-pairing.inc.html +++ b/view-webapp/src/main/webapp/tour-pairing.inc.html @@ -69,15 +69,21 @@
-
## +
## #foreach($game in $games) #set($white = $pmap[$game.w]) #set($black = $pmap[$game.b])
${game.t}.
+#if($blackFirst) +
#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end
+
#if($black)#rank($black.rank)#end / #if($white)#rank($white.rank)#end
+
#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end
+#else
#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end
#if($white)#rank($white.rank)#end / #if($black)#rank($black.rank)#end
#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end
+#end
#if($game.h)h$game.h#{else} #end
#end## @@ -98,8 +104,13 @@ Tbl +#if($blackFirst) + Black + White +#else White Black +#end Hd @@ -109,8 +120,13 @@ #set($black = $pmap[$game.b]) ${game.t} +#if($blackFirst) + #if($black)${black.name} $!{black.firstname} (#rank($black.rank) $!black.country $!black.club)#{else}BIP#end + #if($white)${white.name} $!{white.firstname} (#rank($white.rank) $!white.country $!white.club)#{else}BIP#end +#else #if($white)${white.name} $!{white.firstname} (#rank($white.rank) $!white.country $!white.club)#{else}BIP#end #if($black)${black.name} $!{black.firstname} (#rank($black.rank) $!black.country $!black.club)#{else}BIP#end +#end ${game.h} #end diff --git a/view-webapp/src/main/webapp/tour-results.inc.html b/view-webapp/src/main/webapp/tour-results.inc.html index 4bdfd15..38e529f 100644 --- a/view-webapp/src/main/webapp/tour-results.inc.html +++ b/view-webapp/src/main/webapp/tour-results.inc.html @@ -18,23 +18,34 @@ +#if($blackFirst) + + +#else +#end #set($dispRst = {'?':'?', 'w':'1-0', 'b':'0-1', '=':'½-½', 'X':'X', '#':'1-1', '0':'0-0'}) +#set($dispRstInv = {'?':'?', 'w':'0-1', 'b':'1-0', '=':'½-½', 'X':'X', '#':'1-1', '0':'0-0'}) #foreach($game in $individualGames) #set($white = $plmap[$game.w]) #set($black = $plmap[$game.b]) #if($black && $white) +#if($blackFirst) + + +#else +#end - + #end #end
tableblackwhitewhite blackhd result
${game.t}.#if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end #if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end$!game.h$dispRst[$game.r]#if($blackFirst)$dispRstInv[$game.r]#{else}$dispRst[$game.r]#end