summaryrefslogtreecommitdiff
path: root/static/halfnarp.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/halfnarp.js')
-rw-r--r--static/halfnarp.js510
1 files changed, 510 insertions, 0 deletions
diff --git a/static/halfnarp.js b/static/halfnarp.js
new file mode 100644
index 0000000..878f3ab
--- /dev/null
+++ b/static/halfnarp.js
@@ -0,0 +1,510 @@
1function toggle_grid(whichDay) {
2 var vclasses= [['in-list'], ['in-calendar', 'onlyday1'], ['in-calendar', 'onlyday2'], ['in-calendar', 'onlyday3'],
3 ['in-calendar', 'onlyday4'], ['in-calendar', 'alldays']];
4 document.body.classList.remove( 'alldays', 'onlyday1', 'onlyday2', 'onlyday3', 'onlyday4', 'in-list', 'in-calendar');
5 if( whichDay < 0 || whichDay > 5 ) return;
6 document.body.classList.add(...vclasses[whichDay]);
7 document.getElementById('qrcode').classList.toggle('limit', whichDay == 0);
8}
9
10function toggle_corr_mode() {
11 if (!document.body.classList.contains('correlate'))
12 document.body.classList.remove('all-tracks', 'languages', 'classifiers');
13 document.querySelectorAll('.event').forEach(elem => elem.setAttribute('corr', ''));
14 }
15 document.body.classList.toggle('correlate');
16}
17
18function toggle_classifier(classifier, is_track, is_range) {
19 if (document.body.classList.contains('classifiers') && document.body.getAttribute('classifiers') == classifier) {
20 document.body.classList.remove('classifiers');
21 return;
22 }
23 var default_intensity = 0, prefix = '';
24 if (is_range) {
25 default_intensity = 5;
26 prefix = '+';
27 }
28 is_range = is_range ? '+' : '';
29 for (ev of window.top.all_events) {
30 if (ev.event_classifiers) {
31 var intensity = default_intensity;
32 // if track selector and empty, set to 80%
33 if (ev.event_classifiers[classifier])
34 intensity = Math.round(ev.event_classifiers[classifier] / 10);
35 $('#event_'+ev.event_id).attr('intensity', prefix + intensity);
36 }
37 }
38 document.body.classList.add('classifiers');
39 document.body.classList.remove('all-tracks', 'languages', 'correlate');
40 document.body.setAttribute('classifier', classifier);
41}
42
43function set_random_event() {
44 var keys = Object.keys(all_events).filter(function(event_id) {
45 return $('#event_'+event_id+'.selected').length == 0 &&
46 $('#event_'+event_id+'.rejected').length == 0;
47 });
48
49 if (keys.length == 0) {
50 $('.narpr').toggleClass('hidden');
51 return;
52 }
53
54 item = all_events[keys[ keys.length * Math.random() << 0]];
55 $('.narpr_title').text(item.title || '');
56 $('.narpr_track').text(item.track_name || '');
57 $('.narpr_subtitle').text(item.subtitle || '');
58 $('.narpr_speakers').text(item.speaker_names || '');
59 $('.narpr_abstract').html(item.abstract || '');
60 window.narpr_event = item.event_id;
61}
62
63function redraw_qrcode(ids) {
64 if (!ids)
65 ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get();
66 if ($('#qrcode').hasClass('hidden') && ids.length == 0 )
67 return;
68 var request = JSON.stringify({'talk_ids': ids});
69 var size = 68;
70 if($('body').hasClass('qrcode-huge')) {
71 size = 400;
72 }
73
74 $('#qrcode').empty();
75 $('#qrcode').qrcode({width: size, height: size, text: request});
76 $('#qrcode').removeClass('hidden');
77}
78
79function redraw_calendar(myuid, ids) {
80 if (!ids)
81 ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get();
82
83 var now = new Date();
84 var calendar = 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//events.ccc.de//halfnarp//EN\r\nX-WR-TIMEZONE:Europe/Berlin\r\n';
85 ids.forEach( function(id) {
86 var item = all_events[id];
87 if ('start_time' in item) {
88
89 var start = new Date(item.start_time);
90 calendar += 'BEGIN:VEVENT\r\n';
91 calendar += 'UID:'+myuid+item.event_id+'\r\n';
92 calendar += 'DTSTAMP:' + now.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';
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';
97 calendar += 'SUMMARY:' + item.title + '\r\n';
98 calendar += 'DESCRIPTION:' + item.abstract.replace(/\n|\r/g, ' ') + '\r\n';
99 // console.log( 'id:' + id + ' ' + all_events[id] );
100 // console.log( all_events[id].title );
101 calendar += 'END:VEVENT\r\n';
102 }
103 });
104 calendar += 'END:VCALENDAR\r\n';
105 $('.export-url-a').attr( 'href', "data:text/calendar;filename=38C3.ics," + encodeURIComponent(calendar) );
106 $('.export-url').removeClass( 'hidden' );
107}
108
109function do_the_halfnarp() {
110// var halfnarpAPI = 'talks_36C3.json';
111 var halfnarpAPI = '/-/talkpreferences';
112 var halfnarpCorrs = 'corr_array_38c3.json';
113 var halfnarpPubAPI = halfnarpAPI + '/public/';
114 var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));
115 window.top.all_events = new Object();
116 window.top.narpr_rejected = new Array();
117 var myuid, mypid, newfriend = new Object();
118 var allhours = ['10', '11','12','13','14','15','16','17','18','19','20','21','22','23','00','01','02','03'];
119
120 $('.narpr_done').click( function(ev) {
121 $('.narpr').toggleClass('hidden', true);
122 if (!window.narpr_alerted) {
123 window.narpr_alerted = true;
124 alert("Thank you for using narpr(β). Don't forget to SUBMIT!");
125 }
126 });
127
128 $('.narpr').on({ 'touchstart' : function(ev) {
129 // alert("foo: " + ev.originalEvent.touches[0].clientX );
130 window.touch_startX = ev.originalEvent.touches[0].clientX;
131 window.touch_curX = ev.originalEvent.touches[0].clientX;
132 window.touch_valid = true;
133 $('.narpr').css('background', '#ddd');
134 } });
135
136 $('.narpr').on({ 'touchmove' : function(ev) {
137 var narp_view = $('.narpr');
138 if (ev.originalEvent.touches.length > 1) {
139 narp_view.css('background', 'white');
140 window.touch_valid = false;
141 }
142 if (!window.touch_valid)
143 return;
144 if (ev.originalEvent.touches[0].clientX > window.touch_startX + 100)
145 narp_view.css('background', 'green');
146 else if (ev.originalEvent.touches[0].clientX < window.touch_startX - 100)
147 narp_view.css('background', 'red');
148 else
149 narp_view.css('background', '#ddd');
150 window.touch_curX = ev.originalEvent.touches[0].clientX;
151
152 // console.log(narp_view[0].clientHeight + ':' + narp_view[0].scrollHeight);
153 if( narp_view[0].clientHeight >= narp_view[0].scrollHeight)
154 ev.preventDefault();
155 } });
156
157 $('.narpr').on({ 'touchend' : function(ev) {
158 if (!window.touch_valid)
159 return;
160 if (window.touch_curX > window.touch_startX + 100) {
161 $('#event_'+window.narpr_event).toggleClass('selected', true);
162 set_random_event();
163 }
164 if (window.touch_curX < window.touch_startX - 100) {
165 $('#event_'+window.narpr_event).toggleClass('selected', false);
166 $('#event_'+window.narpr_event).toggleClass('rejected', true);
167 set_random_event();
168 }
169
170 $('.narpr').css('background', 'white');
171 } });
172
173 /* Add callback for submit click */
174 $('.submit').click( function() {
175 var myapi;
176
177 /* Get user's preferences and try to save them locally */
178 var ids = $('.selected').map( function() {
179 return $(this).attr('event_id');
180 }).get();
181 try {
182 localStorage['38C3-halfnarp'] = ids;
183 myapi = localStorage.getItem('38C3-halfnarp-api');
184 if (myapi) {
185 myapi = myapi.replace(/.*?:\//g, "");
186 myapi = 'https:/' + myapi.replace(/.*?:\//g, "");
187 }
188 } catch(err) {
189 alert('Storing your choices locally is forbidden.');
190 }
191
192 /* Convert preferences to JSON and post them to backend */
193 var request = JSON.stringify({'talk_ids': ids});
194 if( !myapi || !myapi.length ) {
195 /* If we do not have resource URL, post data and get resource */
196 $.ajax({
197 type: 'POST',
198 url: halfnarpAPI + '/',
199 data: request,
200 headers: {
201 'Accept': 'application/json',
202 'Content-Type': 'application/json'
203 },
204 contentType: "text/plain",
205 dataType: 'json',
206 }).done(function(data) {
207 $('.info').text('submitted');
208 $('.info').removeClass('hidden');
209 try {
210 localStorage['38C3-halfnarp-api'] = data['update_url'];
211 localStorage['38C3-halfnarp-pid'] = mypid = data['hashed_uid'];
212 localStorage['38C3-halfnarp-uid'] = myuid = data['uid'];
213 window.location.hash = mypid;
214 } catch(err) {}
215 }, 'json' ).fail(function() {
216 $('.info').text('failed :(');
217 $('.info').removeClass('hidden');
218 });
219 } else {
220 /* If we do have a resource URL, update resource */
221 $.ajax({
222 type: 'PUT',
223 url: myapi,
224 data: request,
225 contentType: "application/json",
226 dataType: 'json',
227 }).done(function(data) {
228 localStorage['38C3-halfnarp-uid'] = myuid = data['uid'];
229 if( localStorage['38C3-halfnarp-pid'] ) {
230 window.location.hash = localStorage['38C3-halfnarp-pid'];
231 }
232 $('.info').text('updated');
233 $('.info').removeClass('hidden');
234 }).fail(function(msg) {
235 $('.info').text('failed :(');
236 $('.info').removeClass('hidden');
237 });
238 }
239
240 /* Tell QRCode library to update and/or display preferences for Apps */
241 redraw_qrcode(ids);
242
243 if (myuid)
244 redraw_calendar(myuid, ids);
245 });
246
247 /* Add handler for type ahead search input field */
248 var filter = document.getElementById('filter');
249 filter.onpaste = filter.oncut = filter.onkeypress = filter.onkeydown = filter.onkeyup = function() {
250 var cnt = this.value.toLowerCase();
251 if( cnt.length )
252 document.querySelectorAll('.event').forEach(elem => elem.style.display = (elem.textContent || elem.innerText || '').toLowerCase().includes(cnt) ? "initial" : "none" );
253 else
254 document.querySelectorAll('.event').forEach(elem => elem.style.display = "initial");
255 };
256
257 /* Add click handlers for event div sizers */
258 document.querySelector('.vsmallboxes').onclick = function() {
259 document.body.classList.remove('size-medium', 'size-large');
260 document.body.classList.add('size-small');
261 };
262
263 document.querySelector('.vmediumboxes').onclick = function() {
264 document.body.classList.remove('size-small', 'size-large');
265 document.body.classList.add('size-medium');
266 };
267
268 document.querySelector('.vlargeboxes').onclick = function() {
269 document.body.classList.remove('size-small', 'size-medium');
270 document.body.classList.add('size-large');
271 };
272
273 /* Add de-highlighter on touch interface devices */
274 if( isTouch ) {
275 document.body.onclick = function() {
276 document.querySelector('.highlighted').forEach(elem => elem.classList.remove('highlighted'));
277 };
278 document.querySelector('.touch-only').forEach(elem => elem.classList.remove('hidden'));
279 }
280
281 /* Add callbacks for view selector */
282 document.querySelector('.vlist').onclick = function() { toggle_grid(0); };
283 document.querySelector('.vday1').onclick = function() { toggle_grid(1); };
284 document.querySelector('.vday2').onclick = function() { toggle_grid(2); };
285 document.querySelector('.vday3').onclick = function() { toggle_grid(3); };
286 document.querySelector('.vday4').onclick = function() { toggle_grid(4); };
287 document.querySelector('.vdays').onclick = function() { toggle_grid(5); };
288
289 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('.vnarpr').onclick = function() { $('.narpr').toggleClass('hidden'); set_random_event(); };
292
293 document.querySelector('.vcorr').onclick = toggle_corr_mode;
294
295 $('.vclass').click( function() { toggle_classifier( $(this).attr('classifier'), $(this).hasClass('track'), $(this).hasClass('two_poles')); });
296
297 /* Create hour guides */
298 for (hour of allhours) {
299 var elem = document.createElement('hr');
300 elem.classList.add('guide', 'time_' + hour + '00');
301 document.body.append(elem);
302 elem = document.createElement('div');
303 elem.textContent = hour + '00';
304 elem.classList.add('guide', 'time_' + hour + '00');
305 document.body.append(elem);
306 }
307
308 /* If we've been here before, try to get local preferences. They are authoratative */
309 var selection = [], friends = { };
310 try {
311 selection = localStorage['38C3-halfnarp'] || [];
312 friends = localStorage['38C3-halfnarp-friends'] || { };
313 myuid = localStorage['38C3-halfnarp-uid'] || '';
314 mypid = localStorage['38C3-halfnarp-pid'] || '';
315 } catch(err) {
316 }
317
318 /* Fetch list of lectures to display */
319 $.getJSON( halfnarpAPI, { format: 'json' })
320 .done(function( data ) {
321 $.each( data, function( i, item ) {
322 /* Save event to all_events hash */
323 all_events[item.event_id] = item;
324
325 /* Take copy of hidden event template div and select them, if they're in
326 list of previous prereferences */
327 var t = $( '#template' ).clone(true);
328 var event_id = item.event_id.toString();
329 t.addClass('event ' + ' lang_' + (item.language || 'en'));
330 t.attr('event_id', item.event_id.toString());
331 t.attr('id', 'event_' + item.event_id.toString());
332 if( selection && selection.indexOf(item.event_id) != -1 ) {
333 t.addClass( 'selected' );
334 }
335
336 /* Sort textual info into event div */
337 t.find('.title').text(item.title);
338 t.find('.speakers').text(item.speaker_names);
339 t.find('.abstract').append(item.abstract);
340
341 if (item.event_classifiers && item.event_classifiers['Foundations'] && item.event_classifiers['Foundations'] > 40.0)
342 t.addClass('foundation');
343
344 /* start_time: 2014-12-29T21:15:00+01:00" */
345 var start_time = new Date(item.start_time);
346
347 var day = start_time.getUTCDate() - 26;
348 var hour = start_time.getUTCHours() + 1;
349 var mins = start_time.getUTCMinutes();
350
351 /* After midnight: sort into yesterday */
352 if( hour < 9 )
353 day--;
354 if( hour > 23)
355 hour -= 24;
356
357 /* Fix up room for 38C3 */
358 room = (item.room_id || '').toString().replace('1','room1').replace('2','room2').replace('3','room3');
359
360 /* Apply attributes to sort events into calendar */
361 t.addClass(room + ' duration_' + item.duration + ' day_'+day + ' time_' + (hour<10?'0':'') + hour + '' + (mins<10?'0':'') + mins);
362
363 t.click( function(event) {
364 if ($('body').hasClass('correlate')) {
365 mark_corr($(this).attr('event_id'));
366 event.stopPropagation();
367 return;
368 }
369 /* Transition for touch devices is highlighted => selected => highlighted ... */
370 if( isTouch ) {
371 if ( $( this ).hasClass('highlighted') ) {
372 $( this ).toggleClass('selected');
373 $('.info').addClass('hidden');
374 } else {
375 $('.highlighted').removeClass('highlighted');
376 $( this ).addClass('highlighted');
377 }
378 } else {
379 $( this ).toggleClass('selected');
380 $('.info').addClass('hidden');
381 }
382 event.stopPropagation();
383 });
384 /* Put new event into DOM tree. Track defaults to 'Other' */
385 try {
386 var track = item.track_id.toString();
387 } catch(e) {
388 var track = "Other";
389 }
390 var d = $( '#' + track );
391 t.addClass('track_' + track );
392 if( !d.length ) {
393 d = $( '#Other' );
394 }
395 d.append(t);
396 if( newfriend.pid ) {
397 newfriend.prefs.forEach( function( eventid ) {
398 $( '#event_' + eventid ).addClass( 'friend' );
399 });
400 }
401 });
402
403 $.getJSON( halfnarpCorrs, { format: 'json' }).done(function(data) { window.top.all_votes = data; });
404 toggle_grid(5);
405
406 /* Check for a new friends public uid in location's #hash */
407 var shared = window.location.hash;
408 shared = shared ? shared.substr(1) : '';
409 if( shared.length ) {
410 if ( ( friends[shared] ) || ( shared === mypid ) ) {
411
412 } else {
413 $.getJSON( halfnarpPubAPI + shared, { format: 'json' })
414 .done(function( data ) {
415 newfriend.pid = shared;
416 newfriend.prefs = data.talk_ids;
417 newfriend.prefs.forEach( function( eventid ) {
418 $( '#event_' + eventid ).addClass( 'friend' );
419 });
420 });
421 }
422 }
423 // window.location.hash = '';
424
425 ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get();
426 if (ids.length) {
427 redraw_qrcode(ids);
428 if (myuid)
429 redraw_calendar(myuid, ids);
430 }
431
432 $('#qrcode').click( function() {
433 $('body').toggleClass('qrcode-huge');
434 redraw_qrcode();
435 });
436
437 /* Update friends cache */
438 for( var friend in friends ) {
439 $.getJSON( halfnarpPubAPI + friends.pid, { format: 'json' })
440 .done(function( data ) {
441 friend.prefs = data.talk_ids;
442 localStorage['38C3-halfnarp-friends'] = friends;
443 update_friends();
444 });
445 }
446
447 });
448 document.onkeypress = function(e) {
449 if( document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA' )
450 return;
451 switch( e.keyCode ) {
452 case 48: case 94: /* 0 */
453 toggle_grid(5);
454 break;
455 case 49: case 50: case 51: case 52: /* 1-4 */
456/* toggle_grid(e.keyCode-48); */
457 break;
458 case 76: case 108: /* l */
459 toggle_grid(0);
460 break;
461 case 68: case 100: /* d */
462/* toggle_grid(5); */
463 break;
464 case 73: case 105: /* i */
465 document.body.classList.remove('all-tracks');
466 document.body.classList.toggle('languages');
467 break;
468 case 84: case 116: /* t */
469 document.body.classList.remove('languages');
470 document.body.classList.toggle('all-tracks');
471 break;
472 case 67: case 99: /* c */
473/* toggle_corr_mode(); */
474 break;
475 }
476 };
477}
478
479function mark_corr(eid) {
480 /* If JSON with votes is not there, bail */
481 if (!all_votes) return;
482
483 /* Reset correlation markers */
484 document.querySelectorAll('.event').forEach(elem => elem.setAttribute('corr', '');
485
486 /* Get index of reference event id */
487 var eoff = all_votes.event_ids.indexOf(eid);
488 if (eoff==-1) return;
489
490 document.querySelectorAll('.event').forEach( function(dest) {
491 var destid = dest.getAttribute('event_id');
492 /* mark reference event at another place */
493 if (destid == eid) {
494 dest.setAttribute('corr', 'x');
495 return;
496 }
497
498 var destoff = all_votes.event_ids.indexOf(destid);
499 if (destoff==-1) {
500 dest.setAttribute('corr', '0');
501 return;
502 }
503
504 /* Only the smaller event-id's string has the info */
505 if (eoff < destoff)
506 dest.setAttribute('corr', all_votes.event_corrs[eoff].charAt(destoff-eoff-1));
507 else
508 dest.setAttribute('corr', all_votes.event_corrs[destoff].charAt(eoff-destoff-1));
509 });
510}