jekyll-theme-conference 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,718 @@
1
+ window.conference.live = (() => {
2
+ let config;
3
+ let lang;
4
+
5
+ let data;
6
+
7
+ let confStart;
8
+ let confEnd;
9
+ let confDur;
10
+
11
+ let stream;
12
+ let streamPause;
13
+ let streamPrepend;
14
+ let streamExtend;
15
+
16
+ let demo;
17
+ let demoStart;
18
+ let demoEnd;
19
+ let demoDur;
20
+ let demoPause;
21
+
22
+ let freezeTime = false;
23
+ let timeFrozen = 0;
24
+ let timeOffset = 0;
25
+
26
+ let liveTimer;
27
+ let streamVideoTimer;
28
+ let streamInfoTimer;
29
+
30
+ const loadData = () => {
31
+ // Load schedule
32
+ const request = new Request(window.conference.config.baseurl + '/assets/js/data.json');
33
+
34
+ fetch(request)
35
+ .then(response =>
36
+ response.json()
37
+ )
38
+ .then(d => {
39
+ data = d;
40
+ })
41
+ .catch((error) => {
42
+ console.log(error);
43
+ });
44
+ };
45
+
46
+ const getData = () => {
47
+ // Return data
48
+ return data;
49
+ };
50
+
51
+ const mod = (n, m) => {
52
+ // Absolute modulo
53
+ return ((n % m) + m) % m;
54
+ };
55
+
56
+ const timeNow = () => {
57
+ // Current timestamp in seconds
58
+ return Math.floor(Date.now() / 1000);
59
+ };
60
+
61
+ const timeCont = () => {
62
+ // Continuous time (respecting previous pauses)
63
+ return timeNow() - timeOffset;
64
+ };
65
+
66
+ const timeCycle = () => {
67
+ // Cyclic timestamp in seconds
68
+ const actTime = timeNow();
69
+ const relTime = mod(actTime, demoDur + 2*demoPause) / (demoDur + 2*demoPause);
70
+ const cycleTime = mod((demoEnd - demoStart) * relTime - timeOffset, (demoEnd - demoStart)) + demoStart;
71
+ return cycleTime;
72
+ };
73
+
74
+ const time = () => {
75
+ // Return app time
76
+ if (freezeTime) {
77
+ return timeFrozen;
78
+ }
79
+ else if (demo) {
80
+ return timeCycle();
81
+ }
82
+ else {
83
+ return timeCont();
84
+ }
85
+ };
86
+
87
+ const pauseTime = () => {
88
+ // Pause app time
89
+ if (!freezeTime) {
90
+ timeFrozen = time();
91
+ freezeTime = true;
92
+
93
+ stopUpdate();
94
+ }
95
+ };
96
+
97
+ const continueTime = () => {
98
+ // Continue app time
99
+ if (freezeTime) {
100
+ freezeTime = false;
101
+ timeOffset += time() - timeFrozen;
102
+ startUpdate();
103
+ }
104
+ };
105
+
106
+ const resetTime = () => {
107
+ // Reset app time
108
+ timeOffset = 0;
109
+ freezeTime = false;
110
+
111
+ startUpdate();
112
+ };
113
+
114
+ const setTime = (newTime, newDay) => {
115
+ // Set and pause app time
116
+ pauseTime();
117
+
118
+ let dayIdx;
119
+ if (!newDay) {
120
+ dayIdx = 0;
121
+ }
122
+ else if (Number.isInteger(newDay)) {
123
+ dayIdx = newDay-1;
124
+ }
125
+ else if (/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(newDay)) {
126
+ dayIdx = data.days.find(o => o.name === newDay);
127
+ }
128
+ else {
129
+ dayIdx = data.days.find(o => o.name === newDay);
130
+ }
131
+ const newDate = data.days[dayIdx].date;
132
+
133
+ let d = new Date(newDate);
134
+ newTime = newTime.split(':');
135
+ d.setHours(newTime[0], newTime[1]);
136
+
137
+ timeFrozen = Math.floor(d.getTime() / 1000);
138
+
139
+ update();
140
+ };
141
+
142
+ const getTime = () => {
143
+ // Return app time as string
144
+ const tConvert = time();
145
+
146
+ const d = new Date(tConvert * 1000);
147
+ const dStr = d.toISOString().slice(0,10);
148
+ const h = d.getHours();
149
+ const m = d.getMinutes();
150
+
151
+ return dStr +" "+ h +":"+ (m < 10 ? "0" : "") + m;
152
+ };
153
+
154
+ const timeUnit = () => {
155
+ // App time refresh rate
156
+ if (demo) {
157
+ return 0.1;
158
+ }
159
+ else {
160
+ return 60;
161
+ }
162
+ };
163
+
164
+ const delayStart = (startTime) => {
165
+ // Seconds until given startTime occurs
166
+ const tNow = time();
167
+ const tUnit = timeUnit();
168
+
169
+ if (demo) {
170
+ // Convert virtual duration to real duration
171
+ return mod(startTime - tNow, demoEnd - demoStart) / (demoEnd - demoStart) * (demoDur + 2*demoPause);
172
+ }
173
+ else {
174
+ if (startTime > tNow) {
175
+ return startTime - tNow;
176
+ }
177
+ else {
178
+ // Start on the unit
179
+ return (tUnit - (tNow % tUnit));
180
+ }
181
+ }
182
+ };
183
+
184
+ const model = {
185
+ set demo(value) {
186
+ demo = value;
187
+ resetTime();
188
+ },
189
+ get demo() {
190
+ return demo;
191
+ }
192
+ };
193
+
194
+ const updateLive = () => {
195
+ // Update status all live elements in DOM
196
+ const tNow = time();
197
+ const liveShow = document.getElementsByClassName('live-show');
198
+ const liveHide = document.getElementsByClassName('live-hide');
199
+ const liveTime = document.getElementsByClassName('live-time');
200
+ const livePast = document.getElementsByClassName('live-past');
201
+
202
+ // Show elements for a given period
203
+ for (let i = 0; i < liveShow.length; i++) {
204
+ const tStarts = liveShow[i].dataset.start.split(',');
205
+ const tEnds = liveShow[i].dataset.end.split(',');
206
+
207
+ for (let k = 0; k < tStarts.length; k++) {
208
+ if (tNow >= tStarts[k] && tNow < tEnds[k]) {
209
+ // Show when active
210
+ liveShow[i].classList.remove('d-none');
211
+ break;
212
+ }
213
+ else if (!liveShow[i].classList.contains('d-none')) {
214
+ // Hide otherwise
215
+ liveShow[i].classList.add('d-none');
216
+ }
217
+ }
218
+ }
219
+
220
+ // Hide elements for a given period
221
+ for (let i = 0; i < liveHide.length; i++) {
222
+ const tStarts = liveHide[i].dataset.start.split(',');
223
+ const tEnds = liveHide[i].dataset.end.split(',');
224
+
225
+ for (let k = 0; k < tStarts.length; k++) {
226
+ if (tNow >= tStarts[k] && tNow < tEnds[k]) {
227
+ // Hide when active
228
+ if (!liveHide[i].classList.contains('d-none')) {
229
+ liveHide[i].classList.add('d-none');
230
+ break;
231
+ }
232
+ }
233
+ else {
234
+ // Show otherwise
235
+ liveHide[i].classList.remove('d-none');
236
+ }
237
+ }
238
+ }
239
+
240
+ // Update duration string for given elements
241
+ for (let i = 0; i < liveTime.length; i++) {
242
+ const t = liveTime[i].dataset.time;
243
+ if (typeof t == "undefined") {
244
+ break;
245
+ }
246
+ let tRel = tNow - t;
247
+
248
+ let tStr;
249
+ if (tRel >= -60 && tRel < 0) {
250
+ tStr = lang.time.soon;
251
+ }
252
+ else if (tRel >= 0 && tRel < 60) {
253
+ tStr = lang.time.now;
254
+ }
255
+ else {
256
+ if (tRel < 0) {
257
+ tStr = lang.time.in;
258
+ }
259
+ else {
260
+ tStr = lang.time.since;
261
+ }
262
+ tRel = Math.abs(tRel);
263
+
264
+ let dWeeks = Math.floor(tRel / (7*24*60*60));
265
+ let dDays = Math.floor(tRel / (24*60*60));
266
+ let dHours = Math.floor(tRel / (60*60));
267
+ let dMins = Math.floor(tRel / (60));
268
+ if (dWeeks > 4) {
269
+ break;
270
+ }
271
+ else if (dWeeks > 1) {
272
+ tStr += dWeeks +' '+ lang.time.weeks;
273
+ }
274
+ else if (dWeeks == 1) {
275
+ tStr += '1 '+ lang.time.week;
276
+ }
277
+ else if (dDays > 1) {
278
+ tStr += dDays +' '+ lang.time.days;
279
+ }
280
+ else if (dDays == 1) {
281
+ tStr += '1 '+ lang.time.day;
282
+ }
283
+ else if (dHours > 1) {
284
+ tStr += dHours +' '+ lang.time.hours;
285
+ }
286
+ else if (dHours == 1) {
287
+ tStr += '1 '+ lang.time.hour;
288
+ }
289
+ else if (dMins > 1) {
290
+ tStr += dMins +' '+ lang.time.minutes;
291
+ }
292
+ else {
293
+ tStr += '1 '+ lang.time.minute;
294
+ }
295
+ }
296
+
297
+ liveTime[i].innerHTML = tStr;
298
+ }
299
+
300
+ // Disable elements for a given period
301
+ for (let i = 0; i < livePast.length; i++) {
302
+ const t = livePast[i].dataset.time;
303
+ if (typeof t == "undefined") {
304
+ break;
305
+ }
306
+ let tRel = tNow - t;
307
+
308
+ if (tRel < 0) {
309
+ // Grey out when in past
310
+ if (!livePast[i].classList.contains('text-secondary')) {
311
+ livePast[i].classList.add('text-secondary');
312
+ }
313
+ }
314
+ else {
315
+ // Show normal otherwise
316
+ livePast[i].classList.remove('text-secondary');
317
+ }
318
+ }
319
+
320
+ // Cancel timer after program is over
321
+ if (tNow > confEnd && !demo) {
322
+ stopUpdateLive();
323
+ }
324
+ };
325
+
326
+ const startUpdateLive = () => {
327
+ // Start update timer to update live elements in DOM
328
+ stopUpdateLive();
329
+ updateLive();
330
+
331
+ if (demo) {
332
+ // Immediate start required since delayStart would wait for next wrap around
333
+ liveTimer = setInterval(updateLive, timeUnit() * 1000);
334
+ }
335
+ else {
336
+ setTimeout(() => {
337
+ liveTimer = setInterval(updateLive, timeUnit() * 1000);
338
+ updateLive();
339
+ }, delayStart(confStart) * 1000);
340
+ }
341
+ };
342
+
343
+ const stopUpdateLive = () => {
344
+ // stopUpdate update timer to update live elements in DOM
345
+ if (typeof liveTimer !== "undefined") {
346
+ clearInterval(liveTimer);
347
+ }
348
+ };
349
+
350
+ let streamModal;
351
+
352
+ const getRoom = (roomName) => {
353
+ // Return room object for given room name
354
+ if (roomName in data.rooms) {
355
+ return data.rooms[roomName];
356
+ }
357
+ else {
358
+ return data.rooms[Object.keys(data.rooms)[0]];
359
+ }
360
+ };
361
+
362
+ const getTalks = (roomName) => {
363
+ if (roomName in data.talks) {
364
+ return data.talks[roomName];
365
+ }
366
+ else {
367
+ return false;
368
+ }
369
+ };
370
+
371
+ const getNextTalk = (roomName) => {
372
+ // Get talk object for next talk in given room
373
+ const timeNow = time();
374
+ const talksHere = getTalks(roomName);
375
+
376
+ if (talksHere) {
377
+ if (timeNow < talksHere[talksHere.length-1].end) {
378
+ for (let i = 0; i < talksHere.length; i++) {
379
+ if (timeNow < talksHere[i].end) {
380
+ return talksHere[i];
381
+ }
382
+ }
383
+ }
384
+ }
385
+ return false;
386
+ };
387
+
388
+ const getNextPause = (roomName) => {
389
+ // Get time object for next pause in given room
390
+ const timeNow = time();
391
+ const talksHere = getTalks(roomName);
392
+
393
+ if (talksHere) {
394
+ if (timeNow < talksHere[talksHere.length-1].end) {
395
+ for (let i = 1; i < talksHere.length; i++) {
396
+ if (timeNow < talksHere[i].start && streamPause*60 <= talksHere[i].start - talksHere[i-1].end) {
397
+ return {
398
+ 'start': talksHere[i-1].end,
399
+ 'end': talksHere[i].start,
400
+ };
401
+ }
402
+ }
403
+ }
404
+ }
405
+ return false;
406
+ };
407
+
408
+ const setStreamIframeContent = (content) => {
409
+ // Set stream modal iframe to show given text
410
+ streamModal.find('iframe').attr('src', '');
411
+ streamModal.find('iframe').addClass('d-none');
412
+ streamModal.find('#stream-placeholder > div').text(content);
413
+ streamModal.find('#stream-placeholder').addClass('d-flex');
414
+ };
415
+
416
+ const setStreamIframeSrc = (href) => {
417
+ // Set stream modal iframe to show given URL
418
+ streamModal.find('iframe').attr('src', href);
419
+ streamModal.find('#stream-placeholder').addClass('d-none').removeClass('d-flex');
420
+ streamModal.find('iframe').removeClass('d-none');
421
+ };
422
+
423
+ const setStreamVideo = (roomName) => {
424
+ // Update stream modal iframe:
425
+ // Show stream with start/pause/end message (for given room) and keep updated
426
+ const timeNow = time();
427
+
428
+ const talksHere = getTalks(roomName);
429
+ let roomStart, roomEnd;
430
+ if (talksHere) {
431
+ roomStart = talksHere[0].start;
432
+ roomEnd = talksHere[talksHere.length-1].end;
433
+ }
434
+ else {
435
+ // If no program for given room, take overall first and last talk
436
+ roomStart = 0;
437
+ roomEnd = 0;
438
+ for (let roomNameTalk in data.talks) {
439
+ talksHere = getTalks(roomNameTalk);
440
+ const crntRoomStart = talksHere[0].start;
441
+ const crntRoomEnd = talksHere[talksHere.length-1].end;
442
+
443
+ if (roomStart == 0 || roomStart > crntRoomStart) {
444
+ roomStart = crntRoomStart;
445
+ }
446
+ if (roomEnd == 0 || roomEnd < crntRoomEnd) {
447
+ roomEnd = crntRoomEnd;
448
+ }
449
+ }
450
+ }
451
+
452
+ if (typeof streamVideoTimer !== "undefined") {
453
+ clearInterval(streamVideoTimer);
454
+ }
455
+
456
+ // Conference not yet started
457
+ if (timeNow < roomStart - streamPrepend*60) {
458
+ setStreamIframeContent(lang.pre_stream);
459
+
460
+ if (!freezeTime) {
461
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomStart - streamPrepend*60) * 1000, roomName);
462
+ }
463
+ }
464
+
465
+ // Conference is over
466
+ else if (timeNow > roomEnd + streamExtend*60) {
467
+ setStreamIframeContent(lang.post_stream);
468
+
469
+ if (!freezeTime && demo) {
470
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomEnd - streamPrepend*60) * 1000, roomName);
471
+ }
472
+ }
473
+
474
+ // Conference ongoing
475
+ else {
476
+ const pauseNext = getNextPause(roomName);
477
+
478
+ // Currently stream is paused
479
+ if (pauseNext && timeNow >= pauseNext.start + streamExtend*60 && timeNow <= pauseNext.end - streamPrepend*60) {
480
+ setStreamIframeContent(lang.pause_stream);
481
+
482
+ if (!freezeTime) {
483
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(pauseNext.end - streamPrepend*60) * 1000, roomName);
484
+ }
485
+ }
486
+ // Currently a talk is active
487
+ else {
488
+ const room = getRoom(roomName);
489
+ setStreamIframeSrc(room.href);
490
+
491
+ if (!freezeTime) {
492
+ if (pauseNext) {
493
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(pauseNext.start + streamExtend*60) * 1000, roomName);
494
+ }
495
+ else {
496
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomEnd + streamExtend*60) * 1000, roomName);
497
+ }
498
+ }
499
+ }
500
+ }
501
+ };
502
+
503
+ const setStreamInfo = (roomName) => {
504
+ // Update stream modal info bar:
505
+ // Show next talk and speaker (for given room) and keep updated
506
+ const timeNow = time();
507
+ const talkNext = getNextTalk(roomName);
508
+
509
+ if (typeof streamInfoTimer !== "undefined") {
510
+ clearInterval(streamInfoTimer);
511
+ }
512
+
513
+ if (talkNext && timeNow >= talkNext.start - streamPause*60) {
514
+ document.getElementById('stream-info').dataset.time = talkNext.start;
515
+ document.getElementById('stream-info-time').dataset.time = talkNext.start;
516
+ updateLive();
517
+
518
+ streamModal.find('#stream-info-color').removeClass((index, className) => {
519
+ return (className.match(/(^|\s)border-soft-\S+/g) || []).join(' ');
520
+ });
521
+ streamModal.find('#stream-info-color').addClass('border-soft-' + talkNext.color);
522
+
523
+ streamModal.find('#stream-info-talk').text(talkNext.name).attr('href', talkNext.href);
524
+
525
+ let speakerStr = '';
526
+ for (let i = 0; i < talkNext.speakers.length; i++) {
527
+ let speaker = data.speakers[talkNext.speakers[i]];
528
+ if (speaker.href == '') {
529
+ speakerStr += speaker.name +', '
530
+ }
531
+ else {
532
+ speakerStr += '<a class="text-reset" href="'+ speaker.href +'">'+ speaker.name +'</a>, ';
533
+ }
534
+ }
535
+ speakerStr = speakerStr.slice(0, -2);
536
+ streamModal.find('#stream-info-speakers').html(speakerStr);
537
+
538
+ if (talkNext.live_links) {
539
+ let linksStr = '';
540
+ for (let i = 0; i < talkNext.live_links.length; i++) {
541
+ const link = talkNext.live_links[i];
542
+
543
+ linksStr += '<a href="' + link.href + '" class="btn btn-light m-1'
544
+ if (link.disabled) {
545
+ linksStr += ' disabled';
546
+ }
547
+ linksStr += '">';
548
+ if (link.icon) {
549
+ linksStr += '<i class="fas fa-' + link.icon + '"></i>&nbsp;';
550
+ }
551
+ linksStr += link.name + '</a>';
552
+ }
553
+ streamModal.find('#stream-info-links').html(linksStr).removeClass('d-none');
554
+ }
555
+ else {
556
+ streamModal.find('#stream-info-links').addClass('d-none');
557
+ }
558
+
559
+ streamModal.find('#stream-info').removeClass('d-none');
560
+
561
+ if (!freezeTime) {
562
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talkNext.end) * 1000, roomName);
563
+ }
564
+ }
565
+ else {
566
+ streamModal.find('#stream-info').addClass('d-none');
567
+
568
+ if (!freezeTime) {
569
+ if (talkNext) {
570
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talkNext.start - streamPause*60) * 1000, roomName);
571
+ }
572
+ else if (demo) {
573
+ let talksHere = getTalks(roomName);
574
+ if (talksHere) {
575
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talksHere[0].start - streamPrepend*60) * 1000, roomName);
576
+ }
577
+ }
578
+ }
579
+ }
580
+ };
581
+
582
+ const setStream = (roomName) => {
583
+ // Update stream modal (iframe and info bar) for given room
584
+ streamModal.find('.modal-footer .btn').removeClass('active');
585
+ streamModal.find('#stream-select').val(0);
586
+
587
+ // Recover room name in case of empty default
588
+ const room = getRoom(roomName);
589
+ roomName = room.name;
590
+
591
+ setStreamVideo(roomName);
592
+ setStreamInfo(roomName);
593
+
594
+ streamModal.find('#stream-button' + room.id).addClass('active');
595
+ streamModal.find('#stream-select').val(room.id);
596
+ };
597
+
598
+ const updateStream = () => {
599
+ // Update stream modal for currently active room button
600
+ if (streamModal.hasClass('show')) {
601
+ let activeButton = streamModal.find('.modal-footer .btn.active');
602
+ let roomName = activeButton.data('room');
603
+
604
+ if (typeof roomName !== "undefined") {
605
+ setStream(roomName);
606
+ }
607
+ }
608
+ };
609
+
610
+ const stopUpdateStream = () => {
611
+ // Stop stream modal update timer
612
+ if (typeof streamVideoTimer !== "undefined") {
613
+ clearInterval(streamVideoTimer);
614
+ }
615
+ if (typeof streamInfoTimer !== "undefined") {
616
+ clearInterval(streamInfoTimer);
617
+ }
618
+ };
619
+
620
+ const hideModal = () => {
621
+ // Close stream modal
622
+ streamModal.find('iframe').attr('src', '');
623
+ streamModal.find('.modal-footer .btn').removeClass('active');
624
+ streamModal.find('#stream-select').selectedIndex = -1;
625
+ };
626
+
627
+ const setupStream = () => {
628
+ // Setup events when modal opens/closes
629
+ streamModal = $('#stream-modal');
630
+
631
+ // configure modal opening buttons
632
+ streamModal.on('show.bs.modal', (event) => {
633
+ let button = $(event.relatedTarget);
634
+ let roomName = button.data('room');
635
+ setStream(roomName);
636
+ });
637
+ streamModal.on('hide.bs.modal', () => {
638
+ hideModal();
639
+ });
640
+
641
+ // configure room selection buttons in modal
642
+ streamModal.find('.modal-footer .btn').on('click', function (event) {
643
+ event.preventDefault();
644
+
645
+ let roomName = $(this).data('room');
646
+ setStream(roomName);
647
+ });
648
+
649
+ // configure room selection menu in modal
650
+ streamModal.find('#stream-select').on('change', function (event) {
651
+ event.preventDefault();
652
+
653
+ let roomName = $(this).children('option:selected').text();
654
+ setStream(roomName);
655
+ });
656
+ };
657
+
658
+ const init = (c, l) => {
659
+ config = c;
660
+ lang = l;
661
+
662
+ confStart = config.time.start;
663
+ confEnd = config.time.end;
664
+ confDur = confEnd - confStart;
665
+
666
+ demo = config.demo.enable;
667
+ demoDur = config.demo.duration;
668
+ demoPause = config.demo.pause;
669
+ demoStart = confStart - confDur/demoDur*demoPause;
670
+ demoEnd = confEnd + confDur/demoDur*demoPause;
671
+
672
+ stream = config.streaming.enable;
673
+ streamPause = config.streaming.pause;
674
+ streamPrepend = config.streaming.prepend;
675
+ streamExtend = config.streaming.extend;
676
+
677
+ loadData();
678
+ startUpdateLive();
679
+ if (stream) {
680
+ setupStream();
681
+ }
682
+ };
683
+
684
+ const update = () => {
685
+ updateLive();
686
+ if (stream) {
687
+ updateStream();
688
+ }
689
+ };
690
+
691
+ const startUpdate = () => {
692
+ startUpdateLive();
693
+ if (stream) {
694
+ updateStream();
695
+ }
696
+ };
697
+
698
+ const stopUpdate = () => {
699
+ stopUpdateLive();
700
+ if (stream) {
701
+ stopUpdateStream();
702
+ }
703
+ };
704
+
705
+ return {
706
+ init: init,
707
+ getData: getData,
708
+
709
+ pauseTime: pauseTime,
710
+ continueTime: continueTime,
711
+ resetTime: resetTime,
712
+ setTime: setTime,
713
+ getTime: getTime,
714
+
715
+ demo: model.demo
716
+ };
717
+
718
+ })();