diff options
Diffstat (limited to 'static/fullnarp.js')
| -rw-r--r-- | static/fullnarp.js | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/static/fullnarp.js b/static/fullnarp.js new file mode 100644 index 0000000..8b7d36d --- /dev/null +++ b/static/fullnarp.js | |||
| @@ -0,0 +1,641 @@ | |||
| 1 | let ws; // WebSocket instance | ||
| 2 | |||
| 3 | function toggle_grid(whichDay) { | ||
| 4 | var vclasses= [['in-list'], ['in-calendar', 'onlyday1'], ['in-calendar', 'onlyday2'], ['in-calendar', 'onlyday3'], | ||
| 5 | ['in-calendar', 'onlyday4'], ['in-calendar', 'alldays']]; | ||
| 6 | document.body.classList.remove( 'alldays', 'onlyday1', 'onlyday2', 'onlyday3', 'onlyday4', 'in-list', 'in-calendar'); | ||
| 7 | if( whichDay < 0 || whichDay > 5 ) return; | ||
| 8 | document.body.classList.add(...vclasses[whichDay]); | ||
| 9 | } | ||
| 10 | |||
| 11 | function distribute_votes() { | ||
| 12 | document.querySelectorAll('.event').forEach( function(element) { | ||
| 13 | var eid = element.getAttribute('event_id'); | ||
| 14 | var abs = window.votes[eid]; | ||
| 15 | var klasse = 5000; | ||
| 16 | if (abs < 2000) { klasse = 2000; } | ||
| 17 | if (abs < 1000) { klasse = 1000; } | ||
| 18 | if (abs < 500) { klasse = 500; } | ||
| 19 | if (abs < 200) { klasse = 200; } | ||
| 20 | if (abs < 100) { klasse = 100; } | ||
| 21 | if (abs < 50) { klasse = 50; } | ||
| 22 | if (abs < 20) { klasse = 20; } | ||
| 23 | if (abs < 10) { klasse = 10; } | ||
| 24 | if (!abs) klasse = 10; | ||
| 25 | |||
| 26 | var abselem = element.querySelector('.absval'); | ||
| 27 | if (abselem) | ||
| 28 | abselem.textContent = '' + abs; | ||
| 29 | else { | ||
| 30 | var abselem = document.createElement('div'); | ||
| 31 | abselem.textContent = '' + abs; | ||
| 32 | abselem.classList.add('absval'); | ||
| 33 | element.insertBefore(abselem, element.firstChild); | ||
| 34 | } | ||
| 35 | element.classList.add('class_' + klasse); | ||
| 36 | }); | ||
| 37 | } | ||
| 38 | |||
| 39 | function corr_for_eventids(id1, id2) { | ||
| 40 | var d = 0, c = 0, cd = 0, l = window.raw_votes.length; | ||
| 41 | for (item of window.raw_votes) { | ||
| 42 | var x = 0; | ||
| 43 | if( item.indexOf(id1) > -1 ) { ++d; x++;} | ||
| 44 | if( item.indexOf(id2) > -1 ) { ++c; cd+=x; } | ||
| 45 | } | ||
| 46 | |||
| 47 | var mid = 0; | ||
| 48 | // if ( d * c ) mid = Math.round( 4.0 * ( ( cd * l ) / ( c * d ) ) * ( cd / d + cd / c ) ); | ||
| 49 | if ( d * c ) mid = Math.round( 4 * l * cd * cd * ( c + d ) / ( c * c * d * d ) ) | ||
| 50 | if (mid>9) mid=9; | ||
| 51 | return mid; | ||
| 52 | } | ||
| 53 | |||
| 54 | function show_all_correlates(el) { | ||
| 55 | /* First identify the room to see what other rooms to consider | ||
| 56 | correlates always grow from the top slot to the right, | ||
| 57 | unless there's an overlapping event to the left that starts earlier | ||
| 58 | */ | ||
| 59 | var event_room = el.getAttribute('fullnarp-room'); | ||
| 60 | var event_day = el.getAttribute('fullnarp-day'); | ||
| 61 | var event_time = el.getAttribute('fullnarp-time'); | ||
| 62 | |||
| 63 | if (!event_time) return; | ||
| 64 | |||
| 65 | var event_start; | ||
| 66 | try { event_start = time_to_mins(event_time); } catch(e) { return; } | ||
| 67 | var event_duration = el.getAttribute('fullnarp-duration') / 60; | ||
| 68 | |||
| 69 | /* Only test events to the right, if they start at the exact same time */ | ||
| 70 | document.querySelectorAll('.event.day_'+event_day).forEach( function(check_el, index) { | ||
| 71 | var check_room = check_el.getAttribute('fullnarp-room'); | ||
| 72 | if (event_room == check_room) return; | ||
| 73 | |||
| 74 | var check_time = check_el.getAttribute('fullnarp-time'); | ||
| 75 | if (!check_time) return; | ||
| 76 | var check_start = time_to_mins(check_time); | ||
| 77 | var check_duration = check_el.getAttribute('fullnarp-duration') / 60; | ||
| 78 | var dist = check_el.getAttribute('fullnarp-room') - event_room; | ||
| 79 | var overlap = check_start < event_start + event_duration && event_start < check_start + check_duration; | ||
| 80 | |||
| 81 | if (!overlap) return; | ||
| 82 | if (event_start == check_start && dist <= 0) return; | ||
| 83 | if (event_start < check_start) return; | ||
| 84 | |||
| 85 | var corr = corr_for_eventids(el.getAttribute('event_id'), check_el.getAttribute('event_id')); | ||
| 86 | var dir = dist > 0 ? 'r' : 'l'; | ||
| 87 | var div = document.createElement('div'); | ||
| 88 | div.classList.add('corrweb', dir.repeat(Math.abs(dist)), 'day_' + event_day, 'room' + event_room, 'time_' + event_time, 'corr_d_' + corr); | ||
| 89 | document.body.appendChild(div); | ||
| 90 | }) | ||
| 91 | } | ||
| 92 | |||
| 93 | function display_correlation() { | ||
| 94 | var selected = document.querySelectorAll('.selected'); | ||
| 95 | if( selected.length == 1 ) { | ||
| 96 | selected = selected[0]; | ||
| 97 | document.querySelectorAll('.event').forEach(elem => mark_correlation(elem, selected)); | ||
| 98 | } | ||
| 99 | if (document.body.classList.contains('correlate')) | ||
| 100 | distribute_votes(); | ||
| 101 | document.body.classList.toggle('correlate'); | ||
| 102 | } | ||
| 103 | |||
| 104 | function mark_correlation(dest, comp) { | ||
| 105 | var id1 = dest.getAttribute('event_id'); | ||
| 106 | var id2 = comp.getAttribute('event_id'); | ||
| 107 | var d = 0, c = 0, cd = 0, l = window.raw_votes.length; | ||
| 108 | for (vote of window.raw_votes) { | ||
| 109 | var x = 0; | ||
| 110 | if( vote.indexOf(id1) > -1 ) { ++d; x++;} | ||
| 111 | if( vote.indexOf(id2) > -1 ) { ++c; cd+=x; } | ||
| 112 | } | ||
| 113 | |||
| 114 | var mid = 0; | ||
| 115 | // if ( d * c ) mid = Math.round( 4.0 * ( ( cd * l ) / ( c * d ) ) * ( cd / d + cd / c ) ); | ||
| 116 | if ( d * c ) mid = Math.round( 4 * l * cd * cd * ( c + d ) / ( c * c * d * d ) ) | ||
| 117 | if (mid>9) mid=9; | ||
| 118 | |||
| 119 | dest.className = dest.className.replace(/\bcorr_\S+/g, ''); | ||
| 120 | dest.setAttribute('corr', mid); | ||
| 121 | dest.querySelector('.absval').textContent = mid + ':' + Math.round( 100 * cd / d ) + '%:' + Math.round( 100 * cd / c ) + '%'; | ||
| 122 | |||
| 123 | } | ||
| 124 | |||
| 125 | function mark_avail(el) { | ||
| 126 | el.classList.toggle('unavailable', !check_avail(el, el.getAttribute('fullnarp-day'), el.getAttribute('fullnarp-time'))); | ||
| 127 | } | ||
| 128 | |||
| 129 | function time_to_mins(time) { | ||
| 130 | var hour_mins = /(\d\d)(\d\d)/.exec(time); | ||
| 131 | if( hour_mins[1] < 9 ) { hour_mins[1] = 24 + hour_mins[1]; } | ||
| 132 | return 60 * hour_mins[1] + 1 * hour_mins[2]; | ||
| 133 | } | ||
| 134 | |||
| 135 | function check_avail(el, day, time ) { | ||
| 136 | var all_available = true; | ||
| 137 | var speakers = window.event_speakers[el.getAttribute('event_id')]; | ||
| 138 | |||
| 139 | if (!speakers) | ||
| 140 | return false; | ||
| 141 | |||
| 142 | try { | ||
| 143 | var event_times = /(\d\d)(\d\d)/.exec(time); | ||
| 144 | var event_duration = el.getAttribute('fullnarp-duration') / 60; | ||
| 145 | var event_start = Number(event_times[1]); | ||
| 146 | if (event_start < 9) { event_start = 24 + event_start; } | ||
| 147 | |||
| 148 | var event_start_date = new Date("2024-12-27T00:00:00+01:00"); | ||
| 149 | event_start_date.setTime(event_start_date.getTime() + 60000 * ( 24 * 60 * (day - 1) + event_start * 60 + 1 * Number(event_times[2])) ); | ||
| 150 | var event_end_date = new Date(); | ||
| 151 | event_end_date.setTime(event_start_date.getTime() + 60000 * event_duration); | ||
| 152 | } catch (error) { | ||
| 153 | return false; | ||
| 154 | } | ||
| 155 | |||
| 156 | /* Check availability of all speakers */ | ||
| 157 | for (speaker of speakers) { | ||
| 158 | /* Now if at least one day is set, each missing | ||
| 159 | day means unavailable, */ | ||
| 160 | var have_avails = false, unavail = true; | ||
| 161 | for (avail of speaker.availabilities) { | ||
| 162 | have_avails = true; | ||
| 163 | |||
| 164 | var availtime_start = new Date(avail.start); | ||
| 165 | var availtime_end = new Date(avail.end); | ||
| 166 | |||
| 167 | if( event_start_date >= availtime_start && event_end_date <= availtime_end ) | ||
| 168 | unavail = false; | ||
| 169 | } | ||
| 170 | |||
| 171 | /* If at least one speaker is unavail, check fails */ | ||
| 172 | if( have_avails && unavail ) { | ||
| 173 | all_available = false; | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | return all_available; | ||
| 179 | } | ||
| 180 | |||
| 181 | /* Needs to be done for each moved and all previously conflicting events */ | ||
| 182 | function mark_conflict(el) { | ||
| 183 | var event_start = time_to_mins(el.getAttribute('fullnarp-time')); | ||
| 184 | var event_duration = el.getAttribute('fullnarp-duration') / 60; | ||
| 185 | |||
| 186 | var conflict = false; | ||
| 187 | |||
| 188 | /* We do only need to check events in the same room at the same day for conflicts */ | ||
| 189 | document.querySelectorAll('.event.day_'+el.getAttribute('fullnarp-day')+'.room'+el.getAttribute('fullnarp-room')).forEach( function(check_el) { | ||
| 190 | |||
| 191 | if( el.getAttribute('event_id') == check_el.getAttribute('event_id') ) { return true; } | ||
| 192 | |||
| 193 | var check_start = time_to_mins(check_el.getAttribute('fullnarp-time')); | ||
| 194 | var check_duration = check_el.getAttribute('fullnarp-duration') / 60; | ||
| 195 | |||
| 196 | if( check_start < event_start + event_duration && | ||
| 197 | event_start < check_start + check_duration ) { | ||
| 198 | check_el.classList.add('conflict'); | ||
| 199 | conflict = true; | ||
| 200 | } | ||
| 201 | }); | ||
| 202 | el.classList.toggle('conflict', conflict); | ||
| 203 | } | ||
| 204 | |||
| 205 | /* remove day, room and time from an event */ | ||
| 206 | function remove_event(event_id) { | ||
| 207 | var el = document.getElementById(event_id); | ||
| 208 | el.classList.add('pending'); | ||
| 209 | if (ws && ws.readyState === WebSocket.OPEN) { | ||
| 210 | var message = { | ||
| 211 | lastupdate: window.lastupdate, | ||
| 212 | removeevent: event_id | ||
| 213 | } | ||
| 214 | ws.send(JSON.stringify(message)); | ||
| 215 | console.log('Sent:', message); | ||
| 216 | } else { | ||
| 217 | el.removeClass('pending'); | ||
| 218 | el.addClass('failed'); | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | /* provide time OR hour + minute, time overrides */ | ||
| 223 | function set_all_attributes(event_id, day, room, time, from_server) { | ||
| 224 | var el = document.getElementById(event_id); | ||
| 225 | el.className = el.className.replace( /\btime_\S+ ?/g, '').replace( /\broom\S+ ?/g, '').replace( /\bday_\S+ ?/g, ''); | ||
| 226 | el.classList.add( time, day, room ); | ||
| 227 | el.setAttribute('fullnarp-day', day.replace('day_','')); | ||
| 228 | el.setAttribute('fullnarp-time', time.replace('time_','')); | ||
| 229 | el.setAttribute('fullnarp-room', room.replace('room','')); | ||
| 230 | el.classList.remove('pending'); | ||
| 231 | |||
| 232 | if (!from_server) { | ||
| 233 | el.classList.add('pending'); | ||
| 234 | if (ws && ws.readyState === WebSocket.OPEN) { | ||
| 235 | var message = { | ||
| 236 | lastupdate: window.lastupdate, | ||
| 237 | setevent: event_id, | ||
| 238 | day: el.getAttribute('fullnarp-day'), | ||
| 239 | room: el.getAttribute('fullnarp-room'), | ||
| 240 | time: el.getAttribute('fullnarp-time') | ||
| 241 | } | ||
| 242 | ws.send(JSON.stringify(message)); | ||
| 243 | console.log('Sent:', message); | ||
| 244 | } else { | ||
| 245 | el.classList.remove('pending'); | ||
| 246 | el.classList.add('failed'); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | /* When moving an element, conflict may have been resolved ... */ | ||
| 251 | document.querySelectorAll('.conflict').forEach(elem => mark_conflict(elem)); | ||
| 252 | |||
| 253 | /* ... or introduced */ | ||
| 254 | mark_conflict(el); | ||
| 255 | mark_avail(el); | ||
| 256 | if (document.body.classList.contains('showcorrweb')) { | ||
| 257 | document.querySelectorAll('.corrweb').forEach(elem => elem.remove()); | ||
| 258 | document.querySelectorAll('.event').forEach(elem => show_all_correlates(elem)); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | function signalFullnarpConnect(state) { | ||
| 263 | document.body.classList.remove('fullnarp-connected', 'fullnarp-connecting', 'fullnarp-disconnected'); | ||
| 264 | document.body.classList.add(state); | ||
| 265 | } | ||
| 266 | |||
| 267 | function getFullnarpData(lastupdate) { | ||
| 268 | signalFullnarpConnect('fullnarp-connecting'); | ||
| 269 | ws = new WebSocket('wss://erdgeist.org/38C3/halfnarp/fullnarp-ws'); | ||
| 270 | |||
| 271 | ws.onopen = () => { | ||
| 272 | console.log('Connected to WebSocket server'); | ||
| 273 | //stateElement.textContent = 'Connected'; | ||
| 274 | }; | ||
| 275 | |||
| 276 | ws.onmessage = (event) => { | ||
| 277 | signalFullnarpConnect('fullnarp-connected'); | ||
| 278 | const data = JSON.parse(event.data); | ||
| 279 | console.log('Received:', data); | ||
| 280 | for (const [eventid, event_new] of Object.entries(data.data)) { | ||
| 281 | if (document.getElementById(eventid)) | ||
| 282 | set_all_attributes(eventid, 'day_'+event_new['day'], 'room'+event_new['room'], 'time_'+event_new['time'], true ) | ||
| 283 | } | ||
| 284 | window.lastupdate = data.current_version; | ||
| 285 | current_version_string = ('00000'+data.current_version).slice(-5); | ||
| 286 | document.querySelector('.version').innerHTML = '<a href="https://erdgeist.org/38C3/halfnarp/versions/fullnarp_'+current_version_string+'.json">Version: '+data.current_version+'</a>'; | ||
| 287 | }; | ||
| 288 | |||
| 289 | ws.onerror = (error) => { | ||
| 290 | console.error('WebSocket error:', error); | ||
| 291 | }; | ||
| 292 | |||
| 293 | ws.onclose = () => { | ||
| 294 | console.log('Disconnected from WebSocket server'); | ||
| 295 | signalFullnarpConnect('fullnarp-disconnected'); | ||
| 296 | // stateElement.textContent = 'Disconnected'; | ||
| 297 | // Optionally attempt to reconnect after a delay | ||
| 298 | setTimeout(getFullnarpData, 5000); | ||
| 299 | }; | ||
| 300 | }; | ||
| 301 | |||
| 302 | function do_the_fullnarp() { | ||
| 303 | var halfnarpAPI = 'talks_38C3.json'; | ||
| 304 | var fullnarpAPI = 'votes_38c3.json'; | ||
| 305 | var allrooms = ['1','2','3'] | ||
| 306 | var allminutes = ['00','05','10','15','20','25','30','35','40','45','50','55'] | ||
| 307 | var allhours = ['10','11','12','13','14','15','16','17','18','19','20','21','22','23','00','01','02']; | ||
| 308 | var alldays = ['1','2','3','4']; | ||
| 309 | var voted = 0; | ||
| 310 | window.event_speakers = {}; | ||
| 311 | window.votes = {}; | ||
| 312 | |||
| 313 | /* Add handler for type ahead search input field */ | ||
| 314 | var filter = document.getElementById('filter'); | ||
| 315 | |||
| 316 | filter.onpaste = filter.oncut = filter.onkeypress = filter.onkeydown = filter.onkeyup = function() { | ||
| 317 | var cnt = this.value.toLowerCase(); | ||
| 318 | if( cnt.length ) | ||
| 319 | document.querySelectorAll('.event').forEach(elem => elem.style.display = (elem.textContent || elem.innerText || '').toLowerCase().includes(cnt) ? "block" : "none" ); | ||
| 320 | else | ||
| 321 | document.querySelectorAll('.event').forEach(elem => elem.style.display = "block"); | ||
| 322 | }; | ||
| 323 | |||
| 324 | /* Add click handlers for event div sizers */ | ||
| 325 | document.querySelector('.vsmallboxes').onclick = function() { | ||
| 326 | document.body.classList.remove('size-medium', 'size-large'); | ||
| 327 | document.body.classList.add('size-small'); | ||
| 328 | }; | ||
| 329 | |||
| 330 | document.querySelector('.vmediumboxes').onclick = function() { | ||
| 331 | document.body.classList.remove('size-small', 'size-large'); | ||
| 332 | document.body.classList.add('size-medium'); | ||
| 333 | }; | ||
| 334 | |||
| 335 | document.querySelector('.vlargeboxes').onclick = function() { | ||
| 336 | document.body.classList.remove('size-small', 'size-medium'); | ||
| 337 | document.body.classList.add('size-large'); | ||
| 338 | }; | ||
| 339 | |||
| 340 | /* Add callbacks for view selector */ | ||
| 341 | document.querySelector('.vlist').onclick = function() { toggle_grid(0); }; | ||
| 342 | document.querySelector('.vday1').onclick = function() { toggle_grid(1); }; | ||
| 343 | document.querySelector('.vday2').onclick = function() { toggle_grid(2); }; | ||
| 344 | document.querySelector('.vday3').onclick = function() { toggle_grid(3); }; | ||
| 345 | document.querySelector('.vday4').onclick = function() { toggle_grid(4); }; | ||
| 346 | document.querySelector('.vdays').onclick = function() { toggle_grid(5); }; | ||
| 347 | |||
| 348 | document.querySelector('.vleft').onclick = function() { document.body.classList.toggle('still-left'); }; | ||
| 349 | document.querySelector('.vhalf').onclick = function() { document.body.classList.toggle('absolute'); }; | ||
| 350 | document.querySelector('.vcorr').onclick = display_correlation; | ||
| 351 | document.querySelector('.vlang').onclick = function() { document.body.classList.toggle('languages'); }; | ||
| 352 | document.querySelector('.vtrack').onclick = function() { document.body.classList.toggle('all-tracks'); }; | ||
| 353 | document.querySelector('.vweb').onclick = function() { | ||
| 354 | if (document.body.classList.contains('showcorrweb')) | ||
| 355 | document.querySelectorAll('.corrweb').forEach(elem => elem.remove()); | ||
| 356 | else | ||
| 357 | document.querySelectorAll('.event').forEach(elem => show_all_correlates(elem)); | ||
| 358 | document.body.classList.toggle('showcorrweb'); | ||
| 359 | }; | ||
| 360 | |||
| 361 | /* Make the trashbin a drop target */ | ||
| 362 | var trash = document.querySelector('.trashbin'); | ||
| 363 | trash.setAttribute('dropzone','move'); | ||
| 364 | trash.ondragover = function (event) { | ||
| 365 | event.preventDefault(); // allows us to drop | ||
| 366 | this.classList.add('over'); | ||
| 367 | return false; | ||
| 368 | }; | ||
| 369 | trash.ondragleave = function (event) { this.classList.remove('over'); }; | ||
| 370 | trash.ondrop = function (event) { | ||
| 371 | event.stopPropagation(); | ||
| 372 | set_all_attributes(event.dataTransfer.getData('Text'), 'day_0', 'room_0', 'time_0000', false); | ||
| 373 | return false; | ||
| 374 | }; | ||
| 375 | |||
| 376 | /* Create hour guides */ | ||
| 377 | for (hour of allhours) { | ||
| 378 | var elem = document.createElement('hr'); | ||
| 379 | elem.classList.add('guide', 'time_' + hour + '00'); | ||
| 380 | document.body.append(elem); | ||
| 381 | elem = document.createElement('div'); | ||
| 382 | elem.textContent = hour + '00'; | ||
| 383 | elem.classList.add('guide', 'time_' + hour + '00'); | ||
| 384 | document.body.append(elem); | ||
| 385 | |||
| 386 | for (minute of allminutes) { | ||
| 387 | for (room of allrooms) { | ||
| 388 | for (day of alldays) { | ||
| 389 | elem = document.createElement('div'); | ||
| 390 | elem.classList.add('grid', 'time_' + hour + minute, 'day_' + day, 'room' + room ); | ||
| 391 | elem.textContent = minute; | ||
| 392 | elem.setAttribute('dropzone', 'move'); | ||
| 393 | elem.setAttribute('hour', '' + hour + minute); | ||
| 394 | elem.setAttribute('day', '' + day); | ||
| 395 | elem.setAttribute('room', room); | ||
| 396 | document.body.append(elem); | ||
| 397 | elem.ondragover = function (event) { | ||
| 398 | event.preventDefault(); // allows us to drop | ||
| 399 | this.classList.add('over'); | ||
| 400 | return false; | ||
| 401 | } | ||
| 402 | elem.ondragleave = function (event) { this.classList.remove('over'); } | ||
| 403 | elem.ondrop = function (event) { | ||
| 404 | event.stopPropagation(); | ||
| 405 | set_all_attributes(event.dataTransfer.getData('Text'), 'day_' + this.getAttribute('day'), 'room' + this.getAttribute('room'), 'time_' + this.getAttribute('hour'), false ); | ||
| 406 | /* Don't go back to list view on successful drop */ | ||
| 407 | document.body.classList.remove('was-list'); | ||
| 408 | return false; | ||
| 409 | } | ||
| 410 | } | ||
| 411 | } | ||
| 412 | } | ||
| 413 | } | ||
| 414 | |||
| 415 | /* Fetch list of votes to display */ | ||
| 416 | fetch(`${fullnarpAPI}?format=json`) | ||
| 417 | .then(response => { | ||
| 418 | if (!response.ok) { | ||
| 419 | throw new Error(`HTTP error when fetching fullnarp data! status: ${response.status}`); | ||
| 420 | } | ||
| 421 | return response.json(); | ||
| 422 | }).then(data => { | ||
| 423 | window.raw_votes = data; | ||
| 424 | for (eventidlist of data) | ||
| 425 | for (eventid of eventidlist) | ||
| 426 | window.votes[eventid] = 1 + (window.votes[eventid] || 0 ); | ||
| 427 | if( ++voted == 2 ) { | ||
| 428 | window.lastupdate = 0; | ||
| 429 | distribute_votes(); | ||
| 430 | getFullnarpData(0); | ||
| 431 | } | ||
| 432 | }).catch(error => { | ||
| 433 | console.error('Fetch error:', error); | ||
| 434 | }); | ||
| 435 | |||
| 436 | |||
| 437 | /* Fetch list of lectures to display */ | ||
| 438 | fetch(`${halfnarpAPI}?format=json`) | ||
| 439 | .then(response => { | ||
| 440 | if (!response.ok) { | ||
| 441 | throw new Error(`HTTP error when fetching halfnarp data! status: ${response.status}`); | ||
| 442 | } | ||
| 443 | return response.json(); | ||
| 444 | }).then(data => { | ||
| 445 | for (item of data) { | ||
| 446 | /* Take copy of hidden event template div and select them, if they're in | ||
| 447 | list of previous prereferences */ | ||
| 448 | var t = document.getElementById('template').cloneNode(true); | ||
| 449 | var event_id = item.event_id.toString(); | ||
| 450 | t.classList.add('event', 'duration_' + item.duration, 'lang_' + (item.language || 'en')); | ||
| 451 | t.setAttribute('event_id', event_id); | ||
| 452 | t.setAttribute('id', 'event_' + event_id) | ||
| 453 | t.setAttribute( 'fullnarp-duration', item.duration); | ||
| 454 | |||
| 455 | /* Sort textual info into event div */ | ||
| 456 | t.querySelector('.title').textContent = item.title; | ||
| 457 | t.querySelector('.speakers').textContent = item.speaker_names; | ||
| 458 | t.querySelector('.abstract').append(item.abstract); | ||
| 459 | |||
| 460 | /* Store speakers and their availabilities */ | ||
| 461 | window.event_speakers[event_id] = item.speakers; | ||
| 462 | for (speaker of item.speakers) { | ||
| 463 | var have_avails = false; | ||
| 464 | if (!speaker.availabilities) | ||
| 465 | console.log("Foo"); | ||
| 466 | for (avail of speaker.availabilities) { | ||
| 467 | if (avail.id ) { | ||
| 468 | have_avails = true; | ||
| 469 | break; | ||
| 470 | } | ||
| 471 | } | ||
| 472 | if (!have_avails) | ||
| 473 | t.classList.add('has_unavailable_speaker'); | ||
| 474 | } | ||
| 475 | |||
| 476 | t.setAttribute('draggable', 'true'); | ||
| 477 | |||
| 478 | /* Make the event drag&droppable */ | ||
| 479 | t.ondragstart = function( event, ui ) { | ||
| 480 | event.stopPropagation(); | ||
| 481 | |||
| 482 | event.dataTransfer.setData('text/plain', this.id ); | ||
| 483 | event.dataTransfer.dropEffect = 'move'; | ||
| 484 | event.dataTransfer.effectAllowed = 'move'; | ||
| 485 | event.target.classList.add('is-dragged'); | ||
| 486 | } | ||
| 487 | |||
| 488 | /* While dragging make source element small enough to allow | ||
| 489 | dropping below its original area */ | ||
| 490 | t.ondrag = function( event, ui ) { | ||
| 491 | event.stopPropagation(); | ||
| 492 | event.target.classList.add('is-dragged'); | ||
| 493 | |||
| 494 | /* When drag starts in list view, switch to calendar view */ | ||
| 495 | if( document.body.classList.contains('in-list') ) { | ||
| 496 | toggle_grid(5); | ||
| 497 | document.body.classList.add('was-list'); | ||
| 498 | } | ||
| 499 | if( document.body.classList.contains('in-drag') ) | ||
| 500 | return; | ||
| 501 | |||
| 502 | document.body.classList.add('in-drag'); | ||
| 503 | /* mark all possible drop points regarding to availability */ | ||
| 504 | for (hour of allhours) | ||
| 505 | for (minute of allminutes) | ||
| 506 | for (day of alldays) | ||
| 507 | document.querySelectorAll('.grid.day_'+day+'.time_'+hour+minute).forEach(elem => elem.classList.toggle('possible', check_avail(event.target, day, hour+minute))); | ||
| 508 | |||
| 509 | } | ||
| 510 | |||
| 511 | t.ondragend = function( event, ui ) { | ||
| 512 | event.stopPropagation(); | ||
| 513 | |||
| 514 | /* We removed in-list and the drop did not succeed. Go back to list view */ | ||
| 515 | if (document.body.classList.contains('was-list')) | ||
| 516 | toggle_grid(0); | ||
| 517 | |||
| 518 | document.querySelectorAll('.over').forEach(elem => elem.classList.remove('over')); | ||
| 519 | document.querySelectorAll('.is-dragged').forEach(elem => elem.classList.remove('id-dragged')); | ||
| 520 | document.querySelectorAll('.possible').forEach(elem => elem.classList.remove('possible')); | ||
| 521 | document.body.classList.remove('in-drag', 'was-list'); | ||
| 522 | } | ||
| 523 | |||
| 524 | /* start_time: 2014-12-29T21:15:00+01:00" */ | ||
| 525 | var start_time = new Date(item.start_time); | ||
| 526 | |||
| 527 | var day = start_time.getDate()-26; | ||
| 528 | var hour = start_time.getHours(); | ||
| 529 | var mins = start_time.getMinutes(); | ||
| 530 | |||
| 531 | /* After midnight: sort into yesterday */ | ||
| 532 | if( hour < 9 ) | ||
| 533 | day--; | ||
| 534 | |||
| 535 | /* Fix up room for 38c3 */ | ||
| 536 | room = (item.room_id || 'room_unknown').toString().replace('471','room1').replace('472','room2').replace('473','room3'); | ||
| 537 | |||
| 538 | /* Apply attributes to sort events into calendar */ | ||
| 539 | t.classList.add(room, 'day_' + day, 'time_' + (hour<10?'0':'') + hour + (mins<10?'0':'') + mins); | ||
| 540 | t.setAttribute('fullnarp-day', day); | ||
| 541 | t.setAttribute('fullnarp-time', (hour<10?'0':'') + hour + (mins<10?'0':'') + mins ); | ||
| 542 | t.setAttribute('fullnarp-room', room.replace('room','')); | ||
| 543 | |||
| 544 | mark_avail(t); | ||
| 545 | |||
| 546 | t.onclick = function(event) { | ||
| 547 | _this = this; | ||
| 548 | document.body.classList.remove('in-drag'); | ||
| 549 | if (document.body.classList.contains('correlate')) { | ||
| 550 | document.querySelectorAll('.selected').forEach(elem => elem.classList.remove('selected')); | ||
| 551 | document.querySelectorAll('.event').forEach(elem => mark_correlation(elem, _this)); | ||
| 552 | } | ||
| 553 | _this.classList.toggle('selected'); | ||
| 554 | document.querySelectorAll('.info').forEach(elem => elem.classList.add('hidden')); | ||
| 555 | event.stopPropagation(); | ||
| 556 | } | ||
| 557 | |||
| 558 | /* Put new event into DOM tree. Track defaults to 'Other' */ | ||
| 559 | var track = item.track_id.toString(); | ||
| 560 | t.classList.add('track_' + track ); | ||
| 561 | var d = document.getElementById(track); | ||
| 562 | if (!d) | ||
| 563 | d = document.querySelector('#Other'); | ||
| 564 | d.append(t); | ||
| 565 | }; | ||
| 566 | |||
| 567 | if( ++voted == 2 ) { | ||
| 568 | window.lastupdate = 0; | ||
| 569 | distribute_votes(); | ||
| 570 | getFullnarpData(0); | ||
| 571 | } | ||
| 572 | }).catch(error => { | ||
| 573 | console.error('Fetch error:', error); | ||
| 574 | }); | ||
| 575 | |||
| 576 | document.onkeypress = function(e) { | ||
| 577 | document.body.classList.remove('in-drag'); | ||
| 578 | if( document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA' ) | ||
| 579 | return; | ||
| 580 | switch( e.charCode ) { | ||
| 581 | case 115: case 83: /* s */ | ||
| 582 | var selected = document.querySelectorAll('.selected'); | ||
| 583 | if( selected.length != 2 ) return; | ||
| 584 | |||
| 585 | var id0 = selected[0].getAttribute('id'); | ||
| 586 | var day0 = selected[0].getAttribute('fullnarp-day'); | ||
| 587 | var hour0 = selected[0].getAttribute('fullnarp-time'); | ||
| 588 | var room0 = selected[0].getAttribute('fullnarp-room'); | ||
| 589 | |||
| 590 | var id1 = selected[1].getAttribute('id'); | ||
| 591 | var day1 = selected[1].getAttribute('fullnarp-day'); | ||
| 592 | var hour1 = selected[1].getAttribute('fullnarp-time'); | ||
| 593 | var room1 = selected[1].getAttribute('fullnarp-room'); | ||
| 594 | |||
| 595 | set_all_attributes(id0, day1, room1, hour1, false); | ||
| 596 | set_all_attributes(id1, day0, room0, hour0, false); | ||
| 597 | |||
| 598 | break; | ||
| 599 | case 48: case 94: /* 0 */ | ||
| 600 | toggle_grid(5); | ||
| 601 | break; | ||
| 602 | case 49: case 50: case 51: case 52: /* 1-4 */ | ||
| 603 | toggle_grid(e.charCode-48); | ||
| 604 | break; | ||
| 605 | case 76: case 108: /* l */ | ||
| 606 | toggle_grid(0); | ||
| 607 | break; | ||
| 608 | case 68: case 100: /* d */ | ||
| 609 | toggle_grid(5); | ||
| 610 | break; | ||
| 611 | case 73: case 105: /* i */ | ||
| 612 | document.body.classList.remove('all-tracks'); | ||
| 613 | document.body.classList.toggle('languages'); | ||
| 614 | break; | ||
| 615 | case 65: case 97: /* a */ | ||
| 616 | case 72: case 104: /* h */ | ||
| 617 | document.body.classList.toggle('absolute'); | ||
| 618 | break; | ||
| 619 | case 81: case 113: /* q */ | ||
| 620 | document.querySelectorAll('.selected').forEach(elem => elem.classList.remove('selected')); | ||
| 621 | break; | ||
| 622 | case 84: case 116: /* t */ | ||
| 623 | document.body.classList.remove('languages'); | ||
| 624 | document.body.classList.toggle('all-tracks'); | ||
| 625 | break; | ||
| 626 | case 85: case 117: /* u */ | ||
| 627 | document.body.classList.toggle('still-left'); | ||
| 628 | break; | ||
| 629 | case 67: case 99: /* c */ | ||
| 630 | display_correlation(); | ||
| 631 | break; | ||
| 632 | case 87: case 119: /* w */ | ||
| 633 | if (document.body.classList.contains('showcorrweb')) | ||
| 634 | document.querySelectorAll('.corrweb').forEach(elem => elem.remove()); | ||
| 635 | else | ||
| 636 | document.querySelectorAll('.event').forEach(elem => show_all_correlates(elem)); | ||
| 637 | document.body.classList.toggle('showcorrweb'); | ||
| 638 | break; | ||
| 639 | } | ||
| 640 | }; | ||
| 641 | } | ||
