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