From b2c56145c47b7ef724ac193651bde0634fe21c4f Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Sat, 18 Nov 2023 08:31:24 +0100 Subject: [PATCH] WIP --- api-webapp/pom.xml | 2 +- .../org/jeudego/pairgoth/model/Pairing.kt | 2 +- .../org/jeudego/pairgoth/server/ApiServlet.kt | 1 + .../org/jeudego/pairgoth/util/Translator.kt | 9 +- .../jeudego/pairgoth/web/LanguageFilter.kt | 18 +- view-webapp/src/main/webapp/index.html | 6 +- view-webapp/src/main/webapp/js/main.js | 16 +- view-webapp/src/main/webapp/tour.html | 236 +++++++++++------- 8 files changed, 184 insertions(+), 106 deletions(-) diff --git a/api-webapp/pom.xml b/api-webapp/pom.xml index 24e4db6..4d46cab 100644 --- a/api-webapp/pom.xml +++ b/api-webapp/pom.xml @@ -67,7 +67,7 @@ ${pairgoth.api.external.url} ${pairgoth.webapp.external.url} ${pairgoth.store} - ${pairgoth.store} + ${pairgoth.store.file.path} ${pairgoth.logger.level} ${pairgoth.logger.format} diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt index f4e0662..5afe76a 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt @@ -314,7 +314,7 @@ fun Pairing.toJson() = Json.Object( "type" to type.name, "base" to pairingParams.base.toJson(), "main" to pairingParams.main.toJson(), - "secondary" to pairingParams.main.toJson(), + "secondary" to pairingParams.secondary.toJson(), "geo" to pairingParams.geo.toJson(), "handicap" to pairingParams.handicap.toJson(), "placement" to placementParams.toJson() diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt index e6301b3..3404cb6 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt @@ -248,6 +248,7 @@ class ApiServlet : HttpServlet() { response.status = code if (response.isCommitted) return val errorPayload = Json.Object( + "success" to false, "error" to (message ?: "unknown error") ) setContentType(response) diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Translator.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Translator.kt index 94752d8..b666541 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Translator.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Translator.kt @@ -80,11 +80,15 @@ class Translator private constructor(private val iso: String) { if (groupStart == -1) throw RuntimeException("unexpected case") if (groupStart > start) output.print(text.substring(start, groupStart)) val capture = matcher.group(group) - var token: String = StringEscapeUtils.unescapeHtml4(capture) + + // CB TODO - unescape and escape steps removed, because it breaks text blocks containing unescaped quotes. + // See how it impacts the remaining. + + var token: String = capture // StringEscapeUtils.unescapeHtml4(capture) if (StringUtils.containsOnly(token, "\r\n\t -;:.'\"/<>\u00A00123456789€[]!")) output.print(capture) else { token = normalize(token) token = translate(token) - output.print(StringEscapeUtils.escapeHtml4(token)) + output.print(token) // (StringEscapeUtils.escapeHtml4(token)) } val groupEnd = matcher.end(group) if (groupEnd < end) output.print(text.substring(groupEnd, end)) @@ -168,6 +172,7 @@ class Translator private constructor(private val iso: String) { val providedLanguages = setOf("en", "fr") const val defaultLanguage = "en" + const val defaultLocale = "en" internal fun notifyExiting() { translators.values.filter { diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt index 5e6a0d8..80abcef 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt @@ -53,15 +53,21 @@ class LanguageFilter : Filter { } private fun getPreferredLanguage(request: HttpServletRequest): String { - return (request.session.getAttribute("lang") as String?) ?: - ( langHeaderParser.findAll(request.getHeader("Accept-Language") ?: "").filter { + var lang = request.session.getAttribute("lang") as String? + if (lang == null) { + parseLanguageHeader(request) + lang = request.session.getAttribute("lang") as String? + } + return lang ?: defaultLanguage + } + + private fun parseLanguageHeader(request: HttpServletRequest) { + langHeaderParser.findAll(request.getHeader("Accept-Language") ?: "").filter { providedLanguages.contains(it.groupValues[1]) }.sortedByDescending { - it.groupValues[2].toDoubleOrNull() ?: 1.0 + it.groupValues[3].toDoubleOrNull() ?: 1.0 }.firstOrNull()?.let { it.groupValues[1] - } ?: defaultLanguage ).also { - request.session.setAttribute("lang", it) } } @@ -69,6 +75,6 @@ class LanguageFilter : Filter { companion object { private val langPattern = Regex("/([a-z]{2})(/.+)") - private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)\\w+)?)(?:;q=([0-9.]+))?") + private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)\\w+)?)(?:;q=([0-9.]+))?") ADD locale group captue } } diff --git a/view-webapp/src/main/webapp/index.html b/view-webapp/src/main/webapp/index.html index 3241344..32ce041 100644 --- a/view-webapp/src/main/webapp/index.html +++ b/view-webapp/src/main/webapp/index.html @@ -7,7 +7,7 @@ #foreach($tour in $api.get('tour').entrySet())
$tour - + Open @@ -68,7 +68,3 @@ }); // ]]# - - - - diff --git a/view-webapp/src/main/webapp/js/main.js b/view-webapp/src/main/webapp/js/main.js index 9024b5a..fc0db8a 100644 --- a/view-webapp/src/main/webapp/js/main.js +++ b/view-webapp/src/main/webapp/js/main.js @@ -106,19 +106,23 @@ Element.prototype.modal = function(show) { function formValue(name) { let ctl = $(`[name="${name}"]`)[0]; - let type = ctl.tagName; + if (!ctl) { + console.error(`unknown input name: ${name}`) + } + let tag = ctl.tagName; + let type = tag === 'INPUT' ? ctl.attr('type') : undefined; if ( - (type === 'INPUT' && ['text', 'number'].includes(ctl.attr('type'))) || - type === 'SELECT' + (tag === 'INPUT' && ['text', 'number'].includes(ctl.attr('type'))) || + tag === 'SELECT' ) { return ctl.value; - } else if (type === 'INPUT' && ctl.attr('type') === 'radio') { + } else if (tag === 'INPUT' && ctl.attr('type') === 'radio') { ctl = $(`input[name="${name}"]:checked`)[0]; if (ctl) return ctl.value; - } else if (type === 'INPUT' && ctl.attr('type') === 'radio') { + } else if (tag === 'INPUT' && ctl.attr('type') === 'checkbox') { return ctl.checked; } - console.error(`unknown input name: ${name}`); + console.error(`unhandled input tag or type for input ${name} (tag: ${tag}, type:${type}`); return null; } diff --git a/view-webapp/src/main/webapp/tour.html b/view-webapp/src/main/webapp/tour.html index 7ad4dc9..518110f 100644 --- a/view-webapp/src/main/webapp/tour.html +++ b/view-webapp/src/main/webapp/tour.html @@ -1,17 +1,17 @@ -#macro(hms $value)#if($value)#set($hh = $value / 3600)#set($mm = ($value - $hh) / 60)#set($ss = $value % 60)$hh:$mm:$ss#end#end +#macro(toHMS $value)#if($value)#set($hh = $value / 3600)#set($mm = ($value - $hh) / 60)#set($ss = $value % 60)$hh:$mm:$ss#end#end #macro(levels $sel) - #foreach($k in [-30..-1]) - #set($disp = "${math.abs($k)}k" - - #end - #foreach($d in [0..9]) + #foreach($d in [8..0]) #set($dan = $d + 1) #set($disp = "${dan}d") - + + #end + #foreach($k in [-1..-30]) + #set($disp = "${math.abs($k)}k") + #end #end -#if($params.tour) - #set($tour = $api.get("tour/${params.tour}")) +#if($params.id) + #set($tour = $api.get("tour/${params.id}")) #if (!$tour)

Invalid tournament id

@@ -25,21 +25,23 @@
- +
- +
-
- - +
+
+ + from - + to - + +
@@ -47,7 +49,7 @@
@@ -57,7 +59,7 @@
or
@@ -65,43 +67,79 @@
-
+
- rounds +
-
- - #set($floor = -20) -#if($tour) #set($floor = $tour.pairing. +#if($tour) #set($floor = $tour.pairing.mmFloor) #end +#levels($floor)
-
- +*# + + + +
+ +
+ +
@@ -109,17 +147,17 @@
@@ -131,35 +169,35 @@
- +
- +
- +
- +
- +
- +
@@ -181,12 +219,21 @@ // #[[ const safeRegex = /^[-a-zA-Z0-9_.]+$/; function parseDate(value) { + return value; + /* let locale = Datepicker.locales[lang]; if (locale) { let date = Datepicker.parseDate(value, locale.format, locale); return Datepicker.formatDate(date, 'yyyy-mm-dd') } else return undefined; + */ + } + function fromHMS(value) { + if (value && /\d+:\d+:\d+/.test(value)) { + let parts = value.split(':'); + return parts[0] * 3600 + parts[1] * 60 + parts[2]; + } } onLoad(() => { @@ -215,6 +262,7 @@ valid = false; shortNameCtl.setCustomValidity(msg('invalid_character')); } + if (!valid) return; }); new DateRangePicker($('#date-range')[0], { @@ -222,38 +270,6 @@ language: lang }); - $('#tournament-infos').on('submit', e => { - e.preventDefault(); - let tour = { - name: formValue('name'), - shortName: formValue('shortName'), - startDate: parseDate(formValue('startDate')), - endDate: parseDate(formValue('endDate')), - type: formValue('type'), - rounds: formValue('rounds'), - country: formValue('country'), - online: formValue('online'), - location: formValue('online') ? "" : formValue('location'), - pairing: { - type: formValue('pairing') - }, - timeSystem: { - type: formValue('timeSystemType') - } - } - console.log(tour); - if (typeof(tour_id) !== 'undefined') { - api.putJson(`tour/${tour_id}`, tour); - } else { - api.postJson('tour', tour) - .then((o) => { - if (o !== 'error') { - console.log("success ==> %o", o); - } - }); - } - }); - $('input[name="online"]').on('change', e => { $('input[name="location"]')[0].disabled = e.target.checked; }); @@ -296,12 +312,62 @@ lazy: false, overwrite: true }); + + $('#tournament-infos').on('submit', e => { + e.preventDefault(); + let tour = { + name: formValue('name'), + shortName: formValue('shortName'), + startDate: parseDate(formValue('startDate')), + endDate: parseDate(formValue('endDate')), + type: formValue('type'), + rounds: formValue('rounds'), + country: formValue('country'), + online: formValue('online'), + location: formValue('online') ? "" : formValue('location'), + pairing: { + type: formValue('pairing'), + // mmFloor: formValue('mmFloor'), + mmBar: formValue('mmBar'), + main: { + firstSeed: formValue('firstSeed'), + secondSeed: formValue('secondSeed') + }, + handicap: { + correction: formValue('correction'), + treshold: formValue('treshold') + } + }, + timeSystem: { + type: formValue('timeSystemType'), + mainTime: fromHMS(formValue('mainTime')), + increment: fromHMS(formValue('increment')), + maxTime: fromHMS(formValue('maxTime')), + byoyomi: fromHMS(formValue('byoyomi')), + periods: formValue('periods'), + stones: formValue('stones') + } + } + console.log(tour); + if (typeof(tour_id) !== 'undefined') { + api.putJson(`tour/${tour_id}`, tour); + } else { + api.postJson('tour', tour) + .then((o) => { + if (o !== 'error') { + console.log("success ==> %o", o); + } + }); + } + }); }); // ]]# +