Compare commits

...

15 Commits

Author SHA1 Message Date
3622978e55 Finally also migrate CSV export function 2025-09-11 12:16:24 +02:00
dfcfe86fdb Add success message input to QRgen 2025-09-10 08:59:42 +02:00
e48a69aa1c Show data until next hour 2025-08-30 13:17:14 +02:00
2323d64547 Maybe actually save the game metadata 2025-08-30 13:13:25 +02:00
7a60b8a631 Use absolute paths because i can't do things right 2025-08-30 13:09:53 +02:00
5a99aa2dcc Fix route endpoints. Still too lazy to use rewrite 2025-08-30 13:07:48 +02:00
9f9fdb5299 Fix titles 2025-08-30 12:49:42 +02:00
c94c7bea45 Fix askname in QRgen 2025-08-30 12:48:45 +02:00
3f72fa19f8 Update QR generator 2025-08-30 12:43:56 +02:00
fad56d5c87 Remove comma 2025-08-30 12:18:26 +02:00
f781aca8c1 Fix test link 2025-08-30 12:15:05 +02:00
84c0eded73 Move resource links to /game because i'm too lazy 2025-08-30 12:06:23 +02:00
8e8524c865 Bind to localhost only 2025-08-30 12:01:18 +02:00
d31ae47cde Move paths to /game 2025-08-30 11:52:29 +02:00
8458a5b66f Fix typo 2025-08-30 11:52:13 +02:00
11 changed files with 93 additions and 111 deletions

View File

@ -4,11 +4,11 @@ from .core import app
# Start the flask server if run from terminal
if __name__ == "__main__":
@app.route('/')
@app.route('/game')
def get_root():
return app.send_static_file('index.html')
@app.route('/<path:path>')
@app.route('/game/<path:path>')
def get_path(path):
fpath = f"{app.static_folder}/{path}"
@ -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')

View File

@ -4,8 +4,8 @@
# _/ __/ / /_/ /___/ /_/ / _/ /__/ /_/ // /_/ // __/
# /_/ ___\__,_//____/ /_/ __/_/ \____//_____/_\___/
# Corona time tracker
# Schnitzeljagd time tracker
VERSION = (1, 1, 0)
VERSION = (0, 1, 0)
__version__ = '.'.join(map(str, VERSION))

View File

@ -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:

View File

@ -7,7 +7,7 @@ with open("LICENSE.md", "r") as f:
license_text = f.read()
st.setup(
name="schnitzeljagt",
name="schnitzeljagd",
version="0.1.0",
author="Oskar @ FaSTTUBe",
author_email="o.winkels@fasttube.de",

View File

@ -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>
&copy; 2020 Oskar / FaSTTUBe
https://git.fasttube.de/FaSTTUBe/schnitzeljagd<br>
&copy; 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>
&copy; 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, {

View File

@ -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>
@ -33,13 +33,13 @@
In the former case: Yay it works! In the latter you should
probably contact an admin or a dev nearby :(<br><br>
Here are a few links for testing:<br>
<a href="/view">View Data</a>,
<a href="/QRgen">Door Sign Generator</a>,
<a href="/?game=schnitzeljagd&checkpoint=42">Test Checkpoint</a>,
<a href="/game/view">View Data</a>,
<a href="/game/QRgen">Checkpoint Generator</a>,
<a href="/game?game=schnitzeljagd&checkpoint=Test42&askname=true">Test Checkpoint</a>
<br><br>
&copy; 2025 made by <a target="_blank" href="mailto:&#111;&#46;&#119;&#105;&#110;&#107;&#101;&#108;&#115;&#64;&#102;&#97;&#115;&#116;&#116;&#117;&#98;&#101;&#46;&#100;&#101;">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>

View File

@ -51,7 +51,7 @@ function sendMainData() {
'name': name
}
post("/checkin", payload)
post("/game/checkin", payload)
}

View File

@ -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>

View File

@ -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();
@ -236,7 +199,7 @@ function loadData() {
headers: headers
}
fetch('/data', fetchopts)
fetch('/game/data', fetchopts)
.then(res => {
if (Math.floor(res.status / 100) == 2)
return res.json()
@ -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)