jekyll-theme-conference 2.5.2 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -50
  3. data/_includes/js/conference-live.js +308 -104
  4. data/_includes/js/conference-modal.js +32 -15
  5. data/_includes/js/conference-program.js +48 -0
  6. data/_includes/js/conference.js +11 -11
  7. data/_includes/js/jquery-3.5.1.min.js +2 -0
  8. data/_includes/partials/checks.html +66 -31
  9. data/_includes/partials/footer.html +6 -22
  10. data/_includes/partials/get_day_hash.html +20 -0
  11. data/_includes/partials/get_day_time.html +19 -0
  12. data/_includes/partials/get_link.html +56 -64
  13. data/_includes/partials/get_link_types.html +14 -14
  14. data/_includes/partials/get_live_timestamps.html +49 -0
  15. data/_includes/partials/get_main_category.html +9 -9
  16. data/_includes/partials/get_talk_time.html +7 -7
  17. data/_includes/partials/get_talk_timestamp.html +4 -0
  18. data/_includes/partials/get_time_pronoun.html +6 -0
  19. data/_includes/partials/header.html +15 -12
  20. data/_includes/partials/info_bar.html +22 -22
  21. data/_includes/partials/list_categories.html +2 -2
  22. data/_includes/partials/list_speakers.html +14 -18
  23. data/_includes/partials/list_sub_categories.html +7 -7
  24. data/_includes/partials/modal_link.html +21 -0
  25. data/_includes/partials/{live-modal.html → modal_live.html} +20 -9
  26. data/_includes/partials/navbar.html +32 -32
  27. data/_includes/partials/navbar_rooms.html +21 -20
  28. data/_includes/partials/{live_button.html → show_live_button.html} +6 -8
  29. data/_includes/partials/show_room.html +5 -5
  30. data/_includes/partials/show_talk.html +3 -3
  31. data/_includes/partials/show_talk_duration.html +1 -1
  32. data/_includes/partials/show_talk_time.html +16 -2
  33. data/_layouts/data.html +91 -0
  34. data/_layouts/home.html +12 -14
  35. data/_layouts/program.html +179 -148
  36. data/_layouts/room.html +42 -38
  37. data/_layouts/speaker.html +41 -39
  38. data/_layouts/talk-overview.html +83 -53
  39. data/_layouts/talk.html +38 -39
  40. data/_sass/conference.scss +40 -16
  41. data/assets/icons/live.svg +33 -57
  42. data/assets/js/data.json +3 -0
  43. data/assets/js/main.js +0 -6
  44. metadata +14 -8
  45. data/_includes/js/jquery-3.2.1.slim.min.js +0 -4
  46. data/_includes/partials/get_conf_time.html +0 -54
  47. data/_includes/partials/get_timestamp.html +0 -4
  48. data/_layouts/delete_hidden.html +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ebb94e878ab7f234d64b2a45c45153438c9e075251d9dda31bc12e670168f91
4
- data.tar.gz: '0980a8034943452466ce56df8e81a76b4e0f1bb9f2d66fa9ad7a8028157cf6b8'
3
+ metadata.gz: 2db4dc4680aa85647500757325334eb2e5e4db739cf56247839bed4ebcbd281c
4
+ data.tar.gz: aecfefef9ad494d5ed8069e5fd1ce504ca1a3c33b21b8a150bb73c9e4bf37f40
5
5
  SHA512:
6
- metadata.gz: dfb07d4cad799e4c8e1d2742966b5dce78d586e2d73b359f6f5b8cffd6aacc7f8b510dbadeec3af407f92cd9801eae12edff14240ec81621e8c86de0abc77e93
7
- data.tar.gz: b2381ce0e3e608130c37f55695e3c27af9980f3097359b6989ecb257bad158611bbfc00b61464835b48a3aadb9144e31077a3e4778ee6f746c953a1f20269bd0
6
+ metadata.gz: 10b0fe0bf595f54070bf936326d5498b7d0c79a8668f61251f5695bff52ea281a69889af57ebed2e3904d4d6f2323ea5447b479078a554c03c633861858e2fc2
7
+ data.tar.gz: ad815822f66b57d2f2a3919ee0f3d5771d6898c427cb275e38259fa2849eeeeb3ab1002ba9d92e95cd7cf766711c08523356e7f75d717776885c33279f4458b9
data/README.md CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  ![Screenshot](screenshot.png)
4
4
 
