Example tournaments
This commit is contained in:
@@ -235,8 +235,18 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
||||||
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
|
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
|
||||||
)
|
)
|
||||||
json["pairables"]?.let { pairables ->
|
(json["players"] as Json.Array?)?.forEach { obj ->
|
||||||
|
val pairable = obj as Json.Object
|
||||||
|
tournament.players[pairable.getID("id")!!] = Player.fromJson(pairable)
|
||||||
|
}
|
||||||
|
(json["games"] as Json.Array?)?.forEachIndexed { i, arr ->
|
||||||
|
val round = i + 1
|
||||||
|
val tournamentGames = tournament.games(round)
|
||||||
|
val games = arr as Json.Array
|
||||||
|
games.forEach { obj ->
|
||||||
|
val game = obj as Json.Object
|
||||||
|
tournamentGames[game.getID("id")!!] = Game.fromJson(game)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tournament
|
return tournament
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,8 @@ object Upload {
|
|||||||
// Check that we have a file upload request
|
// Check that we have a file upload request
|
||||||
val isMultipart: Boolean = ServletFileUpload.isMultipartContent(request)
|
val isMultipart: Boolean = ServletFileUpload.isMultipartContent(request)
|
||||||
if (!isMultipart) {
|
if (!isMultipart) {
|
||||||
throw IOException("multipart content expected")
|
logger.warn("multipart content expected")
|
||||||
|
return listOf()
|
||||||
}
|
}
|
||||||
val files = mutableListOf<Pair<String, ByteArray>>()
|
val files = mutableListOf<Pair<String, ByteArray>>()
|
||||||
|
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
package org.jeudego.pairgoth.view
|
package org.jeudego.pairgoth.view
|
||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.walk
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic utilities
|
* Generic utilities
|
||||||
@@ -71,4 +77,21 @@ class PairgothTool {
|
|||||||
games.filter {
|
games.filter {
|
||||||
it.getInt("b")!! != 0 && it.getInt("w")!! != 0
|
it.getInt("b")!! != 0 && it.getInt("w")!! != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
fun getExampleTournaments(): List<String> {
|
||||||
|
val classLoader: ClassLoader = PairgothTool::class.java.classLoader
|
||||||
|
val examplesPath = Paths.get(classLoader.getResource(EXAMPLES_DIRECTORY).toURI())
|
||||||
|
return examplesPath.walk().filter(Files::isRegularFile).map { it.fileName.toString().removeSuffix(".tour") }.sorted().toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExampleTournament(name: String): Json.Object {
|
||||||
|
val classLoader: ClassLoader = PairgothTool::class.java.classLoader
|
||||||
|
return Json.parse(classLoader.getResource("$EXAMPLES_DIRECTORY/$name.tour").readText())?.asObject()
|
||||||
|
?: throw Error("wrong resource file")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXAMPLES_DIRECTORY = "examples"
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,7 +1,9 @@
|
|||||||
package org.jeudego.pairgoth.web
|
package org.jeudego.pairgoth.web
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.util.Upload
|
import org.jeudego.pairgoth.util.Upload
|
||||||
import org.jeudego.pairgoth.view.ApiTool
|
import org.jeudego.pairgoth.view.ApiTool
|
||||||
|
import org.jeudego.pairgoth.view.PairgothTool.Companion.EXAMPLES_DIRECTORY
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import javax.servlet.http.HttpServlet
|
import javax.servlet.http.HttpServlet
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
@@ -11,11 +13,28 @@ class ImportServlet: HttpServlet() {
|
|||||||
|
|
||||||
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
|
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
|
||||||
val api = ApiTool().apply { setRequest(req) }
|
val api = ApiTool().apply { setRequest(req) }
|
||||||
|
val example = req.getParameter("example") as String?
|
||||||
|
if (example != null) uploadExample(req, resp)
|
||||||
|
else {
|
||||||
val uploads = Upload.handleFileUpload(req)
|
val uploads = Upload.handleFileUpload(req)
|
||||||
if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
|
if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
else {
|
else {
|
||||||
val xml = uploads.first().second.toString(StandardCharsets.UTF_8)
|
val name = uploads.first().first
|
||||||
val apiResp = api.post("tour", xml)
|
val bytes = uploads.first().second
|
||||||
|
var apiResp: Json? = null
|
||||||
|
if (name.endsWith(".tour")) {
|
||||||
|
val json = Json.parse(bytes.toString(StandardCharsets.UTF_8))
|
||||||
|
if (json == null || !json.isObject) {
|
||||||
|
resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
|
} else {
|
||||||
|
apiResp = api.post("tour", json.asObject())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // xml ?
|
||||||
|
val xml = bytes.toString(StandardCharsets.UTF_8)
|
||||||
|
apiResp = api.post("tour", xml)
|
||||||
|
}
|
||||||
|
if (apiResp != null) {
|
||||||
if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
|
if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
|
||||||
resp.contentType = "application/json; charset=UTF-8"
|
resp.contentType = "application/json; charset=UTF-8"
|
||||||
resp.writer.println(apiResp.toString())
|
resp.writer.println(apiResp.toString())
|
||||||
@@ -25,3 +44,25 @@ class ImportServlet: HttpServlet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uploadExample(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
val name = request.getParameter("example")
|
||||||
|
val classLoader = ImportServlet::class.java.classLoader
|
||||||
|
val example = classLoader.getResource("$EXAMPLES_DIRECTORY/$name.tour")?.readText()?.let {
|
||||||
|
Json.parse(it)
|
||||||
|
}
|
||||||
|
if (example == null || !example.isObject) {
|
||||||
|
response.sendError(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
|
} else {
|
||||||
|
val api = ApiTool().apply { setRequest(request) }
|
||||||
|
val apiResp = api.post("tour", example)
|
||||||
|
if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
|
||||||
|
response.contentType = "application/json; charset=UTF-8"
|
||||||
|
response.writer.println(apiResp.toString())
|
||||||
|
} else {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1494
view-webapp/src/main/resources/examples/20180907-marseille.tour
Normal file
1494
view-webapp/src/main/resources/examples/20180907-marseille.tour
Normal file
File diff suppressed because it is too large
Load Diff
5790
view-webapp/src/main/resources/examples/20210403-paris.tour
Normal file
5790
view-webapp/src/main/resources/examples/20210403-paris.tour
Normal file
File diff suppressed because it is too large
Load Diff
4844
view-webapp/src/main/resources/examples/20230127-grenoble.tour
Normal file
4844
view-webapp/src/main/resources/examples/20230127-grenoble.tour
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,8 @@ Cancel Annuler
|
|||||||
Change Mettre à jour
|
Change Mettre à jour
|
||||||
Chinese rules Règles chinoises
|
Chinese rules Règles chinoises
|
||||||
Choose format Choix du format
|
Choose format Choix du format
|
||||||
|
Clone Dupliquer
|
||||||
|
Clone example tournament Dupliquer un tournoi d'exemple
|
||||||
Close Fermer
|
Close Fermer
|
||||||
Club Club
|
Club Club
|
||||||
Compile from the sources Compiler depuis les sources
|
Compile from the sources Compiler depuis les sources
|
||||||
@@ -34,6 +36,7 @@ Download the standalone web interface module which suits your need, then follow
|
|||||||
Edit Éditer
|
Edit Éditer
|
||||||
Encoding Encodage
|
Encoding Encodage
|
||||||
Enter the magic word Entrer le mot magique
|
Enter the magic word Entrer le mot magique
|
||||||
|
Example tournament Tournoi
|
||||||
Export Exporter
|
Export Exporter
|
||||||
Export tournament Exporter le tournoi
|
Export tournament Exporter le tournoi
|
||||||
Family name Nom de famille
|
Family name Nom de famille
|
||||||
|
@@ -7,6 +7,10 @@
|
|||||||
<i class="fa fa-upload"></i>
|
<i class="fa fa-upload"></i>
|
||||||
Import tournament
|
Import tournament
|
||||||
</a>
|
</a>
|
||||||
|
<a id="example-tournament" class="ui green icon floating button">
|
||||||
|
<i class="fa fa-copy"></i>
|
||||||
|
Clone example tournament
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="tournaments section">
|
<div class="tournaments section">
|
||||||
#set($files = $api.get('tour'))
|
#set($files = $api.get('tour'))
|
||||||
@@ -52,6 +56,33 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="clone-popup" class="popup">
|
||||||
|
<div class="popup-body">
|
||||||
|
<form id="clone-form" class="ui form">
|
||||||
|
<div class="popup-content">
|
||||||
|
<div class="field">
|
||||||
|
<label>Example tournament</label>
|
||||||
|
<select id="exampleTournamentName">
|
||||||
|
<option value=""></option>
|
||||||
|
#foreach($tour in $utils.exampleTournaments)
|
||||||
|
<option value="$tour">$tour</option>
|
||||||
|
#end
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-footer">
|
||||||
|
<button id="cancel-clone" type="button" class="ui gray right labeled icon floating close button">
|
||||||
|
<i class="times icon"></i>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button id="clone" type="button" class="ui green right labeled icon floating button">
|
||||||
|
<i class="plus icon"></i>
|
||||||
|
Clone
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// #[[
|
// #[[
|
||||||
function doImport() {
|
function doImport() {
|
||||||
@@ -75,6 +106,26 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doClone(name) {
|
||||||
|
fetch(`/api/import?example=${name}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {}
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp.ok) return resp.json();
|
||||||
|
else throw resp;
|
||||||
|
}).then(json => {
|
||||||
|
if (json.success) {
|
||||||
|
console.log(`/tour?id=${json.id}`)
|
||||||
|
document.location.href = `/tour?id=${json.id}`
|
||||||
|
} else {
|
||||||
|
showError(json.error || 'unknown error')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onLoad(()=>{
|
onLoad(()=>{
|
||||||
$('#import-tournament').on('click', e => {
|
$('#import-tournament').on('click', e => {
|
||||||
modal('import-popup');
|
modal('import-popup');
|
||||||
@@ -88,6 +139,15 @@
|
|||||||
} else showError('no file choosen');
|
} else showError('no file choosen');
|
||||||
close_modal();
|
close_modal();
|
||||||
});
|
});
|
||||||
|
$('#example-tournament').on('click', e => {
|
||||||
|
modal('clone-popup');
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$('#clone').on('click', e => {
|
||||||
|
let example = $('#exampleTournamentName')[0].value;
|
||||||
|
doClone(example);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// ]]#
|
// ]]#
|
||||||
</script>
|
</script>
|
||||||
|
Reference in New Issue
Block a user