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
This commit is contained in:
19
CLAUDE.md
19
CLAUDE.md
@@ -158,3 +158,22 @@ Translations in `view-webapp/.../WEB-INF/translations/`
|
|||||||
- French (fr)
|
- French (fr)
|
||||||
- German (de)
|
- German (de)
|
||||||
- Korean (ko)
|
- 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
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ class ViewServlet : VelocityViewServlet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val lang = request.getAttribute("lang") as String
|
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]
|
val menu = menuEntries!![uri]
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
|
|||||||
@@ -523,10 +523,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#logout {
|
#logout, #settings {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings-modal {
|
||||||
|
.setting {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen {
|
@media screen {
|
||||||
#players-list {
|
#players-list {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
|
|||||||
@@ -44,6 +44,9 @@
|
|||||||
#translate('tour-menu.inc.html')
|
#translate('tour-menu.inc.html')
|
||||||
#end
|
#end
|
||||||
<div id="header-right">
|
<div id="header-right">
|
||||||
|
<div id="settings" title="Settings">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
</div>
|
||||||
<div id="lang">
|
<div id="lang">
|
||||||
<i class="$translate.flags[$request.lang] flag"></i>
|
<i class="$translate.flags[$request.lang] flag"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,6 +87,23 @@
|
|||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
</div>
|
</div>
|
||||||
|
<div id="settings-modal" class="popup">
|
||||||
|
<div class="popup-body">
|
||||||
|
<div class="popup-header">Settings</div>
|
||||||
|
<div class="popup-content">
|
||||||
|
<div class="setting">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="pref-black-first" #if($blackFirst)checked#end />
|
||||||
|
Display games as "Black vs White" (instead of "White vs Black")
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-footer">
|
||||||
|
<button class="ui button" id="settings-save">Save</button>
|
||||||
|
<button class="ui button gray close">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="text/javascript" src="/lib/store2-2.14.2.min.js"></script>
|
<script type="text/javascript" src="/lib/store2-2.14.2.min.js"></script>
|
||||||
<script type="text/javascript" src="/lib/tablesort-5.4.0/tablesort.min.js"></script>
|
<script type="text/javascript" src="/lib/tablesort-5.4.0/tablesort.min.js"></script>
|
||||||
<script type="text/javascript" src="/lib/tablesort-5.4.0/sorts/tablesort.number.min.js"></script>
|
<script type="text/javascript" src="/lib/tablesort-5.4.0/sorts/tablesort.number.min.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
// Utilities
|
// Utilities
|
||||||
|
|
||||||
const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
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) {
|
function randomString(length) {
|
||||||
let result = '';
|
let result = '';
|
||||||
const charactersLength = characters.length;
|
const charactersLength = characters.length;
|
||||||
@@ -349,6 +364,21 @@ onLoad(() => {
|
|||||||
if (!dialog) close_modal();
|
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()) {
|
if (isTouchDevice()) {
|
||||||
$("[title]").on('click', e => {
|
$("[title]").on('click', e => {
|
||||||
let item = e.target.closest('[title]');
|
let item = e.target.closest('[title]');
|
||||||
|
|||||||
@@ -59,17 +59,27 @@
|
|||||||
Surround winner's name or ½-½
|
Surround winner's name or ½-½
|
||||||
</div>
|
</div>
|
||||||
<div class="players">
|
<div class="players">
|
||||||
|
#if($blackFirst)
|
||||||
|
<div class="black player">
|
||||||
|
<div class="color">Black</div>
|
||||||
|
<div class="name">$black.name $!black.firstname #rank($black.rank)<br/>#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end</div>
|
||||||
|
</div>
|
||||||
|
<div class="equal">½-½</div>
|
||||||
|
<div class="white player">
|
||||||
|
<div class="color">White</div>
|
||||||
|
<div class="name">$white.name $!white.firstname #rank($white.rank)<br/>#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end</div>
|
||||||
|
</div>
|
||||||
|
#else
|
||||||
<div class="white player">
|
<div class="white player">
|
||||||
<div class="color">White</div>
|
<div class="color">White</div>
|
||||||
<div class="name">$white.name $!white.firstname #rank($white.rank)<br/>#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end</div>
|
<div class="name">$white.name $!white.firstname #rank($white.rank)<br/>#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end</div>
|
||||||
## <div class="pin">$white.egf</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="equal">½-½</div>
|
<div class="equal">½-½</div>
|
||||||
<div class="black player">
|
<div class="black player">
|
||||||
<div class="color">Black</div>
|
<div class="color">Black</div>
|
||||||
<div class="name">$black.name $!black.firstname #rank($black.rank)<br/>#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end</div>
|
<div class="name">$black.name $!black.firstname #rank($black.rank)<br/>#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end</div>
|
||||||
## <div class="pin">$black.egf</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
#end
|
||||||
</div>
|
</div>
|
||||||
<div class="signatures">
|
<div class="signatures">
|
||||||
<div class="signature">Signature:</div>
|
<div class="signature">Signature:</div>
|
||||||
|
|||||||
@@ -69,15 +69,21 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div id="paired" class="multi-select" title="white vs. black">##
|
<div id="paired" class="multi-select" title="#if($blackFirst)black vs. white#{else}white vs. black#end">##
|
||||||
#foreach($game in $games)
|
#foreach($game in $games)
|
||||||
#set($white = $pmap[$game.w])
|
#set($white = $pmap[$game.w])
|
||||||
#set($black = $pmap[$game.b])
|
#set($black = $pmap[$game.b])
|
||||||
<div class="listitem game" data-id="$game.id">
|
<div class="listitem game" data-id="$game.id">
|
||||||
<div class="table" data-value="$game.t">${game.t}.</div>
|
<div class="table" data-value="$game.t">${game.t}.</div>
|
||||||
|
#if($blackFirst)
|
||||||
|
<div class="black" data-id="$game.b">#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end</div>
|
||||||
|
<div class="levels">#if($black)#rank($black.rank)#end / #if($white)#rank($white.rank)#end</div>
|
||||||
|
<div class="white" data-id="$game.w">#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end</div>
|
||||||
|
#else
|
||||||
<div class="white" data-id="$game.w">#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end</div>
|
<div class="white" data-id="$game.w">#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end</div>
|
||||||
<div class="levels">#if($white)#rank($white.rank)#end / #if($black)#rank($black.rank)#end</div>
|
<div class="levels">#if($white)#rank($white.rank)#end / #if($black)#rank($black.rank)#end</div>
|
||||||
<div class="black" data-id="$game.b">#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end</div>
|
<div class="black" data-id="$game.b">#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end</div>
|
||||||
|
#end
|
||||||
<div class="handicap" data-value="$game.h">#if($game.h)h$game.h#{else} #end</div>
|
<div class="handicap" data-value="$game.h">#if($game.h)h$game.h#{else} #end</div>
|
||||||
</div>
|
</div>
|
||||||
#end##
|
#end##
|
||||||
@@ -98,8 +104,13 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Tbl</th>
|
<th>Tbl</th>
|
||||||
|
#if($blackFirst)
|
||||||
|
<th>Black</th>
|
||||||
|
<th>White</th>
|
||||||
|
#else
|
||||||
<th>White</th>
|
<th>White</th>
|
||||||
<th>Black</th>
|
<th>Black</th>
|
||||||
|
#end
|
||||||
<th>Hd</th>
|
<th>Hd</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -109,8 +120,13 @@
|
|||||||
#set($black = $pmap[$game.b])
|
#set($black = $pmap[$game.b])
|
||||||
<tr>
|
<tr>
|
||||||
<td class="t" data-table="${game.t}">${game.t}</td>
|
<td class="t" data-table="${game.t}">${game.t}</td>
|
||||||
|
#if($blackFirst)
|
||||||
|
<td class="left">#if($black)${black.name} $!{black.firstname} (#rank($black.rank) $!black.country $!black.club)#{else}BIP#end</td>
|
||||||
|
<td class="left">#if($white)${white.name} $!{white.firstname} (#rank($white.rank) $!white.country $!white.club)#{else}BIP#end</td>
|
||||||
|
#else
|
||||||
<td class="left">#if($white)${white.name} $!{white.firstname} (#rank($white.rank) $!white.country $!white.club)#{else}BIP#end</td>
|
<td class="left">#if($white)${white.name} $!{white.firstname} (#rank($white.rank) $!white.country $!white.club)#{else}BIP#end</td>
|
||||||
<td class="left">#if($black)${black.name} $!{black.firstname} (#rank($black.rank) $!black.country $!black.club)#{else}BIP#end</td>
|
<td class="left">#if($black)${black.name} $!{black.firstname} (#rank($black.rank) $!black.country $!black.club)#{else}BIP#end</td>
|
||||||
|
#end
|
||||||
<td>${game.h}</td>
|
<td>${game.h}</td>
|
||||||
</tr>
|
</tr>
|
||||||
#end
|
#end
|
||||||
|
|||||||
@@ -18,23 +18,34 @@
|
|||||||
<table id="results-table" class="ui celled striped table">
|
<table id="results-table" class="ui celled striped table">
|
||||||
<thead class="centered">
|
<thead class="centered">
|
||||||
<th data-sort-method="number">table</th>
|
<th data-sort-method="number">table</th>
|
||||||
|
#if($blackFirst)
|
||||||
|
<th>black</th>
|
||||||
|
<th>white</th>
|
||||||
|
#else
|
||||||
<th>white</th>
|
<th>white</th>
|
||||||
<th>black</th>
|
<th>black</th>
|
||||||
|
#end
|
||||||
<th>hd</th>
|
<th>hd</th>
|
||||||
<th>result</th>
|
<th>result</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
#set($dispRst = {'?':'?', 'w':'1-0', 'b':'0-1', '=':'½-½', 'X':'X', '#':'1-1', '0':'0-0'})
|
#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)
|
#foreach($game in $individualGames)
|
||||||
#set($white = $plmap[$game.w])
|
#set($white = $plmap[$game.w])
|
||||||
#set($black = $plmap[$game.b])
|
#set($black = $plmap[$game.b])
|
||||||
#if($black && $white)
|
#if($black && $white)
|
||||||
<tr id="result-$game.id" data-id="$game.id">
|
<tr id="result-$game.id" data-id="$game.id">
|
||||||
<td data-sort="$game.t">${game.t}.</td>
|
<td data-sort="$game.t">${game.t}.</td>
|
||||||
|
#if($blackFirst)
|
||||||
|
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name#if($black.firstname) $black.firstname#end"><span>#if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end</span></td>
|
||||||
|
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name#if($white.firstname) $white.firstname#end"><span>#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end</span></td>
|
||||||
|
#else
|
||||||
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name#if($white.firstname) $white.firstname#end"><span>#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end</span></td>
|
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name#if($white.firstname) $white.firstname#end"><span>#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end</span></td>
|
||||||
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name#if($black.firstname) $black.firstname#end"><span>#if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end</span></td>
|
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name#if($black.firstname) $black.firstname#end"><span>#if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end</span></td>
|
||||||
|
#end
|
||||||
<td class="handicap centered">$!game.h</td>
|
<td class="handicap centered">$!game.h</td>
|
||||||
<td class="result centered" data-sort="$game.r" data-result="$game.r">$dispRst[$game.r]</td>
|
<td class="result centered" data-sort="$game.r" data-result="$game.r">#if($blackFirst)$dispRstInv[$game.r]#{else}$dispRst[$game.r]#end</td>
|
||||||
</tr>
|
</tr>
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|||||||
Reference in New Issue
Block a user