5
- This is a [Jekyll](http://jekyllrb.com) theme based on [Bootstrap 4](http://getbootstrap.com) which can be used to make a simple, responsive website for a one-day conference or workshop with parallel tracks containing:
5
+ This is a responsive [Jekyll](http://jekyllrb.com) theme based on [Bootstrap 4](http://getbootstrap.com) for conferences. It contains
6
6
 
7
- - program / schedule
8
- - talk and speaker descriptions
9
- - map for directions
7
+ - multiday program / schedule,
8
+ - talk and speaker descriptions,
9
+ - map for directions,
10
+ - realtime live indications during the conference, and
11
+ - supports embedded video streaming or recordings.
10
12
 
11
- All components such as talks, speakers or rooms are represented as collection of files. The schedule is given is defined via a simple structure stored in a [YAML](https://en.wikipedia.org/wiki/YAML) file. There is no need for databases and once generated the website consists only of static files.
12
- The design is easily modifiable and is adapted for mobile uses and printing.
13
+ All components such as talks, speakers or rooms are represented as collection of files. The schedule is given is defined via a simple structure stored in a [YAML](https://en.wikipedia.org/wiki/YAML) file. There is no need for databases and once generated the website consists only of static files. A script and workflows are available for easy import, e.g. of [frab](https://github.com/frab/frab/wiki/Manual#introduction) compatible schedules.
14
+ The design is easily customizable and is adapted for mobile uses and printing.
13
15
 
14
16
  The theme was originally created for the yearly Winterkongress conference of the [Digital Society Switzerland](https://digitale-gesellschaft.ch/). You can see this theme in action here:
15
17
 
@@ -44,11 +46,7 @@ This allows for easier installation and updating as you don't have to manage any
44
46
  theme: jekyll-theme-conference
45
47
  ```
46
48
 
47
- 4. Copy the internationalization file containing the different language strings for this repository to your Jekyll folder:
48
-
49
- - `_data/lang.yml`
50
-
51
- 5. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
49
+ 4. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
52
50
 
53
51
  To update the theme run `bundle update`.
54
52
 
@@ -76,11 +74,7 @@ To install:
76
74
 
77
75
  4. Add `remote_theme: "DigitaleGesellschaft/jekyll-theme-conference@2.0.0"` to your `_config.yml` file. Remove any other `theme:` or `remote_theme:` entry.
78
76
 
79
- 5. Copy the internationalization file containing the different language strings for this repository to your Jekyll folder:
80
-
81
- - `_data/lang.yml`
82
-
83
- 6. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
77
+ 5. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
84
78
 
85
79
 
86
80
  ## Setup
@@ -108,21 +102,41 @@ In order to be up and running simply use the default content of this repository
108
102
 
109
103
  ### Automatic Import
110
104
 
111
- There exists a Python file in this repository, `create_entries.py`, which can be used to import content from a [frab](https://github.com/frab/frab/wiki/Manual#introduction) compatible JSON file or a CSV table and generate the different talk, speakers and room files automatically based on it. Just open your terminal and type `python create_entries.py --help` to show the help and get started.
105
+ There exists a Python file in this repository, `_tools/create_entries.py`, which can be used to import content from a [frab](https://github.com/frab/frab/wiki/Manual#introduction) compatible JSON file (e.g. from [pretalx.com](https://pretalx.com/p/about/)) or a CSV table and generate the different talk, speakers and room files automatically. It has as only dependency [PyYAML](https://pypi.org/project/PyYAML/):
106
+
107
+ 1. Copy the file `_tools/create_entries.py` from this repository
108
+
109
+ 2. Create a virtual environment and activate it
110
+
111
+ ```bash
112
+ python -m venv venv
113
+ source venv/bin/activate
114
+ ```
115
+
116
+ 3. Install PyYAML
117
+
118
+ ```bash
119
+ pip install pyyaml
120
+ ```
121
+
122
+ 4. Execute the script, e.g. to show the help type
123
+
124
+ ```bash
125
+ python _tools/create_entries.py --help
126
+ ```
127
+
112
128
 
113
129
  ### Automatic Build
114
130
 
115
- In case you do not want to install the entire Ruby/Jekyll toolchain on your machine you can make use of [GitHub Actions](https://github.com/features/actions), Github's continuous integration platform. This repository contains an example Github Action configuration file which automatically builds and minimizes the website upon adding a new tag. It then attaches the generated website as package to a release for easy downloading. Simply copy the following file to your repository and adapt it to your needs:
131
+ In case you do not want to install the entire Ruby/Jekyll toolchain on your machine you can make use of [GitHub Actions](https://github.com/features/actions), Github's continuous integration platform. This repository contains multiple example Github Action configuration files in the `_tools/` folder:
116
132
 
117
- - `workflow-example.yml` -> `.github/workflows/main.yml`
133
+ - `build.yml`: automatically builds and minimizes the website upon adding a new tag starting with a `v` (e.g. `v2020.01.01`). It then attaches the generated website as an archive to a release for easy downloading.
134
+ - `test.yml`: automatically tries to build the website upon a new pull request. It can thus be used as status check before merging.
135
+ - `schedule.yml`: automatically generates the schedule and content files when a new pull request contains a `schedule.json` file (see the _Automatic Import_subsection above). Thus, it allows quick updates of the site's content from [pretalx.com](https://pretalx.com/p/about/) exports.
118
136
 
119
- Hidden rooms, speakers, or talks are automatically generated in way containing no content. In order to remove these empty files simply add a file called `delete_hidden.sh` to the root with the following content. It will automatically called by the Github Action workflow to delete the files.
137
+ To get started, simply copy the desired workflow file to your repository and adapt it to your needs:
120
138
 
121
- ```markdown
122
- ---
123
- layout: delete_hidden
124
- ---
125
- ```
139
+ - `_tools/build.yml` -> `.github/workflows/build.yml`
126
140
 
127
141
 
128
142
  ## Configuration
@@ -307,21 +321,23 @@ In order to help users navigating the program during the congress, a _Live_ indi
307
321
 
308
322
  This can be further extended if some of the talks have an associated live stream: Upon clicking one of the live indications a modal will open containing the corresponding live stream embedded. The URL to the live stream has to be set via `live` property in each room (see the _Content_ > _Room_ section below).
309
323
 
310
- In order to activate the functionality the `live` property has to be set containing
324
+ In order to activate the functionality, each day in the `program.yml` file must contain a `date` property (see section _Content_ > _Schedule / Program_ below) and the `live` property has to be set in the configuration file containing
311
325
 
312
- - the date of the day at which the conference takes place (`date`),
313
- - the timezone in which the conference takes place (`timezone`),
314
- - optionally if streaming is enabled (`streaming`) with indications how many minutes early the stream will begin and end, and
326
+ - how long a pause between two consecutive talks has to be for the live indication to pause (`time_stop`),
327
+ - optionally if streaming is enabled (`streaming`) with indications
328
+ + how many minutes the stream goes active before a talk (`time_prepend`),
329
+ + how many minutes the stream stays active after a talk (`time_extend`), and
330
+ + how long a pause between two consecutive talks has to be for the stream to pause (`time_pause`),
315
331
  - optionally a demo mode setting, whereby the JavaScript function cycles through the entire program in five minutes for demonstration purposes (`demo: true`, default: `false`).
316
332
 
317
333
  ```yaml
318
334
  conference:
319
335
  live:
320
- date: 01.01.2020
321
- timezone: GMT+1
336
+ time_stop: 240 # in minutes
322
337
  streaming:
323
- start_early: 15 # in minutes
324
- end_late: 0 # in minutes
338
+ time_pause: 60 # in minutes
339
+ time_prepend: 5 # in minutes
340
+ time_extend: 5 # in minutes
325
341
  demo: false
326
342
  ```
327
343
 
@@ -443,33 +459,52 @@ The actual schedule defining when and in which room a talk takes place is stored
443
459
 
444
460
  ### Schedule / Program
445
461
 
446
- The schedule of the conference linking the talks with the rooms and indicating when each talk talks place and how long it goes is set in the `_data/program.yml` file. It consists of an array of rooms each consisting of a
462
+ The schedule of the conference linking the talks with the rooms and indicating when each talk talks place and how long it goes is set in the `_data/program.yml` file. It contains a list of days, whereby each day contains a list of rooms, whereby each room contains a list of talks.
463
+
464
+ Each day consists of
447
465
 
448
- - `room` name (must correspond to one of the room identifier), and
449
- - an array of `talks` which also can be empty `[]`.
466
+ - a list of rooms (`rooms`) in which talks are taking place on that day
467
+ - optionally, the day's `name`, e.g. the weekday
468
+ - optionally, the short form of the day's name (`abbr`), and
469
+ - optionally only if no live indications are active, a `date` in the format `YYYY-MM-DD`.
450
470
 
451
- The order of the room in the file defines the order of the rooms on the website (program and room listings). Each talk in the array consists of
471
+ Each room consists of
472
+
473
+ - the room's `name` (must correspond to one of the room identifier), and
474
+ - a list of talks (`talks`) which also can be empty `[]`.
475
+
476
+ The order of the rooms in the list defines the order of the rooms as shown in the schedule on the program page. For the live streaming or the room overview the order of the rooms is alphabetical but can be adapted via the [main configuration file](https://jekyllrb.com/docs/collections/#sort-by-front-matter-key).
477
+
478
+ Each talk consists of
452
479
 
453
480
  - a `name` (must correspond to one of the talk identifier),
454
481
  - a starting time `time_start` given as `H:M` ([`strftime`](http://www.strfti.me) formated), and
455
482
  - an end time `time_end`.
456
483
 
457
- The array should (manually) be ordered by time. Currently talks can only take place on the same day and multi-day conferences are not supported.
484
+ The list of talks should (manually) be ordered by time, i.e. the first occurring talk should be listed first.
458
485
 
459
486
  Example:
460
487
 
461
488
  ```yaml
462
- - room: Room A
463
- talks:
464
- - name: Vim Impetus Placerat Cotidieque Ad
465
- time_start: '12:00'
466
- time_end: '12:45'
467
- - name: Condimentum Vitae Sapien Pellentesque
468
- time_start: '12:45'
469
- time_end: '13:30'
470
- - room: Room B
471
- talks:
472
- ...
489
+ days:
490
+ - name: Monday
491
+ abbr: Mo
492
+ date: 2020-01-31
493
+ rooms:
494
+ - name: Room A
495
+ talks:
496
+ - name: Vim Impetus Placerat Cotidieque Ad
497
+ time_start: '12:00'
498
+ time_end: '12:45'
499
+ - name: Condimentum Vitae Sapien Pellentesque
500
+ time_start: '12:45'
501
+ time_end: '13:30'
502
+
503
+ - name: Room B
504
+ talks:
505
+ - name: Arcu Non Odio
506
+ time_start: '12:00'
507
+ time_end: '13:00'
473
508
  ```
474
509
 
475
510
  ### Talks
@@ -517,7 +552,7 @@ Links are used at different location throughout the theme: They can either be us
517
552
  Additionally, a navigation bar or main landing page link can also have the following properties:
518
553
 
519
554
  - `menu` containing another list of links. This creates a dropdown menu of multiple sublinks. The sublinks have the same properties as regular links, or
520
- - `live` making the link only visible during the conference and adds a live indication. The `name` property can be omitted. If streaming is enabled and any URL property is omitted, a click on the link will open the streaming modal (see section _Live Indications_ above).
555
+ - `live` making the link only visible during the conference and adds a live indication. The `name` property can be omitted. Using the optional `name_inactive` property shows a placeholder text while the conference is **not** live. If streaming is enabled and any URL property is omitted, a click on the link will open the streaming modal (see section _Live Indications_ above).
521
556
 
522
557
  Using the `file:` indicator, the relative address is automatically set as well as the icon. Using the `video:` indicator, the link is automatically configured to open in an iframe with a corresponding title and the icon is set.
523
558
 
@@ -1,11 +1,20 @@
1
- window.conference.live = (function() {
2
- {%- include partials/get_conf_time.html -%}
3
- {%- assign time_start = conf_start -%}
4
- {%- assign time_end = conf_end -%}
5
- {%- include partials/get_timestamp.html -%}
6
-
7
- let confStart = {{ timestamp_start }};
8
- let confEnd = {{ timestamp_end }};
1
+ window.conference.live = (function() {
2
+ {% assign d = site.data.program.days | first -%}
3
+ {%- include partials/get_day_time.html -%}
4
+ {%- assign t = day_start_talk -%}
5
+
6
+ {%- include partials/get_talk_timestamp.html -%}
7
+ {%- assign conf_start = timestamp_start -%}
8
+
9
+ {%- assign d = site.data.program.days | last -%}
10
+ {%- include partials/get_day_time.html -%}
11
+ {%- assign t = day_end_talk -%}
12
+
13
+ {%- include partials/get_talk_timestamp.html -%}
14
+ {%- assign conf_end = timestamp_end -%}
15
+
16
+ let confStart = {{ conf_start }};
17
+ let confEnd = {{ conf_end }};
9
18
  let confDur = confEnd - confStart;
10
19
 
11
20
  let freezeTime = false;
@@ -20,7 +29,8 @@
20
29
  let demoEnd = confEnd + confDur/durDemo*durPause;
21
30
 
22
31
  let liveTimer;
23
- let streamTimer;
32
+ let streamVideoTimer;
33
+ let streamInfoTimer;
24
34
 
25
35
  let mod = function (n, m) {
26
36
  return ((n % m) + m) % m;
@@ -77,10 +87,22 @@
77
87
  startUpdate();
78
88
  };
79
89
 
80
- let setTime = function (newTime) {
90
+ let setTime = function (newTime, newDay=1) {
81
91
  pauseTime();
82
92
 
83
- let d = new Date(confStart * 1000);
93
+ let dayIdx;
94
+ if (Number.isInteger(newDay)) {
95
+ dayIdx = newDay-1;
96
+ }
97
+ else if (/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(newDay)) {
98
+ dayIdx = data.days.find(o => o.name === newDay);
99
+ }
100
+ else {
101
+ dayIdx = data.days.find(o => o.name === newDay);
102
+ }
103
+ newDate = data.days[dayIdx].date;
104
+
105
+ let d = new Date(newDate);
84
106
  newTime = newTime.split(':');
85
107
  d.setHours(newTime[0], newTime[1]);
86
108
 
@@ -89,12 +111,13 @@
89
111
  update();
90
112
  };
91
113
 
92
- let getTime = function (tConvert = time()) {
114
+ let getTime = function (tConvert=time()) {
93
115
  let d = new Date(tConvert * 1000);
116
+ let dStr = d.toISOString().slice(0,10);
94
117
  let h = d.getHours();
95
118
  let m = d.getMinutes();
96
119
 
97
- return h + ":" + (m < 10 ? "0" : "") + m;
120
+ return dStr +" "+ h +":"+ (m < 10 ? "0" : "") + m;
98
121
  };
99
122
 
100
123
  let timeUnit = function () {
@@ -138,34 +161,120 @@
138
161
  let tNow = time();
139
162
  let liveShow = document.getElementsByClassName('live-show');
140
163
  let liveHide = document.getElementsByClassName('live-hide');
164
+ let liveTime = document.getElementsByClassName('live-time');
165
+ let livePast = document.getElementsByClassName('live-past');
141
166
 
142
167
  for (let i = 0; i < liveShow.length; i++) {
143
- let tStart = liveShow[i].dataset.start;
144
- let tEnd = liveShow[i].dataset.end;
168
+ let tStarts = liveShow[i].dataset.start.split(',');
169
+ let tEnds = liveShow[i].dataset.end.split(',');
170
+
171
+ for (let k = 0; k < tStarts.length; k++) {
172
+ if (tNow >= tStarts[k] && tNow < tEnds[k]) {
173
+ // Show when active
174
+ liveShow[i].classList.remove('d-none');
175
+ break;
176
+ }
177
+ else if (!liveShow[i].classList.contains('d-none')) {
178
+ // Hide otherwise
179
+ liveShow[i].classList.add('d-none');
180
+ }
181
+ }
182
+ }
183
+
184
+ for (let i = 0; i < liveHide.length; i++) {
185
+ let tStarts = liveHide[i].dataset.start.split(',');
186
+ let tEnds = liveHide[i].dataset.end.split(',');
187
+
188
+ for (let k = 0; k < tStarts.length; k++) {
189
+ if (tNow >= tStarts[k] && tNow < tEnds[k]) {
190
+ // Hide when active
191
+ if (!liveHide[i].classList.contains('d-none')) {
192
+ liveHide[i].classList.add('d-none');
193
+ break;
194
+ }
195
+ }
196
+ else {
197
+ // Show otherwise
198
+ liveHide[i].classList.remove('d-none');
199
+ }
200
+ }
201
+ }
202
+
203
+ for (let i = 0; i < liveTime.length; i++) {
204
+ let t = liveTime[i].dataset.time;
205
+ if (typeof t == "undefined") {
206
+ break;
207
+ }
208
+ let tRel = tNow - t;
145
209
 
146
- if (tNow >= tStart && tNow < tEnd) {
147
- // Show when active
148
- liveShow[i].classList.remove('d-none');
210
+ let tStr;
211
+ if (tRel >= -60 && tRel < 0) {
212
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.soon | default: "soon" }}';
149
213
  }
150
- else if (!liveShow[i].classList.contains('d-none')) {
151
- // Hide otherwise
152
- liveShow[i].classList.add('d-none');
214
+ else if (tRel >= 0 && tRel < 60) {
215
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.now | default: "now" }}';
153
216
  }
217
+ else {
218
+ if (tRel < 0) {
219
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.in | default: "in" }} ';
220
+ }
221
+ else {
222
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.since | default: "since" }} ';
223
+ }
224
+ tRel = Math.abs(tRel);
225
+
226
+ let dWeeks = Math.floor(tRel / (7*24*60*60));
227
+ let dDays = Math.floor(tRel / (24*60*60));
228
+ let dHours = Math.floor(tRel / (60*60));
229
+ let dMins = Math.floor(tRel / (60));
230
+ if (dWeeks > 4) {
231
+ break;
232
+ }
233
+ else if (dWeeks > 1) {
234
+ tStr += dWeeks +' {{ site.data.lang[site.conference.lang].live.time.weeks | default: "weeks" }}';
235
+ }
236
+ else if (dWeeks == 1) {
237
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.week | default: "week" }}';
238
+ }
239
+ else if (dDays > 1) {
240
+ tStr += dDays +' {{ site.data.lang[site.conference.lang].live.time.days | default: "days" }}';
241
+ }
242
+ else if (dDays == 1) {
243
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.day | default: "day" }}';
244
+ }
245
+ else if (dHours > 1) {
246
+ tStr += dHours +' {{ site.data.lang[site.conference.lang].live.time.hours | default: "hours" }}';
247
+ }
248
+ else if (dHours == 1) {
249
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.hour | default: "hour" }}';
250
+ }
251
+ else if (dMins > 1) {
252
+ tStr += dMins +' {{ site.data.lang[site.conference.lang].live.time.minutes | default: "minutes" }}';
253
+ }
254
+ else {
255
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.minute | default: "minute" }}';
256
+ }
257
+ }
258
+
259
+ liveTime[i].innerHTML = tStr;
154
260
  }
155
261
 
156
- for (let i = 0; i < liveHide.length; i++) {
157
- let tStart = liveHide[i].dataset.start;
158
- let tEnd = liveHide[i].dataset.end;
262
+ for (let i = 0; i < livePast.length; i++) {
263
+ let t = livePast[i].dataset.time;
264
+ if (typeof t == "undefined") {
265
+ break;
266
+ }
267
+ let tRel = tNow - t;
159
268
 
160
- if (tNow >= tStart && tNow < tEnd) {
161
- // Hide when active
162
- if (!liveHide[i].classList.contains('d-none')) {
163
- liveHide[i].classList.add('d-none');
269
+ if (tRel < 0) {
270
+ // Grey out when in past
271
+ if (!livePast[i].classList.contains('text-secondary')) {
272
+ livePast[i].classList.add('text-secondary');
164
273
  }
165
274
  }
166
275
  else {
167
- // Show otherwise
168
- liveHide[i].classList.remove('d-none');
276
+ // Show normal otherwise
277
+ livePast[i].classList.remove('text-secondary');
169
278
  }
170
279
  }
171
280
 
@@ -198,111 +307,195 @@
198
307
  };
199
308
 
200
309
  {% if site.conference.live.streaming -%}
201
-
202
- let rooms = {
203
- {%- for r in site.data.program -%}
204
- {%- assign room = site.rooms | where: 'name', r.room | first -%}
205
- {%- if room.live -%}
206
-
207
- {%- assign t = r.talks | first -%}
208
- {%- include partials/get_talk_time.html -%}
209
- {%- assign time_start = talk_start -%}
210
- {%- assign time_end = talk_end -%}
211
- {%- include partials/get_timestamp.html -%}
212
-
213
- {%- assign offset_start = site.conference.live.streaming.start_early | default: 0 -%}
214
- {%- assign room_ts_start = offset_start | times: -60 | plus: timestamp_start -%}
215
-
216
- {%- assign t = r.talks | last -%}
217
- {%- include partials/get_talk_time.html -%}
218
- {%- assign time_start = talk_start -%}
219
- {%- assign time_end = talk_end -%}
220
- {%- include partials/get_timestamp.html -%}
221
-
222
- {%- assign offset_end = site.conference.live.streaming.end_late | default: 0 -%}
223
- {%- assign room_ts_end = offset_end | times: 60 | plus: timestamp_end -%}
224
-
225
- "{{ room.name }}": {
226
- "id": {{ forloop.index }},
227
- "href": "{{ room.live }}",
228
- "start": {{ room_ts_start }},
229
- "end": {{ room_ts_end }}
230
- },
231
- {%- endif -%}
232
- {%- endfor -%}
233
- };
310
+ let streamPause = {{ site.conference.live.streaming.time_pause | default: 60 }}; // in minutes
311
+ let streamPrepend = {{ site.conference.live.streaming.time_prepend | default: 5 }}; // in minutes
312
+ let streamExtend = {{ site.conference.live.streaming.time_extend | default: 5 }}; // in minutes
234
313
 
235
314
  let streamModal;
315
+ let data;
236
316
 
237
317
  let getRoom = function (roomName) {
238
- if (roomName in rooms) {
239
- return rooms[roomName];
318
+ if (roomName in data.rooms) {
319
+ return data.rooms[roomName];
240
320
  }
241
321
  else {
242
- return rooms[Object.keys(rooms)[0]];
322
+ return data.rooms[Object.keys(data.rooms)[0]];
243
323
  }
244
324
  };
245
325
 
246
- let preStartStream = function (roomName) {
247
- let room = getRoom(roomName);
326
+ let getNextTalk = function (roomName) {
327
+ let timeNow = time();
328
+ let talksHere = data.talks[roomName];
329
+
330
+ if (typeof talksHere !== "undefined") {
331
+ if (timeNow < talksHere[talksHere.length-1].end) {
332
+ for (var i = 0; i < talksHere.length; i++) {
333
+ if (timeNow < talksHere[i].end) {
334
+ return talksHere[i];
335
+ }
336
+ }
337
+ }
338
+ }
339
+ return false;
340
+ };
341
+
342
+ let getNextPause = function (roomName) {
343
+ let timeNow = time();
344
+ let talksHere = data.talks[roomName];
345
+
346
+ if (typeof talksHere !== "undefined") {
347
+ if (timeNow < talksHere[talksHere.length-1].end) {
348
+ for (var i = 1; i < talksHere.length; i++) {
349
+ if (timeNow < talksHere[i].start && streamPause*60 <= talksHere[i].start - talksHere[i-1].end) {
350
+ return {
351
+ 'start': talksHere[i-1].end,
352
+ 'end': talksHere[i].start,
353
+ };
354
+ }
355
+ }
356
+ }
357
+ }
358
+ return false;
359
+ };
248
360
 
361
+ let setStreamContent = function (content) {
249
362
  streamModal.find('iframe').attr('src', '');
250
363
  streamModal.find('iframe').addClass('d-none');
251
- streamModal.find('#stream-placeholder > div').text('{{ site.data.lang[site.conference.lang].live.pre_stream | default: "Live stream has not started yet." }}');
364
+ streamModal.find('#stream-placeholder > div').text(content);
252
365
  streamModal.find('#stream-placeholder').addClass('d-flex');
253
-
254
- stopUpdateStream();
255
- if (!freezeTime) {
256
- streamTimer = setTimeout(activeStream, delayStart(room.start) * 1000, roomName);
257
- }
258
366
  };
259
367
 
260
- let activeStream = function (roomName) {
261
- let room = getRoom(roomName);
262
-
263
- streamModal.find('iframe').attr('src', room.href);
368
+ let setStreamSrc = function (href) {
369
+ streamModal.find('iframe').attr('src', href);
264
370
  streamModal.find('#stream-placeholder').addClass('d-none').removeClass('d-flex');
265
371
  streamModal.find('iframe').removeClass('d-none');
372
+ };
266
373
 
267
- stopUpdateStream();
268
- if (!freezeTime) {
269
- streamTimer = setTimeout(postEndStream, delayStart(room.end) * 1000, roomName);
374
+ let setStreamVideo = function (roomName) {
375
+ let timeNow = time();
376
+
377
+ let talksHere = data.talks[roomName];
378
+ let roomStart = talksHere[0].start;
379
+ let roomEnd = talksHere[talksHere.length-1].end;
380
+
381
+ if (typeof streamVideoTimer !== "undefined") {
382
+ clearInterval(streamVideoTimer);
383
+ }
384
+
385
+ // Conference not yet started
386
+ if (timeNow < roomStart - streamPrepend*60) {
387
+ setStreamContent('{{ site.data.lang[site.conference.lang].live.pre_stream | default: "Live stream has not started yet." }}');
388
+
389
+ if (!freezeTime) {
390
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomStart - streamPrepend*60) * 1000, roomName);
391
+ }
392
+ }
393
+
394
+ // Conference is over
395
+ else if (timeNow > roomEnd + streamExtend*60) {
396
+ setStreamContent('{{ site.data.lang[site.conference.lang].live.post_stream | default: "Live stream has ended." }}');
397
+
398
+ if (!freezeTime && demo) {
399
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomEnd - streamPrepend*60) * 1000, roomName);
400
+ }
401
+ }
402
+
403
+ // Conference ongoing
404
+ else {
405
+ let pauseNext = getNextPause(roomName);
406
+
407
+ // Currently stream is paused
408
+ if (pauseNext && timeNow >= pauseNext.start + streamExtend*60 && timeNow <= pauseNext.end - streamPrepend*60) {
409
+ setStreamContent('{{ site.data.lang[site.conference.lang].live.pause_stream | default: "Live stream is currently paused." }}');
410
+
411
+ if (!freezeTime) {
412
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(pauseNext.end - streamPrepend*60) * 1000, roomName);
413
+ }
414
+ }
415
+ // Currently a talk is active
416
+ else {
417
+ let room = getRoom(roomName);
418
+ setStreamSrc(room.href);
419
+
420
+ if (!freezeTime) {
421
+ if (pauseNext) {
422
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(pauseNext.start + streamExtend*60) * 1000, roomName);
423
+ }
424
+ else {
425
+ streamVideoTimer = setTimeout(setStreamVideo, delayStart(roomEnd + streamExtend*60) * 1000, roomName);
426
+ }
427
+ }
428
+ }
270
429
  }
271
430
  };
272
431
 
273
- let postEndStream = function (roomName) {
274
- let room = getRoom(roomName);
432
+ let setStreamInfo = function (roomName) {
433
+ let timeNow = time();
434
+ let talkNext = getNextTalk(roomName);
275
435
 
276
- streamModal.find('iframe').attr('src', '');
277
- streamModal.find('iframe').addClass('d-none');
278
- streamModal.find('#stream-placeholder > div').text('{{ site.data.lang[site.conference.lang].live.post_stream | default: "Live stream has ended." }}');
279
- streamModal.find('#stream-placeholder').addClass('d-flex');
436
+ if (typeof streamInfoTimer !== "undefined") {
437
+ clearInterval(streamInfoTimer);
438
+ }
280
439
 
281
- stopUpdateStream();
282
- if (!freezeTime && demo) {
283
- streamTimer = setTimeout(preStartStream, delayStart(demoStart) * 1000, roomName);
440
+ if (timeNow >= talkNext.start - streamPause*60) {
441
+ document.getElementById('stream-info').dataset.time = talkNext.start;
442
+ document.getElementById('stream-info-time').dataset.time = talkNext.start;
443
+ updateLive();
444
+
445
+ streamModal.find('#stream-info-color').removeClass(function (index, className) {
446
+ return (className.match(/(^|\s)border-soft-\S+/g) || []).join(' ');
447
+ });
448
+ streamModal.find('#stream-info-color').addClass('border-soft-' + talkNext.color);
449
+
450
+ streamModal.find('#stream-info-talk').text(talkNext.name).attr('href', talkNext.href);
451
+
452
+ let speakerStr = '';
453
+ for (var i = 0; i < talkNext.speakers.length; i++) {
454
+ let speaker = data.speakers[talkNext.speakers[i]];
455
+ if (speaker.href == '') {
456
+ speakerStr += speaker.name +', '
457
+ }
458
+ else {
459
+ speakerStr += '<a class="text-reset" href="'+ speaker.href +'">'+ speaker.name +'</a>, ';
460
+ }
461
+ }
462
+ speakerStr = speakerStr.slice(0, -2);
463
+ streamModal.find('#stream-info-speakers').html(speakerStr);
464
+
465
+ streamModal.find('#stream-info').removeClass('d-none');
466
+
467
+ if (!freezeTime) {
468
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talkNext.end) * 1000, roomName);
469
+ }
470
+ }
471
+ else {
472
+ streamModal.find('#stream-info').addClass('d-none');
473
+
474
+ if (!freezeTime) {
475
+ if (talkNext) {
476
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talkNext.start - streamPause*60) * 1000, roomName);
477
+ }
478
+ else if (demo) {
479
+ let talksHere = data.talks[roomName];
480
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talksHere[0].start - streamPrepend*60) * 1000, roomName);
481
+ }
482
+ }
284
483
  }
285
484
  };
286
485
 
287
486
  let setStream = function (roomName) {
288
487
  streamModal.find('.modal-footer .btn').removeClass('active');
289
- streamModal.find('#stream-select').selectedIndex = -1;
488
+ streamModal.find('#stream-select').val(0);
290
489
 
490
+ // Recover room name in case of empty default
291
491
  let room = getRoom(roomName);
292
- let tNow = time();
492
+ roomName = room.name;
293
493
 
294
- if (tNow < room.start) {
295
- preStartStream(roomName);
296
- }
297
- else if (tNow > room.end) {
298
- postEndStream(roomName);
299
- }
300
- else {
301
- activeStream(roomName);
302
- }
494
+ setStreamVideo(roomName);
495
+ setStreamInfo(roomName);
303
496
 
304
497
  streamModal.find('#stream-button' + room.id).addClass('active');
305
- streamModal.find('#stream-select').selectedIndex = room.id;
498
+ streamModal.find('#stream-select').val(room.id);
306
499
  };
307
500
 
308
501
  let updateStream = function () {
@@ -317,8 +510,11 @@
317
510
  };
318
511
 
319
512
  let stopUpdateStream = function () {
320
- if (typeof streamTimer !== "undefined") {
321
- clearInterval(streamTimer);
513
+ if (typeof streamVideoTimer !== "undefined") {
514
+ clearInterval(streamVideoTimer);
515
+ }
516
+ if (typeof streamInfoTimer !== "undefined") {
517
+ clearInterval(streamInfoTimer);
322
518
  }
323
519
  };
324
520
 
@@ -331,6 +527,7 @@
331
527
  let setupStream = function () {
332
528
  streamModal = $('#stream-modal');
333
529
 
530
+ // configure modal opening buttons
334
531
  streamModal.on('show.bs.modal', function (event) {
335
532
  let button = $(event.relatedTarget);
336
533
  let roomName = button.data('room');
@@ -340,6 +537,7 @@
340
537
  hideModal(event);
341
538
  });
342
539
 
540
+ // configure room selection buttons in modal
343
541
  streamModal.find('.modal-footer .btn').on('click', function(event) {
344
542
  event.preventDefault();
345
543
 
@@ -347,12 +545,18 @@
347
545
  setStream(roomName);
348
546
  });
349
547
 
548
+ // configure room selection menu in modal
350
549
  streamModal.find('#stream-select').on('change', function(event) {
351
550
  event.preventDefault();
352
551
 
353
552
  let roomName = $(this).children('option:selected').text();
354
553
  setStream(roomName);
355
554
  });
555
+
556
+ // load data
557
+ $.getJSON('{{ site.baseurl }}/assets/js/data.json', function(json) {
558
+ data = json;
559
+ });
356
560
  };
357
561
 
358
562
  let setup = function () {