function toggle_grid(whichDay) { var vclasses= [['in-list'], ['in-calendar', 'onlyday1'], ['in-calendar', 'onlyday2'], ['in-calendar', 'onlyday3'], ['in-calendar', 'onlyday4'], ['in-calendar', 'alldays']]; document.body.classList.remove( 'alldays', 'onlyday1', 'onlyday2', 'onlyday3', 'onlyday4', 'in-list', 'in-calendar'); if( whichDay < 0 || whichDay > 5 ) return; document.body.classList.add(...vclasses[whichDay]); document.getElementById('qrcode').classList.toggle('limit', whichDay == 0); } function toggle_corr_mode() { if (!document.body.classList.contains('correlate')) { document.body.classList.remove('all-tracks', 'languages', 'classifiers'); document.querySelectorAll('.event').forEach(elem => elem.setAttribute('corr', '')); } document.body.classList.toggle('correlate'); } function toggle_classifier(classifier, is_track, is_range) { if (document.body.classList.contains('classifiers') && document.body.getAttribute('classifiers') == classifier) { document.body.classList.remove('classifiers'); return; } var default_intensity = 0, prefix = ''; if (is_range) { default_intensity = 5; prefix = '+'; } is_range = is_range ? '+' : ''; for (ev of window.top.all_events) { if (ev.event_classifiers) { var intensity = default_intensity; // if track selector and empty, set to 80% if (ev.event_classifiers[classifier]) intensity = Math.round(ev.event_classifiers[classifier] / 10); $('#event_'+ev.event_id).attr('intensity', prefix + intensity); } } document.body.classList.add('classifiers'); document.body.classList.remove('all-tracks', 'languages', 'correlate'); document.body.setAttribute('classifier', classifier); } function set_random_event() { var keys = Object.keys(all_events).filter(function(event_id) { return $('#event_'+event_id+'.selected').length == 0 && $('#event_'+event_id+'.rejected').length == 0; }); if (keys.length == 0) { $('.narpr').toggleClass('hidden'); return; } item = all_events[keys[ keys.length * Math.random() << 0]]; $('.narpr_title').text(item.title || ''); $('.narpr_track').text(item.track_name || ''); $('.narpr_subtitle').text(item.subtitle || ''); $('.narpr_speakers').text(item.speaker_names || ''); $('.narpr_abstract').html(item.abstract || ''); window.narpr_event = item.event_id; } function redraw_qrcode(ids) { if (!ids) ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get(); if ($('#qrcode').hasClass('hidden') && ids.length == 0 ) return; var request = JSON.stringify({'talk_ids': ids}); var size = 68; if($('body').hasClass('qrcode-huge')) { size = 400; } $('#qrcode').empty(); $('#qrcode').qrcode({width: size, height: size, text: request}); $('#qrcode').removeClass('hidden'); } function redraw_calendar(myuid, ids) { if (!ids) ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get(); var now = new Date(); var calendar = 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//events.ccc.de//halfnarp//EN\r\nX-WR-TIMEZONE:Europe/Berlin\r\n'; ids.forEach( function(id) { var item = all_events[id]; if ('start_time' in item) { var start = new Date(item.start_time); calendar += 'BEGIN:VEVENT\r\n'; calendar += 'UID:'+myuid+item.event_id+'\r\n'; calendar += 'DTSTAMP:' + now.toISOString().replace(/-|;|:|\./g, '').replace(/...Z$/, 'Z') + '\r\n'; calendar += 'DTSTART:' + start.toISOString().replace(/-|;|:|\./g, '').replace(/...Z$/, 'Z') + '\r\n'; calendar += 'DURATION:PT' + item.duration + 'S\r\n'; calendar += 'LOCATION:' + item.room_name + '\r\n'; calendar += 'URL:http://events.ccc.de/congress/2023/Fahrplan/events/' + item.event_id + '.html\r\n'; calendar += 'SUMMARY:' + item.title + '\r\n'; calendar += 'DESCRIPTION:' + item.abstract.replace(/\n|\r/g, ' ') + '\r\n'; // console.log( 'id:' + id + ' ' + all_events[id] ); // console.log( all_events[id].title ); calendar += 'END:VEVENT\r\n'; } }); calendar += 'END:VCALENDAR\r\n'; $('.export-url-a').attr( 'href', "data:text/calendar;filename=38C3.ics," + encodeURIComponent(calendar) ); $('.export-url').removeClass( 'hidden' ); } function do_the_halfnarp() { // var halfnarpAPI = 'talks_36C3.json'; var halfnarpAPI = '/-/talkpreferences'; var halfnarpCorrs = 'corr_array_38c3.json'; var halfnarpPubAPI = halfnarpAPI + '/public/'; var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0)); window.top.all_events = new Object(); window.top.narpr_rejected = new Array(); var myuid, mypid, newfriend = new Object(); var allhours = ['10', '11','12','13','14','15','16','17','18','19','20','21','22','23','00','01','02','03']; $('.narpr_done').click( function(ev) { $('.narpr').toggleClass('hidden', true); if (!window.narpr_alerted) { window.narpr_alerted = true; alert("Thank you for using narpr(β). Don't forget to SUBMIT!"); } }); $('.narpr').on({ 'touchstart' : function(ev) { // alert("foo: " + ev.originalEvent.touches[0].clientX ); window.touch_startX = ev.originalEvent.touches[0].clientX; window.touch_curX = ev.originalEvent.touches[0].clientX; window.touch_valid = true; $('.narpr').css('background', '#ddd'); } }); $('.narpr').on({ 'touchmove' : function(ev) { var narp_view = $('.narpr'); if (ev.originalEvent.touches.length > 1) { narp_view.css('background', 'white'); window.touch_valid = false; } if (!window.touch_valid) return; if (ev.originalEvent.touches[0].clientX > window.touch_startX + 100) narp_view.css('background', 'green'); else if (ev.originalEvent.touches[0].clientX < window.touch_startX - 100) narp_view.css('background', 'red'); else narp_view.css('background', '#ddd'); window.touch_curX = ev.originalEvent.touches[0].clientX; // console.log(narp_view[0].clientHeight + ':' + narp_view[0].scrollHeight); if( narp_view[0].clientHeight >= narp_view[0].scrollHeight) ev.preventDefault(); } }); $('.narpr').on({ 'touchend' : function(ev) { if (!window.touch_valid) return; if (window.touch_curX > window.touch_startX + 100) { $('#event_'+window.narpr_event).toggleClass('selected', true); set_random_event(); } if (window.touch_curX < window.touch_startX - 100) { $('#event_'+window.narpr_event).toggleClass('selected', false); $('#event_'+window.narpr_event).toggleClass('rejected', true); set_random_event(); } $('.narpr').css('background', 'white'); } }); /* Add callback for submit click */ $('.submit').click( function() { var myapi; /* Get user's preferences and try to save them locally */ var ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get(); try { localStorage['38C3-halfnarp'] = ids; myapi = localStorage.getItem('38C3-halfnarp-api'); if (myapi) { myapi = myapi.replace(/.*?:\//g, ""); myapi = 'https:/' + myapi.replace(/.*?:\//g, ""); } } catch(err) { alert('Storing your choices locally is forbidden.'); } /* Convert preferences to JSON and post them to backend */ var request = JSON.stringify({'talk_ids': ids}); if( !myapi || !myapi.length ) { /* If we do not have resource URL, post data and get resource */ $.ajax({ type: 'POST', url: halfnarpAPI + '/', data: request, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, contentType: "text/plain", dataType: 'json', }).done(function(data) { $('.info').text('submitted'); $('.info').removeClass('hidden'); try { localStorage['38C3-halfnarp-api'] = data['update_url']; localStorage['38C3-halfnarp-pid'] = mypid = data['hashed_uid']; localStorage['38C3-halfnarp-uid'] = myuid = data['uid']; window.location.hash = mypid; } catch(err) {} }, 'json' ).fail(function() { $('.info').text('failed :('); $('.info').removeClass('hidden'); }); } else { /* If we do have a resource URL, update resource */ $.ajax({ type: 'PUT', url: myapi, data: request, contentType: "application/json", dataType: 'json', }).done(function(data) { localStorage['38C3-halfnarp-uid'] = myuid = data['uid']; if( localStorage['38C3-halfnarp-pid'] ) { window.location.hash = localStorage['38C3-halfnarp-pid']; } $('.info').text('updated'); $('.info').removeClass('hidden'); }).fail(function(msg) { $('.info').text('failed :('); $('.info').removeClass('hidden'); }); } /* Tell QRCode library to update and/or display preferences for Apps */ redraw_qrcode(ids); if (myuid) redraw_calendar(myuid, ids); }); /* Add handler for type ahead search input field */ var filter = document.getElementById('filter'); filter.onpaste = filter.oncut = filter.onkeypress = filter.onkeydown = filter.onkeyup = function() { var cnt = this.value.toLowerCase(); if( cnt.length ) document.querySelectorAll('.event').forEach(elem => elem.style.display = (elem.textContent || elem.innerText || '').toLowerCase().includes(cnt) ? "initial" : "none" ); else document.querySelectorAll('.event').forEach(elem => elem.style.display = "initial"); }; /* Add click handlers for event div sizers */ document.querySelector('.vsmallboxes').onclick = function() { document.body.classList.remove('size-medium', 'size-large'); document.body.classList.add('size-small'); }; document.querySelector('.vmediumboxes').onclick = function() { document.body.classList.remove('size-small', 'size-large'); document.body.classList.add('size-medium'); }; document.querySelector('.vlargeboxes').onclick = function() { document.body.classList.remove('size-small', 'size-medium'); document.body.classList.add('size-large'); }; /* Add de-highlighter on touch interface devices */ if( isTouch ) { document.body.onclick = function() { document.querySelector('.highlighted').forEach(elem => elem.classList.remove('highlighted')); }; document.querySelector('.touch-only').forEach(elem => elem.classList.remove('hidden')); } /* Add callbacks for view selector */ document.querySelector('.vlist').onclick = function() { toggle_grid(0); }; document.querySelector('.vday1').onclick = function() { toggle_grid(1); }; document.querySelector('.vday2').onclick = function() { toggle_grid(2); }; document.querySelector('.vday3').onclick = function() { toggle_grid(3); }; document.querySelector('.vday4').onclick = function() { toggle_grid(4); }; document.querySelector('.vdays').onclick = function() { toggle_grid(5); }; document.querySelector('.vlang').onclick = function() { document.body.classList.toggle('languages'); }; document.querySelector('.vtrack').onclick = function() { document.body.classList.toggle('all-tracks'); }; document.querySelector('.vnarpr').onclick = function() { $('.narpr').toggleClass('hidden'); set_random_event(); }; document.querySelector('.vcorr').onclick = toggle_corr_mode; $('.vclass').click( function() { toggle_classifier( $(this).attr('classifier'), $(this).hasClass('track'), $(this).hasClass('two_poles')); }); /* Create hour guides */ for (hour of allhours) { var elem = document.createElement('hr'); elem.classList.add('guide', 'time_' + hour + '00'); document.body.append(elem); elem = document.createElement('div'); elem.textContent = hour + '00'; elem.classList.add('guide', 'time_' + hour + '00'); document.body.append(elem); } /* If we've been here before, try to get local preferences. They are authoratative */ var selection = [], friends = { }; try { selection = localStorage['38C3-halfnarp'] || []; friends = localStorage['38C3-halfnarp-friends'] || { }; myuid = localStorage['38C3-halfnarp-uid'] || ''; mypid = localStorage['38C3-halfnarp-pid'] || ''; } catch(err) { } /* Fetch list of lectures to display */ $.getJSON( halfnarpAPI, { format: 'json' }) .done(function( data ) { $.each( data, function( i, item ) { /* Save event to all_events hash */ all_events[item.event_id] = item; /* Take copy of hidden event template div and select them, if they're in list of previous prereferences */ var t = $( '#template' ).clone(true); var event_id = item.event_id.toString(); t.addClass('event ' + ' lang_' + (item.language || 'en')); t.attr('event_id', item.event_id.toString()); t.attr('id', 'event_' + item.event_id.toString()); if( selection && selection.indexOf(item.event_id) != -1 ) { t.addClass( 'selected' ); } /* Sort textual info into event div */ t.find('.title').text(item.title); t.find('.speakers').text(item.speaker_names); t.find('.abstract').append(item.abstract); if (item.event_classifiers && item.event_classifiers['Foundations'] && item.event_classifiers['Foundations'] > 40.0) t.addClass('foundation'); /* start_time: 2014-12-29T21:15:00+01:00" */ var start_time = new Date(item.start_time); var day = start_time.getUTCDate() - 26; var hour = start_time.getUTCHours() + 1; var mins = start_time.getUTCMinutes(); /* After midnight: sort into yesterday */ if( hour < 9 ) day--; if( hour > 23) hour -= 24; /* Fix up room for 38C3 */ room = (item.room_id || '').toString().replace('1','room1').replace('2','room2').replace('3','room3'); /* Apply attributes to sort events into calendar */ t.addClass(room + ' duration_' + item.duration + ' day_'+day + ' time_' + (hour<10?'0':'') + hour + '' + (mins<10?'0':'') + mins); t.click( function(event) { if ($('body').hasClass('correlate')) { mark_corr($(this).attr('event_id')); event.stopPropagation(); return; } /* Transition for touch devices is highlighted => selected => highlighted ... */ if( isTouch ) { if ( $( this ).hasClass('highlighted') ) { $( this ).toggleClass('selected'); $('.info').addClass('hidden'); } else { $('.highlighted').removeClass('highlighted'); $( this ).addClass('highlighted'); } } else { $( this ).toggleClass('selected'); $('.info').addClass('hidden'); } event.stopPropagation(); }); /* Put new event into DOM tree. Track defaults to 'Other' */ try { var track = item.track_id.toString(); } catch(e) { var track = "Other"; } var d = $( '#' + track ); t.addClass('track_' + track ); if( !d.length ) { d = $( '#Other' ); } d.append(t); if( newfriend.pid ) { newfriend.prefs.forEach( function( eventid ) { $( '#event_' + eventid ).addClass( 'friend' ); }); } }); $.getJSON( halfnarpCorrs, { format: 'json' }).done(function(data) { window.top.all_votes = data; }); toggle_grid(5); /* Check for a new friends public uid in location's #hash */ var shared = window.location.hash; shared = shared ? shared.substr(1) : ''; if( shared.length ) { if ( ( friends[shared] ) || ( shared === mypid ) ) { } else { $.getJSON( halfnarpPubAPI + shared, { format: 'json' }) .done(function( data ) { newfriend.pid = shared; newfriend.prefs = data.talk_ids; newfriend.prefs.forEach( function( eventid ) { $( '#event_' + eventid ).addClass( 'friend' ); }); }); } } // window.location.hash = ''; ids = $('.selected').map( function() { return $(this).attr('event_id'); }).get(); if (ids.length) { redraw_qrcode(ids); if (myuid) redraw_calendar(myuid, ids); } $('#qrcode').click( function() { $('body').toggleClass('qrcode-huge'); redraw_qrcode(); }); /* Update friends cache */ for( var friend in friends ) { $.getJSON( halfnarpPubAPI + friends.pid, { format: 'json' }) .done(function( data ) { friend.prefs = data.talk_ids; localStorage['38C3-halfnarp-friends'] = friends; update_friends(); }); } }); document.onkeypress = function(e) { if( document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA' ) return; switch( e.keyCode ) { case 48: case 94: /* 0 */ toggle_grid(5); break; case 49: case 50: case 51: case 52: /* 1-4 */ /* toggle_grid(e.keyCode-48); */ break; case 76: case 108: /* l */ toggle_grid(0); break; case 68: case 100: /* d */ /* toggle_grid(5); */ break; case 73: case 105: /* i */ document.body.classList.remove('all-tracks'); document.body.classList.toggle('languages'); break; case 84: case 116: /* t */ document.body.classList.remove('languages'); document.body.classList.toggle('all-tracks'); break; case 67: case 99: /* c */ /* toggle_corr_mode(); */ break; } }; } function mark_corr(eid) { /* If JSON with votes is not there, bail */ if (!all_votes) return; /* Reset correlation markers */ document.querySelectorAll('.event').forEach(elem => elem.setAttribute('corr', '')); /* Get index of reference event id */ var eoff = all_votes.event_ids.indexOf(eid); if (eoff==-1) return; document.querySelectorAll('.event').forEach( function(dest) { var destid = dest.getAttribute('event_id'); /* mark reference event at another place */ if (destid == eid) { dest.setAttribute('corr', 'x'); return; } var destoff = all_votes.event_ids.indexOf(destid); if (destoff==-1) { dest.setAttribute('corr', '0'); return; } /* Only the smaller event-id's string has the info */ if (eoff < destoff) dest.setAttribute('corr', all_votes.event_corrs[eoff].charAt(destoff-eoff-1)); else dest.setAttribute('corr', all_votes.event_corrs[destoff].charAt(eoff-destoff-1)); }); }