summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rwxr-xr-xhalfnarp2.py107
-rwxr-xr-xscripts/gen_css_tables.py2
-rw-r--r--static/faq.html8
-rw-r--r--static/fullnarp.html28
-rw-r--r--static/fullnarp.js4
-rw-r--r--static/halfnarp.js43
-rw-r--r--static/index.html37
-rw-r--r--wsgi.py6
9 files changed, 137 insertions, 116 deletions
diff --git a/Makefile b/Makefile
index 1f72d53..34a3d59 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,28 @@
1all: install 1all: install
2 2
3import: venv 3install: venv/bin/flask
4
5import: install
4 venv/bin/python3 ./halfnarp2.py -i 6 venv/bin/python3 ./halfnarp2.py -i
5 7
6do-export: venv 8do-export: install
7 venv/bin/python3 ./halfnarp2.py -e 9 venv/bin/python3 ./halfnarp2.py -e
8 10
9run: venv 11run: install
10 PYTHONIOENCODING=utf-8 venv/bin/python3 ./halfnarp2.py 12 PYTHONIOENCODING=utf-8 venv/bin/python3 ./halfnarp2.py
11 13
12run-fullnarp: venv 14run-halfnarp-waitress: install venv/bin/waitress-serve
15 PYTHONIOENCODING=utf-8 venv/bin/python3 venv/bin/waitress-serve --host 127.0.0.1 --port 5023 wsgi:app
16
17run-fullnarp: install
13 PYTHONIOENCODING=utf-8 venv/bin/python3 ./fullnarp.py 18 PYTHONIOENCODING=utf-8 venv/bin/python3 ./fullnarp.py
14 19
20venv/bin/waitress-serve: install
21 venv/bin/pip install waitress
22
15venv: 23venv:
16 python3 -m venv ./venv 24 python3 -m venv ./venv
17 25
18install: venv 26venv/bin/flask: venv
19 venv/bin/pip install --upgrade pip 27 venv/bin/pip install --upgrade pip
20 venv/bin/pip install -r requirements.txt 28 venv/bin/pip install -r requirements.txt
diff --git a/halfnarp2.py b/halfnarp2.py
index 0cc3906..fcc8788 100755
--- a/halfnarp2.py
+++ b/halfnarp2.py
@@ -1,6 +1,6 @@
1#!venv/bin/python 1#!venv/bin/python
2 2
3from flask import Flask, render_template, jsonify, request, abort, send_file, url_for 3from flask import Flask, Blueprint, render_template, jsonify, request, abort, send_file, url_for
4from flask_sqlalchemy import SQLAlchemy 4from flask_sqlalchemy import SQLAlchemy
5from flask_cors import CORS 5from flask_cors import CORS
6from lxml import etree 6from lxml import etree
@@ -13,10 +13,9 @@ from datetime import datetime, time, timedelta
13from html_sanitizer import Sanitizer 13from html_sanitizer import Sanitizer
14from hashlib import sha256 14from hashlib import sha256
15 15
16app = Flask(__name__) 16bp = Blueprint("main", __name__)
17db = SQLAlchemy() 17db = SQLAlchemy()
18 18
19
20class TalkPreference(db.Model): 19class TalkPreference(db.Model):
21 """A preference of halfnarp frontend. An array of strings""" 20 """A preference of halfnarp frontend. An array of strings"""
22 21
@@ -26,17 +25,17 @@ class TalkPreference(db.Model):
26 25
27 26
28""" 27"""
29@app.route("/") 28@bp.route("/")
30def root(): 29def root():
31 return render_template("index.html") 30 return render_template("index.html")
32""" 31"""
33 32
34@app.route("/-/talkpreferences", methods=["GET"]) 33@bp.route("/-/talkpreferences", methods=["GET"])
35def sessions(): 34def sessions():
36 return send_file("var/talks_local", mimetype="application/json") 35 return send_file("var/talks_local", mimetype="application/json")
37 36
38 37
39@app.route("/-/talkpreferences/<uid>", methods=["GET"]) 38@bp.route("/-/talkpreferences/<uid>", methods=["GET"])
40def get_own_preferences(uid): 39def get_own_preferences(uid):
41 pref = db.session.get(TalkPreference, uid) 40 pref = db.session.get(TalkPreference, uid)
42 if pref == None: 41 if pref == None:
@@ -46,7 +45,7 @@ def get_own_preferences(uid):
46 { 45 {
47 "hashed_uid": pref.public_uid, 46 "hashed_uid": pref.public_uid,
48 "public_url": url_for( 47 "public_url": url_for(
49 "get_preferences", 48 "main.get_preferences",
50 public_uid=public_uid, 49 public_uid=public_uid,
51 _external=True, 50 _external=True,
52 _scheme="https", 51 _scheme="https",
@@ -57,7 +56,7 @@ def get_own_preferences(uid):
57 ) 56 )
58 57
59 58
60@app.route("/-/talkpreferences/", methods=["POST"]) 59@bp.route("/-/talkpreferences/", methods=["POST"])
61def store_preferences(): 60def store_preferences():
62 try: 61 try:
63 content = request.json 62 content = request.json
@@ -80,19 +79,19 @@ def store_preferences():
80 "uid": uid, 79 "uid": uid,
81 "hashed_uid": str(public_uid), 80 "hashed_uid": str(public_uid),
82 "public_url": url_for( 81 "public_url": url_for(
83 "get_preferences", 82 "main.get_preferences",
84 public_uid=public_uid, 83 public_uid=public_uid,
85 _external=True, 84 _external=True,
86 _scheme="https", 85 _scheme="https",
87 ), 86 ),
88 "update_url": url_for( 87 "update_url": url_for(
89 "update_preferences", uid=uid, _external=True, _scheme="https" 88 "main.update_preferences", uid=uid, _external=True, _scheme="https"
90 ), 89 ),
91 } 90 }
92 ) 91 )
93 92
94 93
95@app.route("/-/talkpreferences/<uid>", methods=["POST", "PUT"]) 94@bp.route("/-/talkpreferences/<uid>", methods=["POST", "PUT"])
96def update_preferences(uid): 95def update_preferences(uid):
97 pref = db.session.get(TalkPreference, uid) 96 pref = db.session.get(TalkPreference, uid)
98 if pref == None: 97 if pref == None:
@@ -105,7 +104,7 @@ def update_preferences(uid):
105 return jsonify({"uid": pref.uid, "hashed_uid": pref.public_uid}) 104 return jsonify({"uid": pref.uid, "hashed_uid": pref.public_uid})
106 105
107 106
108@app.route("/-/talkpreferences/public/<public_uid>", methods=["GET"]) 107@bp.route("/-/talkpreferences/public/<public_uid>", methods=["GET"])
109def get_preferences(public_uid): 108def get_preferences(public_uid):
110 pref = ( 109 pref = (
111 db.session.query(TalkPreference) 110 db.session.query(TalkPreference)
@@ -121,14 +120,14 @@ def get_preferences(public_uid):
121def filter_keys_halfnarp(session): 120def filter_keys_halfnarp(session):
122 abstract_html = markdown.markdown(session["abstract"], enable_attributes=False) 121 abstract_html = markdown.markdown(session["abstract"], enable_attributes=False)
123 abstract_clean_html = Sanitizer().sanitize(abstract_html) 122 abstract_clean_html = Sanitizer().sanitize(abstract_html)
124 slot = session["slot"] 123 slot = next(iter(session["slots"]), {})
125 124
126 return { 125 return {
127 "title": session.get("title", "!!! NO TITLE !!!"), 126 "title": session.get("title", "!!! NO TITLE !!!"),
128 "duration": 60 * session.get("duration", 40), 127 "duration": 60 * session.get("duration", 40),
129 "event_id": session["code"], 128 "event_id": session["code"],
130 "language": session.get("content_locale", "de"), 129 "language": session.get("content_locale", "de"),
131 "track_id": session["track_id"], 130 "track_id": session["track"],
132 "speaker_names": ", ".join( 131 "speaker_names": ", ".join(
133 [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})] 132 [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})]
134 ), 133 ),
@@ -141,7 +140,7 @@ def filter_keys_halfnarp(session):
141def filter_keys_fullnarp(session, speakers): 140def filter_keys_fullnarp(session, speakers):
142 abstract_html = markdown.markdown(session["abstract"], enable_attributes=False) 141 abstract_html = markdown.markdown(session["abstract"], enable_attributes=False)
143 abstract_clean_html = Sanitizer().sanitize(abstract_html) 142 abstract_clean_html = Sanitizer().sanitize(abstract_html)
144 slot = session["slot"] 143 slot = next(iter(session["slots"]), {})
145 144
146 speaker_info = [] 145 speaker_info = []
147 for speaker in session.get("speakers", {}): 146 for speaker in session.get("speakers", {}):
@@ -181,7 +180,7 @@ def filter_keys_fullnarp(session, speakers):
181 "duration": 60 * session.get("duration", 40), 180 "duration": 60 * session.get("duration", 40),
182 "event_id": session["code"], 181 "event_id": session["code"],
183 "language": session.get("content_locale", "de"), 182 "language": session.get("content_locale", "de"),
184 "track_id": session["track_id"], 183 "track_id": session["track"],
185 "speakers": speaker_info, 184 "speakers": speaker_info,
186 "speaker_names": ", ".join( 185 "speaker_names": ", ".join(
187 [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})] 186 [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})]
@@ -194,27 +193,28 @@ def filter_keys_fullnarp(session, speakers):
194 193
195def fetch_talks(config): 194def fetch_talks(config):
196 sess = requests.Session() 195 sess = requests.Session()
197 196 headers = {'Accept': 'application/json',
198 response = sess.get( 197 'Authorization': "Token " + config['pretalx-token'],
199 config["pretalx-api-url"] + "/submissions/?format=json&limit=20000", 198 'Pretalx-Version' : 'v1'}
200 stream=True, 199
201 headers={"Authorization": "Token " + config["pretalx-token"]}, 200 speakers_result = []
202 ) 201 url = config['pretalx-api-url'] + '/speakers/?format=json&limit=20000'
203 # with open('dump.txt', mode='wb') as localfile: 202 while url:
204 # localfile.write(response.content) 203 response = sess.get(url, stream=True, headers=headers).json()
205 talks_json = json.loads(response.text) 204 speakers_result.extend(response['results'])
206 205 url = response['next']
207 response = sess.get( 206 speakers = { speaker['code']: speaker for speaker in speakers_result }
208 config["pretalx-api-url"] + "/speakers/?format=json&limit=20000", 207
209 stream=True, 208 session_results = []
210 headers={"Authorization": "Token " + config["pretalx-token"]}, 209 url = config['pretalx-api-url'] + '/submissions/?expand=speakers,slot,speakers.availabilities&format=json&limit=20000'
211 ) 210 while url:
212 speakers_json = json.loads(response.text) 211 response = sess.get(url, stream=True, headers=headers).json()
213 speakers = dict((speaker["code"], speaker) for speaker in speakers_json["results"]) 212 session_results.extend(response['results'])
213 url = response['next']
214 214
215 sessions = [ 215 sessions = [
216 filter_keys_halfnarp(submission) 216 filter_keys_halfnarp(submission)
217 for submission in talks_json["results"] 217 for submission in session_results
218 if submission["state"] == "confirmed" 218 if submission["state"] == "confirmed"
219 and not "non-public" in submission.get("tags", {}) 219 and not "non-public" in submission.get("tags", {})
220 ] 220 ]
@@ -223,7 +223,7 @@ def fetch_talks(config):
223 223
224 sessions = [ 224 sessions = [
225 filter_keys_fullnarp(submission, speakers) 225 filter_keys_fullnarp(submission, speakers)
226 for submission in talks_json["results"] 226 for submission in session_results
227 if submission["state"] == "confirmed" or submission["state"] == "accepted" 227 if submission["state"] == "confirmed" or submission["state"] == "accepted"
228 ] 228 ]
229 with open("var/talks_local_fullnarp", mode="w", encoding="utf8") as sessionsfile: 229 with open("var/talks_local_fullnarp", mode="w", encoding="utf8") as sessionsfile:
@@ -237,7 +237,7 @@ def export_prefs(config):
237 print("[]]") 237 print("[]]")
238 238
239 239
240if __name__ == "__main__": 240def parse_args():
241 parser = ArgumentParser(description="halfnarp2") 241 parser = ArgumentParser(description="halfnarp2")
242 parser.add_argument( 242 parser.add_argument(
243 "-i", 243 "-i",
@@ -256,20 +256,20 @@ if __name__ == "__main__":
256 parser.add_argument( 256 parser.add_argument(
257 "-c", "--config", help="Config file location", default="./config.json" 257 "-c", "--config", help="Config file location", default="./config.json"
258 ) 258 )
259 args = parser.parse_args() 259 return parser.parse_args()
260 260
261 with open(args.config, mode="r", encoding="utf-8") as json_file:
262 config = json.load(json_file)
263 config["pretalx-api-url"] = (
264 config["pretalx-url"] + "api/events/" + config["pretalx-conference"]
265 )
266 261
267 app.config["SQLALCHEMY_DATABASE_URI"] = config.get( 262def create_app(config_file="./config.json"):
263 app = Flask(__name__)
264 with open(config_file, mode="r", encoding="utf-8") as config_file:
265 app.config["halfnarp"] = json.load(config_file)
266
267 app.config["SQLALCHEMY_DATABASE_URI"] = app.config["halfnarp"].get(
268 "database-uri", "sqlite:///test.db" 268 "database-uri", "sqlite:///test.db"
269 ) 269 )
270 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 270 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
271 app.config["SERVER_NAME"] = config.get("server-name", "localhost") 271 app.config["SERVER_NAME"] = app.config["halfnarp"].get("server-name", "localhost")
272 app.config["SECRET_KEY"] = config.get("server-secret", "<YOUR SERVER SECRET HERE>") 272 app.config["SECRET_KEY"] = app.config["halfnarp"].get("server-secret", "<YOUR SERVER SECRET HERE>")
273 273
274 if app.config["SECRET_KEY"] == "<YOUR SERVER SECRET HERE>": 274 if app.config["SECRET_KEY"] == "<YOUR SERVER SECRET HERE>":
275 print("You must set the server-secret in your config.json") 275 print("You must set the server-secret in your config.json")
@@ -280,15 +280,24 @@ if __name__ == "__main__":
280 CORS() 280 CORS()
281 281
282 db.init_app(app) 282 db.init_app(app)
283 app.register_blueprint(bp)
284 return app
283 285
286if __name__ == "__main__":
287 args = parse_args()
288 app = create_app(args.config)
289
290 app.config["halfnarp"]["pretalx-api-url"] = (
291 app.config["halfnarp"]["pretalx-url"] + "api/events/" + app.config["halfnarp"]["pretalx-conference"]
292 )
284 with app.app_context(): 293 with app.app_context():
285 db.create_all() 294 db.create_all()
286 if args.pretalx_import: 295 if args.pretalx_import:
287 fetch_talks(config) 296 fetch_talks(app.config["halfnarp"])
288 elif args.fullnarp_export: 297 elif args.fullnarp_export:
289 export_prefs(config) 298 export_prefs(app.config["halfnarp"])
290 else: 299 else:
291 app.run( 300 app.run(
292 host=config.get("host", "127.0.0.1"), 301 host=app.config["halfnarp"].get("host", "127.0.0.1"),
293 port=int(config.get("port", "8080")), 302 port=int(app.config["halfnarp"].get("port", "8080")),
294 ) 303 )
diff --git a/scripts/gen_css_tables.py b/scripts/gen_css_tables.py
index 4bc5ea8..0eb88f4 100755
--- a/scripts/gen_css_tables.py
+++ b/scripts/gen_css_tables.py
@@ -8,7 +8,7 @@
8start_y = 400 8start_y = 400
9starttime = 10 9starttime = 10
10endtime = 28 10endtime = 28
11rooms = 3 11rooms = 4
12days = 4 12days = 4
13columns = days * rooms # how many columns does 13columns = days * rooms # how many columns does
14event_gap = { 'large': 5, 'medium': 3, 'small': 2 } # how much is an event shorter than what the grid would allow in px 14event_gap = { 'large': 5, 'medium': 3, 'small': 2 } # how much is an event shorter than what the grid would allow in px
diff --git a/static/faq.html b/static/faq.html
index aefd03d..e5f53cd 100644
--- a/static/faq.html
+++ b/static/faq.html
@@ -3,11 +3,11 @@
3<head> 3<head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title>halfnarp FAQ</title> 5 <title>halfnarp FAQ</title>
6 <link rel="stylesheet" href="style.css"> 6 <link rel="stylesheet" href="style_39c3.css">
7</head> 7</head>
8<body> 8<body>
9 9
10<div class="headline">The 38C3 halfnarp FAQ</div> 10<div class="headline">The 39C3 halfnarp FAQ</div>
11 11
12<dl> 12<dl>
13<dt>Q: What is halfnarp?</dt> 13<dt>Q: What is halfnarp?</dt>
@@ -18,7 +18,7 @@
18</dd> 18</dd>
19<dt>Q: How does it work?</dt> 19<dt>Q: How does it work?</dt>
20<dd> 20<dd>
21 <p>A: 38C3 Fahrplan is curated by six teams each responsible for one track. By default, lectures are sorted by these tracks.</p> 21 <p>A: 39C3 Fahrplan is curated by six teams each responsible for one track. By default, lectures are sorted by these tracks.</p>
22 <ul> 22 <ul>
23 <li>On a desktop browser, hovering over an event’s description reveals the full abstract. Clicking on an event adds or removes events to/from your favorites list – they turn green.</li> 23 <li>On a desktop browser, hovering over an event’s description reveals the full abstract. Clicking on an event adds or removes events to/from your favorites list – they turn green.</li>
24 <li>On mobile browsers, tapping an event once selects it and reveals the whole content. Tapping a selected event adds or removes events to/from your favorites list – they turn green.</li> 24 <li>On mobile browsers, tapping an event once selects it and reveals the whole content. Tapping a selected event adds or removes events to/from your favorites list – they turn green.</li>
@@ -38,7 +38,7 @@
38</dd> 38</dd>
39<dt>Q: How can I help?</dt> 39<dt>Q: How can I help?</dt>
40<dd> 40<dd>
41 <p>A: Submitting your preferences to the halfnarp servers helps a lot. You can find and create pull requests for <a href="https://github.com/tomster/halfnarp">halfnarp on its github home</a>.</p> 41 <p>A: Submitting your preferences to the halfnarp servers helps a lot. You can the halfnarp living <a href="https://erdgeist.org/gitweb/halfnarp2">halfnarp2 on its git home</a>.</p>
42</dd> 42</dd>
43 43
44</html> 44</html>
diff --git a/static/fullnarp.html b/static/fullnarp.html
index a00f89e..3b86d6c 100644
--- a/static/fullnarp.html
+++ b/static/fullnarp.html
@@ -3,8 +3,8 @@
3<head> 3<head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title>FULLnarp web scheduling helper app</title> 5 <title>FULLnarp web scheduling helper app</title>
6 <link rel="stylesheet" href="style_38c3.css"> 6 <link rel="stylesheet" href="style_39c3.css">
7 <link rel="stylesheet" href="style_38c3_tables.css"> 7 <link rel="stylesheet" href="style_39c3_tables.css">
8 <script src="fullnarp.js"></script> 8 <script src="fullnarp.js"></script>
9 <script> 9 <script>
10 document.addEventListener('DOMContentLoaded', () => { do_the_fullnarp(); }); 10 document.addEventListener('DOMContentLoaded', () => { do_the_fullnarp(); });
@@ -20,7 +20,7 @@
20</div> 20</div>
21<div class="trashbin">🗑️</div> 21<div class="trashbin">🗑️</div>
22<div class="version">version</div> 22<div class="version">version</div>
23<div class="headline">The 38C3 fullnarp</div> 23<div class="headline">The 39C3 fullnarp</div>
24<div class="header"> 24<div class="header">
25<div class="leftbox"> 25<div class="leftbox">
26<input id="filter" type="text" placeholder="Filter events"/> 26<input id="filter" type="text" placeholder="Filter events"/>
@@ -46,10 +46,10 @@
46</div> 46</div>
47</div> 47</div>
48</div> 48</div>
49<div class="day_1 room-label room1">Saal 1</div><div class="day_1 room-label room2">Saal 2</div><div class="day_1 room-label room3">Saal 3</div></div> 49<div class="day_1 room-label room1">Saal 1</div><div class="day_1 room-label room2">Saal 2</div><div class="day_1 room-label room3">Saal 3</div><div class="day_1 room-label room4">Saal 4</div></div>
50<div class="day_2 room-label room1">Saal 1</div><div class="day_2 room-label room2">Saal 2</div><div class="day_2 room-label room3">Saal 3</div></div> 50<div class="day_2 room-label room1">Saal 1</div><div class="day_2 room-label room2">Saal 2</div><div class="day_2 room-label room3">Saal 3</div><div class="day_2 room-label room4">Saal 4</div></div>
51<div class="day_3 room-label room1">Saal 1</div><div class="day_3 room-label room2">Saal 2</div><div class="day_3 room-label room3">Saal 3</div></div> 51<div class="day_3 room-label room1">Saal 1</div><div class="day_3 room-label room2">Saal 2</div><div class="day_3 room-label room3">Saal 3</div><div class="day_3 room-label room4">Saal 4</div></div>
52<div class="day_4 room-label room1">Saal 1</div><div class="day_4 room-label room2">Saal 2</div><div class="day_4 room-label room3">Saal 3</div></div> 52<div class="day_4 room-label room1">Saal 1</div><div class="day_4 room-label room2">Saal 2</div><div class="day_4 room-label room3">Saal 3</div><div class="day_4 room-label room4">Saal 4</div></div>
53 53
54<div class=" time_1815 duration_3600 guide pause ">P A U S E</div> 54<div class=" time_1815 duration_3600 guide pause ">P A U S E</div>
55<div class=" time_1445 wholeblock room1 guide day_1"></div> 55<div class=" time_1445 wholeblock room1 guide day_1"></div>
@@ -65,13 +65,13 @@
65<div class="wholeday room1 day_1 guide uneven"></div> 65<div class="wholeday room1 day_1 guide uneven"></div>
66<div class="wholeday room1 day_3 guide uneven"></div> 66<div class="wholeday room1 day_3 guide uneven"></div>
67 67
68<div class="track" id="6"><h2>Security</h2></div> 68<div class="track" id="31"><h2>Security</h2></div>
69<div class="track" id="4"><h2>Hardware &amp; Making</h2></div> 69<div class="track" id="32"><h2>Hardware &amp; Making</h2></div>
70<div class="track" id="7"><h2>Ethics, Society &amp; Politics</h2></div> 70<div class="track" id="28"><h2>Ethics, Society &amp; Politics</h2></div>
71<div class="track" id="2"><h2>CCC</h2></div> 71<div class="track" id="30"><h2>CCC</h2></div>
72<div class="track" id="3"><h2>Entertainment</h2></div> 72<div class="track" id="27"><h2>Entertainment</h2></div>
73<div class="track" id="5"><h2>Science</h2></div> 73<div class="track" id="29"><h2>Science</h2></div>
74<div class="track" id="1"><h2>Art &amp; Beauty</div> 74<div class="track" id="33"><h2>Art &amp; Beauty</div>
75<div class="track" id="999"><h2>Other</div> 75<div class="track" id="999"><h2>Other</div>
76 76
77</body> 77</body>
diff --git a/static/fullnarp.js b/static/fullnarp.js
index c4e24f4..34a59c4 100644
--- a/static/fullnarp.js
+++ b/static/fullnarp.js
@@ -1,5 +1,5 @@
1let ws; // WebSocket instance 1let ws; // WebSocket instance
2let allrooms = ['1','2','3'] 2let allrooms = ['1','2','3','4']
3let allminutes = ['00','05','10','15','20','25','30','35','40','45','50','55'] 3let allminutes = ['00','05','10','15','20','25','30','35','40','45','50','55']
4let allhours = ['10','11','12','13','14','15','16','17','18','19','20','21','22','23','00','01','02']; 4let allhours = ['10','11','12','13','14','15','16','17','18','19','20','21','22','23','00','01','02'];
5let alldays = ['1','2','3','4']; 5let alldays = ['1','2','3','4'];
@@ -395,7 +395,7 @@ function signalFullnarpConnect(state) {
395 395
396function getFullnarpData() { 396function getFullnarpData() {
397 signalFullnarpConnect('fullnarp-connecting'); 397 signalFullnarpConnect('fullnarp-connecting');
398 connect = window.location.href.replace('http', 'ws') + '/ws/'; 398 connect = window.location.href.replace('http', 'ws') + 'ws/';
399 ws = new WebSocket(connect); 399 ws = new WebSocket(connect);
400 400
401 ws.onopen = () => { 401 ws.onopen = () => {
diff --git a/static/halfnarp.js b/static/halfnarp.js
index b7f4a45..e40c84e 100644
--- a/static/halfnarp.js
+++ b/static/halfnarp.js
@@ -93,7 +93,7 @@ function redraw_calendar(myuid, ids) {
93 calendar += 'DTSTART:' + start.toISOString().replace(/-|;|:|\./g, '').replace(/...Z$/, 'Z') + '\r\n'; 93 calendar += 'DTSTART:' + start.toISOString().replace(/-|;|:|\./g, '').replace(/...Z$/, 'Z') + '\r\n';
94 calendar += 'DURATION:PT' + item.duration + 'S\r\n'; 94 calendar += 'DURATION:PT' + item.duration + 'S\r\n';
95 calendar += 'LOCATION:' + item.room_name + '\r\n'; 95 calendar += 'LOCATION:' + item.room_name + '\r\n';
96 calendar += 'URL:http://events.ccc.de/congress/2023/Fahrplan/events/' + item.event_id + '.html\r\n'; 96 calendar += 'URL:http://events.ccc.de/congress/2025/Fahrplan/events/' + item.event_id + '.html\r\n';
97 calendar += 'SUMMARY:' + item.title + '\r\n'; 97 calendar += 'SUMMARY:' + item.title + '\r\n';
98 calendar += 'DESCRIPTION:' + item.abstract.replace(/\n|\r/g, ' ') + '\r\n'; 98 calendar += 'DESCRIPTION:' + item.abstract.replace(/\n|\r/g, ' ') + '\r\n';
99 // console.log( 'id:' + id + ' ' + all_events[id] ); 99 // console.log( 'id:' + id + ' ' + all_events[id] );
@@ -102,14 +102,14 @@ function redraw_calendar(myuid, ids) {
102 } 102 }
103 }); 103 });
104 calendar += 'END:VCALENDAR\r\n'; 104 calendar += 'END:VCALENDAR\r\n';
105 $('.export-url-a').attr( 'href', "data:text/calendar;filename=38C3.ics," + encodeURIComponent(calendar) ); 105 $('.export-url-a').attr( 'href', "data:text/calendar;filename=39C3.ics," + encodeURIComponent(calendar) );
106 $('.export-url').removeClass( 'hidden' ); 106 $('.export-url').removeClass( 'hidden' );
107} 107}
108 108
109function do_the_halfnarp() { 109function do_the_halfnarp() {
110// var halfnarpAPI = 'talks_36C3.json'; 110// var halfnarpAPI = 'talks_36C3.json';
111 var halfnarpAPI = '/-/talkpreferences'; 111 var halfnarpAPI = '/-/talkpreferences';
112 var halfnarpCorrs = 'corr_array_38c3.json'; 112 var halfnarpCorrs = 'corr_array_39c3.json';
113 var halfnarpPubAPI = halfnarpAPI + '/public/'; 113 var halfnarpPubAPI = halfnarpAPI + '/public/';
114 var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0)); 114 var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));
115 window.top.all_events = new Object(); 115 window.top.all_events = new Object();
@@ -179,8 +179,8 @@ function do_the_halfnarp() {
179 return $(this).attr('event_id'); 179 return $(this).attr('event_id');
180 }).get(); 180 }).get();
181 try { 181 try {
182 localStorage['38C3-halfnarp'] = ids; 182 localStorage['39C3-halfnarp'] = ids;
183 myapi = localStorage.getItem('38C3-halfnarp-api'); 183 myapi = localStorage.getItem('39C3-halfnarp-api');
184 if (myapi) { 184 if (myapi) {
185 myapi = myapi.replace(/.*?:\//g, ""); 185 myapi = myapi.replace(/.*?:\//g, "");
186 myapi = 'https:/' + myapi.replace(/.*?:\//g, ""); 186 myapi = 'https:/' + myapi.replace(/.*?:\//g, "");
@@ -207,9 +207,9 @@ function do_the_halfnarp() {
207 $('.info').text('submitted'); 207 $('.info').text('submitted');
208 $('.info').removeClass('hidden'); 208 $('.info').removeClass('hidden');
209 try { 209 try {
210 localStorage['38C3-halfnarp-api'] = data['update_url']; 210 localStorage['39C3-halfnarp-api'] = data['update_url'];
211 localStorage['38C3-halfnarp-pid'] = mypid = data['hashed_uid']; 211 localStorage['39C3-halfnarp-pid'] = mypid = data['hashed_uid'];
212 localStorage['38C3-halfnarp-uid'] = myuid = data['uid']; 212 localStorage['39C3-halfnarp-uid'] = myuid = data['uid'];
213 window.location.hash = mypid; 213 window.location.hash = mypid;
214 } catch(err) {} 214 } catch(err) {}
215 }, 'json' ).fail(function() { 215 }, 'json' ).fail(function() {
@@ -225,9 +225,9 @@ function do_the_halfnarp() {
225 contentType: "application/json", 225 contentType: "application/json",
226 dataType: 'json', 226 dataType: 'json',
227 }).done(function(data) { 227 }).done(function(data) {
228 localStorage['38C3-halfnarp-uid'] = myuid = data['uid']; 228 localStorage['39C3-halfnarp-uid'] = myuid = data['uid'];
229 if( localStorage['38C3-halfnarp-pid'] ) { 229 if( localStorage['39C3-halfnarp-pid'] ) {
230 window.location.hash = localStorage['38C3-halfnarp-pid']; 230 window.location.hash = localStorage['39C3-halfnarp-pid'];
231 } 231 }
232 $('.info').text('updated'); 232 $('.info').text('updated');
233 $('.info').removeClass('hidden'); 233 $('.info').removeClass('hidden');
@@ -279,18 +279,21 @@ function do_the_halfnarp() {
279 } 279 }
280 280
281 /* Add callbacks for view selector */ 281 /* Add callbacks for view selector */
282 if (0) {
282 document.querySelector('.vlist').onclick = function() { toggle_grid(0); }; 283 document.querySelector('.vlist').onclick = function() { toggle_grid(0); };
283 document.querySelector('.vday1').onclick = function() { toggle_grid(1); }; 284 document.querySelector('.vday1').onclick = function() { toggle_grid(1); };
284 document.querySelector('.vday2').onclick = function() { toggle_grid(2); }; 285 document.querySelector('.vday2').onclick = function() { toggle_grid(2); };
285 document.querySelector('.vday3').onclick = function() { toggle_grid(3); }; 286 document.querySelector('.vday3').onclick = function() { toggle_grid(3); };
286 document.querySelector('.vday4').onclick = function() { toggle_grid(4); }; 287 document.querySelector('.vday4').onclick = function() { toggle_grid(4); };
287 document.querySelector('.vdays').onclick = function() { toggle_grid(5); }; 288 document.querySelector('.vdays').onclick = function() { toggle_grid(5); };
288 289 }
289 document.querySelector('.vlang').onclick = function() { document.body.classList.toggle('languages'); }; 290 document.querySelector('.vlang').onclick = function() { document.body.classList.toggle('languages'); };
290 document.querySelector('.vtrack').onclick = function() { document.body.classList.toggle('all-tracks'); }; 291 document.querySelector('.vtrack').onclick = function() { document.body.classList.toggle('all-tracks'); };
291 document.querySelector('.vnarpr').onclick = function() { $('.narpr').toggleClass('hidden'); set_random_event(); }; 292 document.querySelector('.vnarpr').onclick = function() { $('.narpr').toggleClass('hidden'); set_random_event(); };
292 293
294 if (0) {
293 document.querySelector('.vcorr').onclick = toggle_corr_mode; 295 document.querySelector('.vcorr').onclick = toggle_corr_mode;
296 }
294 297
295 $('.vclass').click( function() { toggle_classifier( $(this).attr('classifier'), $(this).hasClass('track'), $(this).hasClass('two_poles')); }); 298 $('.vclass').click( function() { toggle_classifier( $(this).attr('classifier'), $(this).hasClass('track'), $(this).hasClass('two_poles')); });
296 299
@@ -308,10 +311,10 @@ function do_the_halfnarp() {
308 /* If we've been here before, try to get local preferences. They are authoratative */ 311 /* If we've been here before, try to get local preferences. They are authoratative */
309 var selection = [], friends = { }; 312 var selection = [], friends = { };
310 try { 313 try {
311 selection = localStorage['38C3-halfnarp'] || []; 314 selection = localStorage['39C3-halfnarp'] || [];
312 friends = localStorage['38C3-halfnarp-friends'] || { }; 315 friends = localStorage['39C3-halfnarp-friends'] || { };
313 myuid = localStorage['38C3-halfnarp-uid'] || ''; 316 myuid = localStorage['39C3-halfnarp-uid'] || '';
314 mypid = localStorage['38C3-halfnarp-pid'] || ''; 317 mypid = localStorage['39C3-halfnarp-pid'] || '';
315 } catch(err) { 318 } catch(err) {
316 } 319 }
317 320
@@ -354,7 +357,7 @@ function do_the_halfnarp() {
354 if( hour > 23) 357 if( hour > 23)
355 hour -= 24; 358 hour -= 24;
356 359
357 /* Fix up room for 38C3 */ 360 /* Fix up room for 39C3 */
358 room = (item.room_id || '').toString().replace('1','room1').replace('2','room2').replace('3','room3'); 361 room = (item.room_id || '').toString().replace('1','room1').replace('2','room2').replace('3','room3');
359 362
360 /* Apply attributes to sort events into calendar */ 363 /* Apply attributes to sort events into calendar */
@@ -400,8 +403,8 @@ function do_the_halfnarp() {
400 } 403 }
401 }); 404 });
402 405
403 $.getJSON( halfnarpCorrs, { format: 'json' }).done(function(data) { window.top.all_votes = data; }); 406 // $.getJSON( halfnarpCorrs, { format: 'json' }).done(function(data) { window.top.all_votes = data; });
404 toggle_grid(5); 407 toggle_grid(0);
405 408
406 /* Check for a new friends public uid in location's #hash */ 409 /* Check for a new friends public uid in location's #hash */
407 var shared = window.location.hash; 410 var shared = window.location.hash;
@@ -439,7 +442,7 @@ function do_the_halfnarp() {
439 $.getJSON( halfnarpPubAPI + friends.pid, { format: 'json' }) 442 $.getJSON( halfnarpPubAPI + friends.pid, { format: 'json' })
440 .done(function( data ) { 443 .done(function( data ) {
441 friend.prefs = data.talk_ids; 444 friend.prefs = data.talk_ids;
442 localStorage['38C3-halfnarp-friends'] = friends; 445 localStorage['39C3-halfnarp-friends'] = friends;
443 update_friends(); 446 update_friends();
444 }); 447 });
445 } 448 }
diff --git a/static/index.html b/static/index.html
index bb680d1..bc8da67 100644
--- a/static/index.html
+++ b/static/index.html
@@ -3,8 +3,8 @@
3<head> 3<head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title>halfnarp web scheduling helper app</title> 5 <title>halfnarp web scheduling helper app</title>
6 <link rel="stylesheet" href="style_38c3.css"> 6 <link rel="stylesheet" href="style_39c3.css">
7 <link rel="stylesheet" href="style_38c3_tables.css"> 7 <link rel="stylesheet" href="style_39c3_tables.css">
8 <script src="jquery-3.7.1.min.js"></script> 8 <script src="jquery-3.7.1.min.js"></script>
9 <script src="jquery.qrcode.min.js"></script> 9 <script src="jquery.qrcode.min.js"></script>
10 <script src="halfnarp.js"></script> 10 <script src="halfnarp.js"></script>
@@ -14,14 +14,14 @@
14</head> 14</head>
15<body class="size-small in-list halfnarp"> 15<body class="size-small in-list halfnarp">
16 <header class="header"> 16 <header class="header">
17 <!--div class="views touch-only hidden vnarpr">narpr</div--> 17 <!--div class="views touch-only hidden vnarpr">narpr</div>
18 <div class="views vlist" title="Display all events sorted by track (Hotkey: L)">list</div> 18 <div class="views vlist" title="Display all events sorted by track (Hotkey: L)">list</div>
19 <div class="views vdays" title="Display all events in a 4-day-view (Hotkey: 0)">days</div> 19 <div class="views vdays" title="Display all events in a 4-day-view (Hotkey: 0)">days</div>
20 <div class="views vday1" title="Display all events on Day 1 (Hotkey: 1)">day 1</div> 20 <div class="views vday1" title="Display all events on Day 1 (Hotkey: 1)">day 1</div>
21 <div class="views vday2" title="Display all events on Day 2 (Hotkey: 2)">day 2</div> 21 <div class="views vday2" title="Display all events on Day 2 (Hotkey: 2)">day 2</div>
22 <div class="views vday3" title="Display all events on Day 3 (Hotkey: 3)">day 3</div> 22 <div class="views vday3" title="Display all events on Day 3 (Hotkey: 3)">day 3</div>
23 <div class="views vday4" title="Display all events on Day 4 (Hotkey: 4)">day 4</div> 23 <div class="views vday4" title="Display all events on Day 4 (Hotkey: 4)">day 4</div>
24 <div class="views vcorr" title="Enter correlation view (Hotkey: C)">half</div> 24 <div class="views vcorr" title="Enter correlation view (Hotkey: C)">half</div-->
25 <div class="views vtrack" title="Color event by track (Hotkey: T)">track</div> 25 <div class="views vtrack" title="Color event by track (Hotkey: T)">track</div>
26 <div class="views vlang" title="Color event by language (Hotkey: I)">lang</div> 26 <div class="views vlang" title="Color event by language (Hotkey: I)">lang</div>
27 <div class="header__right"> 27 <div class="header__right">
@@ -51,9 +51,9 @@
51</div> 51</div>
52 52
53 <div class="intro"> 53 <div class="intro">
54 <h1>The 38C3 halfnarp</h1> 54 <h1>The 39C3 halfnarp</h1>
55 <div class="touch-only hidden vnarpr narpr-beta">narpr<sup>β</sup></div> 55 <div class="touch-only hidden vnarpr narpr-beta">narpr<sup>β</sup></div>
56 <p>Help us to reduce conflicts of scheduling 38C3's Fahrplan: Click on the talks you would like to watch and press submit.</p> 56 <p>Help us to reduce conflicts of scheduling 39C3's Fahrplan: Click on the talks you would like to watch and press submit.</p>
57 <p>For questions please read <a href="faq.html">our FAQ</a> and <a href="http://events.ccc.de/2014/11/25/lets-do-the-halfnarp/">our blogpost</a> for details. Please report problems to <a href="mailto:erdgeist@ccc.de">erdgeist@ccc.de</a>.</p> 57 <p>For questions please read <a href="faq.html">our FAQ</a> and <a href="http://events.ccc.de/2014/11/25/lets-do-the-halfnarp/">our blogpost</a> for details. Please report problems to <a href="mailto:erdgeist@ccc.de">erdgeist@ccc.de</a>.</p>
58 <p class="corr-hint">Select an event below to display halfnarp correlations, the darker the stronger.</p> 58 <p class="corr-hint">Select an event below to display halfnarp correlations, the darker the stronger.</p>
59 <p class="touch-only hidden">NEW on mobile: Swipe the narpr!</p> 59 <p class="touch-only hidden">NEW on mobile: Swipe the narpr!</p>
@@ -75,10 +75,10 @@
75</div--> 75</div-->
76<div style="clear:both"></div> 76<div style="clear:both"></div>
77 77
78<div class="day_1 room-label room1">Saal 1</div><div class="day_1 room-label room2">Saal GLITCH</div><div class="day_1 room-label room3">Saal ZIGZAG</div> 78<div class="day_1 room-label room1">Saal One</div><div class="day_1 room-label room2">Saal Zero</div><div class="day_1 room-label room3">Saal Ground</div><div class="day_1 room-label room4">Saal Fuse</div>
79<div class="day_2 room-label room1">Saal 1</div><div class="day_2 room-label room2">Saal GLITCH</div><div class="day_2 room-label room3">Saal ZIGZAG</div> 79<div class="day_2 room-label room1">Saal One</div><div class="day_2 room-label room2">Saal Zero</div><div class="day_2 room-label room3">Saal Ground</div><div class="day_2 room-label room4">Saal Fuse</div>
80<div class="day_3 room-label room1">Saal 1</div><div class="day_3 room-label room2">Saal GLITCH</div><div class="day_3 room-label room3">Saal ZIGZAG</div> 80<div class="day_3 room-label room1">Saal One</div><div class="day_3 room-label room2">Saal Zero</div><div class="day_3 room-label room3">Saal Ground</div><div class="day_3 room-label room4">Saal Fuse</div>
81<div class="day_4 room-label room1">Saal 1</div><div class="day_4 room-label room2">Saal GLITCH</div><div class="day_4 room-label room3">Saal ZIGZAG</div> 81<div class="day_4 room-label room1">Saal One</div><div class="day_4 room-label room2">Saal Zero</div><div class="day_4 room-label room3">Saal Ground</div><div class="day_4 room-label room4">Saal Fuse</div>
82 82
83<div class=" time_1815 duration_3600 guide pause pause1">P A U S E</div> 83<div class=" time_1815 duration_3600 guide pause pause1">P A U S E</div>
84<!--div class=" time_1950 duration_3600 guide pause ">P A U S E</div--> 84<!--div class=" time_1950 duration_3600 guide pause ">P A U S E</div-->
@@ -90,13 +90,14 @@
90 <div class="wholeday room1 day_3 guide uneven">Day 3</div> 90 <div class="wholeday room1 day_3 guide uneven">Day 3</div>
91 <div class="wholeday room1 day_4 guide">Day 4</div> 91 <div class="wholeday room1 day_4 guide">Day 4</div>
92 92
93 <div class="track" id="6"><h2>Security</h2></div> 93 <div class="track" id="31"><h2>Security</h2></div>
94 <div class="track" id="4"><h2>Hardware &amp; Making</h2></div> 94 <div class="track" id="32"><h2>Hardware &amp; Making</h2></div>
95 <div class="track" id="7"><h2>Ethics, Society &amp; Politics</h2></div> 95 <div class="track" id="28"><h2>Ethics, Society &amp; Politics</h2></div>
96 <div class="track" id="2"><h2>CCC</h2></div> 96 <div class="track" id="30"><h2>CCC</h2></div>
97 <div class="track" id="3"><h2>Entertainment</h2></div> 97 <div class="track" id="27"><h2>Entertainment</h2></div>
98 <div class="track" id="5"><h2>Science</h2></div> 98 <div class="track" id="29"><h2>Science</h2></div>
99 <div class="track" id="1"><h2>Art &amp; Beauty</div> 99 <div class="track" id="33"><h2>Art &amp; Beauty</div>
100 <!--div class="track" id="999"><h2>Other</div-->
100 101
101<footer class="footer"> 102<footer class="footer">
102 <div class="footer__filter-side"> 103 <div class="footer__filter-side">
@@ -107,7 +108,7 @@
107<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 28" enable-background="new 0 0 22 28"><style type="text/css">.st0{fill:none;}</style><path class="st0" d="M-854-2242h1400v3600h-1400v-3600z"/><path d="M0 0v28l22-14-22-14z"/><path class="st0" d="M-16-10h48v48h-48v-48z"/></svg> 108<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 28" enable-background="new 0 0 22 28"><style type="text/css">.st0{fill:none;}</style><path class="st0" d="M-854-2242h1400v3600h-1400v-3600z"/><path d="M0 0v28l22-14-22-14z"/><path class="st0" d="M-16-10h48v48h-48v-48z"/></svg>
108</div> 109</div>
109 <div class="info hidden"></div> 110 <div class="info hidden"></div>
110 <a class="export-url-a" download="38C3.ics" type="text/calendar" href="#"><div class="export-url hidden">38C3.ics</div></a> 111 <a class="export-url-a" download="39C3.ics" type="text/calendar" href="#"><div class="export-url hidden">39C3.ics</div></a>
111 <div id="qrcode" class="hidden"></div> 112 <div id="qrcode" class="hidden"></div>
112</footer> 113</footer>
113 114
diff --git a/wsgi.py b/wsgi.py
index 2ad28d9..db217db 100644
--- a/wsgi.py
+++ b/wsgi.py
@@ -1,4 +1,4 @@
1from halfnarp2 import app 1from halfnarp2 import create_app
2 2
3if __name__ == "__main__": 3app = create_app("config.json")
4 app.run(debug=False) 4#app.run(debug=False)