View layout in progress
This commit is contained in:
@@ -40,7 +40,7 @@ class FileStore(pathStr: String): StoreImplementation {
|
|||||||
return path.useDirectoryEntries("*.tour") { entries ->
|
return path.useDirectoryEntries("*.tour") { entries ->
|
||||||
entries.mapNotNull { entry ->
|
entries.mapNotNull { entry ->
|
||||||
filenameRegex.matchEntire(entry.fileName.toString())?.groupValues?.get(1)?.toID()
|
filenameRegex.matchEntire(entry.fileName.toString())?.groupValues?.get(1)?.toID()
|
||||||
}.toSet()
|
}.toSortedSet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -170,7 +170,7 @@ class ApiServlet : HttpServlet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check charset
|
// check charset
|
||||||
if (charset != null && EXPECTED_CHARSET != charset) throw ApiException(
|
if (charset != null && EXPECTED_CHARSET != charset.lowercase(Locale.ROOT).replace("-", "")) throw ApiException(
|
||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"UTF-8 content expected"
|
"UTF-8 content expected"
|
||||||
)
|
)
|
||||||
@@ -215,6 +215,8 @@ class ApiServlet : HttpServlet() {
|
|||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"Missing 'Accept' header"
|
"Missing 'Accept' header"
|
||||||
)
|
)
|
||||||
|
// CB TODO 1) a reference to a specific API call at this point is a code smell.
|
||||||
|
// 2) there will e other content types: .tou, .h9, .html
|
||||||
if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
|
if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
|
||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"Invalid 'Accept' header"
|
"Invalid 'Accept' header"
|
||||||
|
@@ -196,34 +196,12 @@
|
|||||||
<version>70.1</version>
|
<version>70.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
-->
|
-->
|
||||||
<!-- net clients
|
<!-- http client -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
<version>4.5.13</version>
|
<version>4.8.1</version>
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<artifactId>commons-logging</artifactId>
|
|
||||||
<groupId>commons-logging</groupId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpmime</artifactId>
|
|
||||||
<version>4.5.13</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>2.11.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-net</groupId>
|
|
||||||
<artifactId>commons-net</artifactId>
|
|
||||||
<version>3.8.0</version>
|
|
||||||
</dependency>
|
|
||||||
-->
|
|
||||||
<!-- server-side events -->
|
<!-- server-side events -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.republicate</groupId>
|
<groupId>com.republicate</groupId>
|
||||||
|
@@ -9,9 +9,13 @@
|
|||||||
<link rel="stylesheet" href="/fonts/fork-awesome-1.2.0/fork-awesome.min.css">
|
<link rel="stylesheet" href="/fonts/fork-awesome-1.2.0/fork-awesome.min.css">
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="vert flex">
|
||||||
<div id="header">
|
<div id="header" class="horz flex">
|
||||||
<div id="logo">Pairgoth v0.1</div>
|
<div id="left-header" class="horz flex">
|
||||||
|
<div id="logo" class="vert flex">
|
||||||
|
<img src="/img/logo.svg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<button>New tournament</button>
|
<button>New tournament</button>
|
||||||
<button>Players</button>
|
<button>Players</button>
|
||||||
@@ -20,11 +24,16 @@
|
|||||||
<button>Results</button>
|
<button>Results</button>
|
||||||
<button>Standings</button>
|
<button>Standings</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="right-header">
|
||||||
|
[flag]
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="center">
|
<div id="center">
|
||||||
#translate($page)
|
#translate($page)
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer" class="horz flex">
|
||||||
|
<div id="version">pairgoth v0.1</div>
|
||||||
|
<div id="contact"><a href="mailto:pairgoth@jeudego.org">contact</a></div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="/js/store2-2.14.2.min.js"></script>
|
<script type="text/javascript" src="/js/store2-2.14.2.min.js"></script>
|
||||||
<script type="text/javascript" src="/js/tablesort-5.4.0.min.js"></script>
|
<script type="text/javascript" src="/js/tablesort-5.4.0.min.js"></script>
|
||||||
|
@@ -17,7 +17,8 @@
|
|||||||
</toolbox>
|
</toolbox>
|
||||||
|
|
||||||
<toolbox scope="request">
|
<toolbox scope="request">
|
||||||
<tool key="intl" class="org.jeudego.pairgoth.view.IntlTool"/>
|
<tool key="translate" class="org.jeudego.pairgoth.view.TranslationTool"/>
|
||||||
|
<tool key="api" class="org.jeudego.pairgoth.view.ApiTool"/>
|
||||||
</toolbox>
|
</toolbox>
|
||||||
|
|
||||||
</tools>
|
</tools>
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
package org.jeudego.pairgoth.view
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.internal.EMPTY_REQUEST
|
||||||
|
|
||||||
|
class ApiTool {
|
||||||
|
companion object {
|
||||||
|
const val JSON = "application/json"
|
||||||
|
val apiRoot = System.getProperty("pairgoth.webapp.url").let { base ->
|
||||||
|
if (base.endsWith('/')) "${base}api/"
|
||||||
|
else "${base}/api/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
private fun prepare(url: String) = Request.Builder().url("$apiRoot$url").header("Accept", JSON)
|
||||||
|
private fun Json.toRequestBody() = toString().toRequestBody(JSON.toMediaType())
|
||||||
|
private fun Request.Builder.process(): Json {
|
||||||
|
client.newCall(build()).execute().use { response ->
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
when (response.body?.contentType()?.subtype) {
|
||||||
|
null -> throw Error("null body or content type")
|
||||||
|
"json" -> return Json.parse(response.body!!.string()) ?: throw Error("could not parse json")
|
||||||
|
else -> throw Error("unhandled content type: ${response.body!!.contentType()}")
|
||||||
|
}
|
||||||
|
} else throw Error("api call failed: ${response.code} ${response.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(url: String) = prepare(url).process()
|
||||||
|
fun post(url: String, payload: Json) = prepare(url)
|
||||||
|
.post(payload.toRequestBody())
|
||||||
|
.process()
|
||||||
|
fun put(url: String, payload: Json) = prepare(url)
|
||||||
|
.put(payload.toRequestBody())
|
||||||
|
.process()
|
||||||
|
fun delete(url: String, payload: Json? = null) = prepare(url)
|
||||||
|
.delete(payload?.toRequestBody() ?: EMPTY_REQUEST)
|
||||||
|
.process()
|
||||||
|
}
|
@@ -2,7 +2,6 @@ package org.jeudego.pairgoth.view
|
|||||||
|
|
||||||
import org.apache.velocity.Template
|
import org.apache.velocity.Template
|
||||||
import org.apache.velocity.runtime.directive.Parse
|
import org.apache.velocity.runtime.directive.Parse
|
||||||
import org.jeudego.pairgoth.view.IntlTool
|
|
||||||
|
|
||||||
class TranslateDirective : Parse() {
|
class TranslateDirective : Parse() {
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
@@ -11,7 +10,7 @@ class TranslateDirective : Parse() {
|
|||||||
|
|
||||||
override fun getTemplate(path: String, encoding: String): Template? {
|
override fun getTemplate(path: String, encoding: String): Template? {
|
||||||
val template = super.getTemplate(path, encoding)
|
val template = super.getTemplate(path, encoding)
|
||||||
val translator = IntlTool.translator.get()
|
val translator = TranslationTool.translator.get()
|
||||||
?: throw RuntimeException("no current active translator")
|
?: throw RuntimeException("no current active translator")
|
||||||
return translator.translate(path, template)
|
return translator.translate(path, template)
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import org.apache.velocity.tools.config.ValidScope
|
|||||||
import org.jeudego.pairgoth.util.Translator
|
import org.jeudego.pairgoth.util.Translator
|
||||||
|
|
||||||
@ValidScope("request")
|
@ValidScope("request")
|
||||||
class IntlTool {
|
class TranslationTool {
|
||||||
|
|
||||||
fun translate(enText: String): String {
|
fun translate(enText: String): String {
|
||||||
return translator.get().translate(enText)
|
return translator.get().translate(enText)
|
@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.web
|
|||||||
import org.jeudego.pairgoth.util.Translator
|
import org.jeudego.pairgoth.util.Translator
|
||||||
import org.jeudego.pairgoth.util.Translator.Companion.defaultLanguage
|
import org.jeudego.pairgoth.util.Translator.Companion.defaultLanguage
|
||||||
import org.jeudego.pairgoth.util.Translator.Companion.providedLanguages
|
import org.jeudego.pairgoth.util.Translator.Companion.providedLanguages
|
||||||
import org.jeudego.pairgoth.view.IntlTool
|
import org.jeudego.pairgoth.view.TranslationTool
|
||||||
import javax.servlet.Filter
|
import javax.servlet.Filter
|
||||||
import javax.servlet.FilterChain
|
import javax.servlet.FilterChain
|
||||||
import javax.servlet.FilterConfig
|
import javax.servlet.FilterConfig
|
||||||
@@ -25,7 +25,7 @@ class LanguageFilter : Filter {
|
|||||||
|
|
||||||
val reqLang = request.getAttribute("lang") as String?
|
val reqLang = request.getAttribute("lang") as String?
|
||||||
if (reqLang != null) {
|
if (reqLang != null) {
|
||||||
IntlTool.translator.set(Translator.getTranslator(reqLang))
|
TranslationTool.translator.set(Translator.getTranslator(reqLang))
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
val uri = request.requestURI
|
val uri = request.requestURI
|
||||||
|
@@ -1,19 +1,38 @@
|
|||||||
body {
|
body {
|
||||||
font-size : clamp(2rem, 10vw, 5rem);
|
font-size : clamp(1rem, 3vw, 3rem);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
&.horz {
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&.vert {
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
justify-items: stretch;
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
height: 1.5em;
|
height: 2em;
|
||||||
display: flex;
|
width: 100%;
|
||||||
flex-flow: row nowrap;
|
position: relative;
|
||||||
justify-items: center;
|
justify-content: space-between;
|
||||||
align-items: stretch;
|
#left-header, #right-header {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#logo {
|
||||||
|
height: 100%;
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#center {
|
#center {
|
||||||
@@ -24,5 +43,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
height: 1.5em;
|
flex: 0;
|
||||||
|
height: 2em;
|
||||||
|
margin: 0 2em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
BIN
view-webapp/src/main/webapp/favicon.ico
Normal file
BIN
view-webapp/src/main/webapp/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
18
view-webapp/src/main/webapp/img/logo.svg
Normal file
18
view-webapp/src/main/webapp/img/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 19 KiB |
@@ -1 +1,4 @@
|
|||||||
|
|
||||||
|
Known tournaments:
|
||||||
|
|
||||||
|
$api.get('tour')
|
||||||
|
@@ -61,6 +61,11 @@
|
|||||||
<artifactId>http2-server</artifactId>
|
<artifactId>http2-server</artifactId>
|
||||||
<version>${jetty.version}</version>
|
<version>${jetty.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-slf4j-impl</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
|
Reference in New Issue
Block a user