View layout in progress
This commit is contained in:
@@ -40,7 +40,7 @@ class FileStore(pathStr: String): StoreImplementation {
|
||||
return path.useDirectoryEntries("*.tour") { entries ->
|
||||
entries.mapNotNull { entry ->
|
||||
filenameRegex.matchEntire(entry.fileName.toString())?.groupValues?.get(1)?.toID()
|
||||
}.toSet()
|
||||
}.toSortedSet()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -170,7 +170,7 @@ class ApiServlet : HttpServlet() {
|
||||
}
|
||||
|
||||
// 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,
|
||||
"UTF-8 content expected"
|
||||
)
|
||||
@@ -215,6 +215,8 @@ class ApiServlet : HttpServlet() {
|
||||
HttpServletResponse.SC_BAD_REQUEST,
|
||||
"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(
|
||||
HttpServletResponse.SC_BAD_REQUEST,
|
||||
"Invalid 'Accept' header"
|
||||
|
@@ -196,34 +196,12 @@
|
||||
<version>70.1</version>
|
||||
</dependency>
|
||||
-->
|
||||
<!-- net clients
|
||||
<!-- http client -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.8.1</version>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<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="/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<div id="logo">Pairgoth v0.1</div>
|
||||
<body class="vert flex">
|
||||
<div id="header" class="horz flex">
|
||||
<div id="left-header" class="horz flex">
|
||||
<div id="logo" class="vert flex">
|
||||
<img src="/img/logo.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="menu">
|
||||
<button>New tournament</button>
|
||||
<button>Players</button>
|
||||
@@ -20,11 +24,16 @@
|
||||
<button>Results</button>
|
||||
<button>Standings</button>
|
||||
</div>
|
||||
<div id="right-header">
|
||||
[flag]
|
||||
</div>
|
||||
</div>
|
||||
<div id="center">
|
||||
#translate($page)
|
||||
</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>
|
||||
<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>
|
||||
|
@@ -17,7 +17,8 @@
|
||||
</toolbox>
|
||||
|
||||
<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>
|
||||
|
||||
</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.runtime.directive.Parse
|
||||
import org.jeudego.pairgoth.view.IntlTool
|
||||
|
||||
class TranslateDirective : Parse() {
|
||||
override fun getName(): String {
|
||||
@@ -11,7 +10,7 @@ class TranslateDirective : Parse() {
|
||||
|
||||
override fun getTemplate(path: String, encoding: String): Template? {
|
||||
val template = super.getTemplate(path, encoding)
|
||||
val translator = IntlTool.translator.get()
|
||||
val translator = TranslationTool.translator.get()
|
||||
?: throw RuntimeException("no current active translator")
|
||||
return translator.translate(path, template)
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import org.apache.velocity.tools.config.ValidScope
|
||||
import org.jeudego.pairgoth.util.Translator
|
||||
|
||||
@ValidScope("request")
|
||||
class IntlTool {
|
||||
class TranslationTool {
|
||||
|
||||
fun translate(enText: String): String {
|
||||
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.Companion.defaultLanguage
|
||||
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.FilterChain
|
||||
import javax.servlet.FilterConfig
|
||||
@@ -25,7 +25,7 @@ class LanguageFilter : Filter {
|
||||
|
||||
val reqLang = request.getAttribute("lang") as String?
|
||||
if (reqLang != null) {
|
||||
IntlTool.translator.set(Translator.getTranslator(reqLang))
|
||||
TranslationTool.translator.set(Translator.getTranslator(reqLang))
|
||||
chain.doFilter(request, response)
|
||||
} else {
|
||||
val uri = request.requestURI
|
||||
|
@@ -1,19 +1,38 @@
|
||||
body {
|
||||
font-size : clamp(2rem, 10vw, 5rem);
|
||||
font-size : clamp(1rem, 3vw, 3rem);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
&.horz {
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
&.vert {
|
||||
flex-flow: column nowrap;
|
||||
justify-items: stretch;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
#header {
|
||||
height: 1.5em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-items: center;
|
||||
align-items: stretch;
|
||||
height: 2em;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
#left-header, #right-header {
|
||||
height: 100%;
|
||||
}
|
||||
#logo {
|
||||
height: 100%;
|
||||
img {
|
||||
display: inline-block;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
@@ -24,5 +43,9 @@ body {
|
||||
}
|
||||
|
||||
#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>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
|
Reference in New Issue
Block a user