Compare commits
13 Commits
d31ae47cde
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
3622978e55
|
|||
|
dfcfe86fdb
|
|||
|
e48a69aa1c
|
|||
|
2323d64547
|
|||
|
7a60b8a631
|
|||
|
5a99aa2dcc
|
|||
|
9f9fdb5299
|
|||
|
c94c7bea45
|
|||
|
3f72fa19f8
|
|||
|
fad56d5c87
|
|||
|
f781aca8c1
|
|||
|
84c0eded73
|
|||
|
8e8524c865
|
@ -27,4 +27,4 @@ if __name__ == "__main__":
|
||||
response.headers['Access-Control-Allow-Headers'] = '*'
|
||||
return response
|
||||
|
||||
app.run(host='0.0.0.0')
|
||||
app.run(host='127.0.0.1')
|
||||
|
||||
@ -24,15 +24,7 @@ def shutdown():
|
||||
atexit.register(shutdown)
|
||||
|
||||
|
||||
@app.route('/guidelines')
|
||||
def get_guidelines():
|
||||
dest = config['guideline_url'] or None
|
||||
if dest:
|
||||
return redirect(dest)
|
||||
return "No guideline document was configured.", 404
|
||||
|
||||
|
||||
@app.route('/checkin', methods=['POST'])
|
||||
@app.route('/game/checkin', methods=['POST'])
|
||||
def post_arrival():
|
||||
|
||||
try:
|
||||
@ -44,11 +36,13 @@ def post_arrival():
|
||||
if not ('checkpoint' in data and 'name' in data):
|
||||
return "Error: Key missing. See docs/API.md for reference.", 400
|
||||
|
||||
game = slugify(data['game'])
|
||||
name = slugify(data['name'])
|
||||
checkpoint = slugify(data['checkpoint'])
|
||||
|
||||
now = datetime.utcnow().isoformat() + 'Z'
|
||||
db.insert({
|
||||
'game': game,
|
||||
'name': name,
|
||||
'checkpoint': checkpoint,
|
||||
'arrival': now,
|
||||
@ -57,7 +51,7 @@ def post_arrival():
|
||||
return 'OK', 200
|
||||
|
||||
|
||||
@app.route('/data')
|
||||
@app.route('/game/data')
|
||||
def get_data():
|
||||
|
||||
if not 'Authorization' in request.headers:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>FTracker</title>
|
||||
<title>Schnitzeljagd</title>
|
||||
<meta name="theme-color" content="#c50e1f">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<style>
|
||||
@ -49,7 +49,7 @@
|
||||
}
|
||||
.print * {
|
||||
display: inline-block;
|
||||
margin: 42px auto;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.print .link {
|
||||
font-size: 24px;
|
||||
@ -65,68 +65,85 @@
|
||||
</head>
|
||||
<body>
|
||||
<main id="formView">
|
||||
<h1>FTracker<br>Door Sign Genrator</h1>
|
||||
<form id="roomform">
|
||||
<h1>Schnitzeljagd<br>Sign Generator</h1>
|
||||
<form id="qrform">
|
||||
<label>
|
||||
Room Nr/Name:<br>
|
||||
<input type="text" name="room" id="room" placeholder="123" required>
|
||||
Game Name:<br>
|
||||
<input type="text" name="game" id="game" placeholder="schnitzeljagd" required>
|
||||
</label>
|
||||
<label>
|
||||
Checkpoint Name:<br>
|
||||
<input type="text" name="checkpoint" id="checkpoint" placeholder="123" required>
|
||||
</label>
|
||||
<label>
|
||||
Custom Success Message:<br>
|
||||
<input type="text" name="success_message" id="success_message" placeholder="Super, du hast alle Objekte gefunden!">
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" name="askname" id="askname">
|
||||
Ask for name entry at this checkpoint
|
||||
</label>
|
||||
<br><br>
|
||||
<input type="submit" value="Print">
|
||||
</form>
|
||||
</main>
|
||||
<main id="printA" class="print">
|
||||
<main id="print" class="print">
|
||||
<h1 class="title"></h1><br>
|
||||
<div class="qr"></div><br>
|
||||
<span class="link"></span><br>
|
||||
<span>
|
||||
Made with FTracker<br>
|
||||
https://git.fasttube.de/FaSTTUBe/ftracker<br>
|
||||
© 2020 Oskar / FaSTTUBe
|
||||
https://git.fasttube.de/FaSTTUBe/schnitzeljagd<br>
|
||||
© 2025 Oskar / FaSTTUBe
|
||||
</span>
|
||||
</main>
|
||||
<main id="printD" class="print">
|
||||
<h1 class="title"></h1><br>
|
||||
<div class="qr"></div><br>
|
||||
<span class="link"></span><br>
|
||||
<span>
|
||||
Made with FTracker<br>
|
||||
https://git.fasttube.de/FaSTTUBe/ftracker<br>
|
||||
© 2020 Oskar / FaSTTUBe
|
||||
</span>
|
||||
</main>
|
||||
<script src="/qrcodejs/qrcode.min.js"></script>
|
||||
<script src="/game/qrcodejs/qrcode.min.js"></script>
|
||||
<script>
|
||||
var fv = document.getElementById('formView')
|
||||
var pa = document.getElementById('printA')
|
||||
var pd = document.getElementById('printD')
|
||||
var p = document.getElementById('print')
|
||||
|
||||
var rform = document.getElementById('roomform')
|
||||
var rform = document.getElementById('qrform')
|
||||
rform.onsubmit = function(e) {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
var room = e.srcElement[0].value
|
||||
console.log(e)
|
||||
|
||||
writePage(pa, room, 'arrival')
|
||||
writePage(pd, room, 'departure')
|
||||
var details = {}
|
||||
details.game = e.srcElement[0].value
|
||||
details.checkpoint = e.srcElement[1].value
|
||||
details.success_message = e.srcElement[2].value
|
||||
details.askname = document.getElementById('askname').checked
|
||||
|
||||
printPage(pa, 'ftracker-arrival-'+room)
|
||||
printPage(pd, 'ftracker-departure-'+room)
|
||||
writePage(p, details)
|
||||
|
||||
printPage(p, details.game+'-'+details.checkpoint)
|
||||
|
||||
}
|
||||
|
||||
function writePage(el, room, type) {
|
||||
function writePage(el, details) {
|
||||
|
||||
var base = location.href.split('/').slice(0,3).join('/')
|
||||
var base = location.href.split('/').slice(0,4).join('/')
|
||||
|
||||
var url = base + '/?' + type + '=' + room
|
||||
var url = base
|
||||
+ '?game=' + details.game
|
||||
+ '&checkpoint=' + details.checkpoint
|
||||
|
||||
console.log(details)
|
||||
|
||||
if (details.askname)
|
||||
url += '&askname=true'
|
||||
|
||||
if (details.success_message)
|
||||
url += '&success_message=' + encodeURI(details.success_message)
|
||||
|
||||
var title = el.querySelector('.title')
|
||||
var qr = el.querySelector('.qr')
|
||||
var link = el.querySelector('.link')
|
||||
|
||||
title.innerHTML =
|
||||
'Scan here to log ' + type + '<br> in room ' + room
|
||||
'Hier scannen um am checkpoint<br>' + details.checkpoint + '<br> einzuchecken'
|
||||
link.innerHTML = url
|
||||
qr.innerHTML = ''
|
||||
new QRCode(qr, {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Schnitzeljagt</title>
|
||||
<title>Schnitzeljagd</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#c50e1f">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link href="style.css" rel="stylesheet" type="text/css">
|
||||
<link href="/game/style.css" rel="stylesheet" type="text/css">
|
||||
<script>
|
||||
// 1st script, prepares values needed for writing document
|
||||
function getParams() {
|
||||
@ -24,7 +24,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1><script>
|
||||
document.write(qp.checkpoint ? ("Checkpoint<br>" + qp.checkpoint) : 'Schnitzeljagt<br>V 0.1')
|
||||
document.write(qp.checkpoint ? ("Checkpoint<br>" + qp.checkpoint) : 'Schnitzeljagd<br>V 0.1')
|
||||
</script></h1>
|
||||
<div id="startpage">
|
||||
This is a web app to track games at the 20J FaSTTUBe festival.<br><br>
|
||||
@ -34,12 +34,12 @@
|
||||
probably contact an admin or a dev nearby :(<br><br>
|
||||
Here are a few links for testing:<br>
|
||||
<a href="/game/view">View Data</a>,
|
||||
<a href="/game/QRgen">Door Sign Generator</a>,
|
||||
<a href="/game/?game=schnitzeljagd&checkpoint=42">Test Checkpoint</a>,
|
||||
<a href="/game/QRgen">Checkpoint Generator</a>,
|
||||
<a href="/game?game=schnitzeljagd&checkpoint=Test42&askname=true">Test Checkpoint</a>
|
||||
<br><br>
|
||||
© 2025 made by <a target="_blank" href="mailto:o.winkels@fasttube.de">Oskar</a>
|
||||
for <a target="_blank" href="//fasttube.de">FaSTTUBe</a>.<br>
|
||||
For source code & licensing see <a href="//git.fasttube.de/FaSTTUBe/schnitzeljagt">git repo</a>
|
||||
For source code & licensing see <a href="//git.fasttube.de/FaSTTUBe/schnitzeljagd">git repo</a>
|
||||
</div>
|
||||
<form id="mainform" action="javascript:void(0);" style="display: none">
|
||||
<label>
|
||||
@ -48,6 +48,6 @@
|
||||
</label>
|
||||
<input type="submit" value="Einchecken">
|
||||
</form>
|
||||
<script src="main.js"></script>
|
||||
<script src="/game/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>FTracker Data</title>
|
||||
<title>Schnitzeljagd Data</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#c50e1f">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link href="view.css" rel="stylesheet" type="text/css">
|
||||
<link href="/game/view.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -31,6 +31,6 @@
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="view.js"></script>
|
||||
<script src="/game/view.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
71
web/view.js
71
web/view.js
@ -18,71 +18,34 @@ function exportCSV() {
|
||||
|
||||
var startDate = new Date(startInput.value)
|
||||
var endDate = new Date(endInput.value)
|
||||
var roomRE = new RegExp(gameInput.value || '.*')
|
||||
var gameRE = new RegExp(gameInput.value || '.*')
|
||||
|
||||
csv = '"ftracker-export",'
|
||||
days = []
|
||||
|
||||
var tc = new Date(startDate.getTime())
|
||||
tc.setHours(1,0,0,0)
|
||||
while (tc < endDate) {
|
||||
var isodate = tc.toISOString().split('T')[0]
|
||||
csv += ('"' + isodate + '",')
|
||||
days.push(isodate)
|
||||
tc.setDate(tc.getDate() + 1);
|
||||
}
|
||||
|
||||
csv = csv.replace(/,$/, '')
|
||||
|
||||
csv += '\n'
|
||||
csv = 'timestamp,name,game,checkpoint\n'
|
||||
|
||||
for (var [name, list] of Object.entries(data)) {
|
||||
|
||||
csv += '"' + name + '"'
|
||||
|
||||
for (day of days) {
|
||||
for (entry of list) {
|
||||
|
||||
csv += ',"'
|
||||
if (entry.game.match(gameRE) == null)
|
||||
continue
|
||||
|
||||
daytexts = []
|
||||
var arrD = new Date(entry.arrival)
|
||||
|
||||
for (entry of list) {
|
||||
if (arrD < startDate || arrD > endDate)
|
||||
continue
|
||||
|
||||
if (entry.room.match(roomRE) == null)
|
||||
continue
|
||||
var arrTS = localISOTimeSeconds(arrD)
|
||||
|
||||
var arrD = new Date(entry.arrival)
|
||||
var depD = entry.departure ? new Date(entry.departure) : new Date()
|
||||
|
||||
if (depD < startDate || arrD > endDate)
|
||||
continue
|
||||
|
||||
var [arrDay, arrT] = localISOTimeMinutes(arrD).split('T')
|
||||
var [depDay, depT] = localISOTimeMinutes(depD).split('T')
|
||||
|
||||
if ((arrDay == day) && (depDay == day)) {
|
||||
daytexts.push(arrT + '-' + depT + ' (' + entry.room + ')')
|
||||
} else if (arrDay == day) {
|
||||
daytexts.push(arrT + '-... (' + entry.room + ')')
|
||||
} else if (depDay == day) {
|
||||
daytexts.push('...-' + depT + ' (' + entry.room + ')')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
csv += daytexts.join('\n')
|
||||
|
||||
csv += '"'
|
||||
csv += `${arrTS},${name},${entry.game},${entry.checkpoint}\n`
|
||||
|
||||
}
|
||||
|
||||
csv += '\n'
|
||||
|
||||
}
|
||||
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(csv));
|
||||
element.setAttribute('download', 'ftracker-export.csv');
|
||||
element.setAttribute('download', 'schnitzeljagd-export.csv');
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
@ -251,16 +214,24 @@ function loadData() {
|
||||
|
||||
}
|
||||
|
||||
function localISOTimeMinutes(date) {
|
||||
function localISOTimeSeconds(date) {
|
||||
|
||||
var tzoffset = date.getTimezoneOffset() * 60000; //offset in milliseconds
|
||||
var localISOTime = (new Date(date - tzoffset)).toISOString().slice(0, -1);
|
||||
|
||||
return localISOTime.split(':').slice(0,2).join(':')
|
||||
return localISOTime
|
||||
|
||||
}
|
||||
|
||||
function localISOTimeMinutes(date) {
|
||||
|
||||
return localISOTimeSeconds(date).split(':').slice(0,2).join(':')
|
||||
|
||||
}
|
||||
|
||||
var now = new Date()
|
||||
now.setHours(now.getHours()+1,0,0,0)
|
||||
now.setMinutes(0,0,0)
|
||||
var startDate = new Date()
|
||||
startDate.setDate(now.getDate())
|
||||
startDate.setHours(8,0,0,0)
|
||||
|
||||
Reference in New Issue
Block a user