jekyll-theme-conference 2.4.1 → 2.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b4a68d1ce4697568c8582882600a0373e99ffc2f2fb71200d1cbaee33a8850c
4
- data.tar.gz: 6abbc6c552d5df4a1bf0f126c9c01573887e101904fd9321e17000ecf68a8fd8
3
+ metadata.gz: fbbe3c769ff41772597a229dc226732d868691b98d006c4767a569a4ef0b4d14
4
+ data.tar.gz: ffd668e698af13d076e91823355b69550a0e0486cee4b0be54c0db7f5c1a5916
5
5
  SHA512:
6
- metadata.gz: 11dc593b0689b0834a22c61c28735ed343d5bc17e0663ed7269b1ebf435a9895642be0d25d3f95944048e290d23cfffdac0cd72f0c674e5b311fa2bba10cf8ff
7
- data.tar.gz: 4914fca74c4dac1b34ae4e764716a91a6256da5d02e78463d5769ff47221a6a6ecee4219c09ea806461cccbe5215be7b35c3908927b0e7f25d7c528278714a6c
6
+ metadata.gz: 54b43f0628ab99bf3c509bb75f7fab9d9bd390f41c1ac377f29e1f2f6a46f456b2281c74368fd6fad970e3d0f6a9043bb3831ffaeef4744177343006119427d5
7
+ data.tar.gz: 8b109d80ee5ae3810cacb223840414953c757b91e50dac38b7c66d840277a937a263cfb4e2b72bb0dcd541223b24500418e461b859274849bb41af9025e22907
data/README.md CHANGED
@@ -4,11 +4,14 @@
4
4
 
5
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:
6
6
 
7
- - program / schedule
8
- - talk and speaker descriptions
9
- - map for directions
10
-
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.
7
+ - program / schedule,
8
+ - talk and speaker descriptions with
9
+ + links to slides, and
10
+ + embedded recordings,
11
+ - map for directions,
12
+ - live indication and embedded video streaming.
13
+
14
+ 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. [frab](https://github.com/frab/frab/wiki/Manual#introduction) compatible schedules.
12
15
  The design is easily modifiable and is adapted for mobile uses and printing.
13
16
 
14
17
  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:
@@ -44,11 +47,7 @@ This allows for easier installation and updating as you don't have to manage any
44
47
  theme: jekyll-theme-conference
45
48
  ```
46
49
 
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
50
+ 4. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
52
51
 
53
52
  To update the theme run `bundle update`.
54
53
 
@@ -76,11 +75,7 @@ To install:
76
75
 
77
76
  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
77
 
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
78
+ 5. Continue with the _Setup_ section further below to customize the theme and add some content for your conference
84
79
 
85
80
 
86
81
  ## Setup
@@ -89,6 +84,10 @@ The different talks, speakers and rooms are stored as a collection of files. Eac
89
84
 
90
85
  The actual schedule defining when and in which room a talk takes place is stored as a [YAML data file](https://jekyllrb.com/docs/datafiles/) under `_data/program.yml`. For further details about it see below in the section _Content_.
91
86
 
87
+ :warning: Please note that the generated website can be quite large containing many unnecessary whitespaces. It is recommended to minimize the generated output files before uploading them to a server (e.g. with [minify](https://github.com/tdewolff/minify)).
88
+
89
+ ### Jump Start
90
+
92
91
  In order to be up and running simply use the default content of this repository as an initial base for your new website. After having setup a new Jekyll website copy the following files and folders into the website's folder:
93
92
 
94
93
  - `_config.example.yml` -> `_config.yml`
@@ -102,11 +101,51 @@ In order to be up and running simply use the default content of this repository
102
101
  - `speakers/`
103
102
  - `talks/`
104
103
 
105
- There exists a Python file in this repository, `create_entries.py`, which can be used to import content from 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.
104
+ ### Automatic Import
105
+
106
+ 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/):
107
+
108
+ 1. Copy the file `_tools/create_entries.py` from this repository
109
+
110
+ 2. Create a virtual environment and activate it
111
+
112
+ ```bash
113
+ python -m venv venv
114
+ source venv/bin/activate
115
+ ```
116
+
117
+ 3. Install PyYAML
118
+
119
+ ```bash
120
+ pip install pyyaml
121
+ ```
122
+
123
+ 4. Execute the script, e.g. to show the help type
124
+
125
+ ```bash
126
+ python _tools/create_entries.py --help
127
+ ```
128
+
106
129
 
107
- 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 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:
130
+ ### Automatic Build
108
131
 
109
- - `workflows.example.yml` -> `.github/workflows.main.yml`
132
+ 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:
133
+
134
+ - `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.
135
+ - `test.yml`: automatically tries to build the website upon a new pull request. It can thus be used as status check before merging.
136
+ - `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.
137
+
138
+ To get started, simply copy the desired workflow file to your repository and adapt it to your needs:
139
+
140
+ - `_tools/build.yml` -> `.github/workflows/build.yml`
141
+
142
+ 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 `build` workflow (if available) to delete the files.
143
+
144
+ ```markdown
145
+ ---
146
+ layout: delete_hidden
147
+ ---
148
+ ```
110
149
 
111
150
 
112
151
  ## Configuration
@@ -124,6 +163,8 @@ conference:
124
163
  show_errors: false
125
164
  ```
126
165
 
166
+ :warning: Please be sure to disable this parameter for your production system.
167
+
127
168
  ### Collection URLs
128
169
 
129
170
  The three required collections containing the files for the talks, speakers and rooms have to be specified in the `_config.yml` file. The first block declares them and sets the URL under which they will later be accessed. The second block defines the default layout for each of the collection.
@@ -183,10 +224,6 @@ conference:
183
224
 
184
225
  The navigation bar is located at the top and visible on every site. On the right it show the title of the website (`site.title`) followed by the links listed under the `links` property of the `navigation` property. See the _Content_ > _Links_ section below for the available properties per link.
185
226
 
186
- Additionally, a navigation bar link can also have the following properties:
187
-
188
- - `menu` containing another list of links. This creates a dropdown menu of multiple sublinks. The sublinks have the same properties as regular links (see the _Content_ > _Links_ section).
189
-
190
227
  Example:
191
228
 
192
229
  ```yaml
@@ -195,6 +232,7 @@ conference:
195
232
  links:
196
233
  - name: Program
197
234
  relative_url: /program/
235
+ - live: true
198
236
  - name: Previous Editions
199
237
  menu:
200
238
  - name: 2020 (current)
@@ -224,7 +262,7 @@ conference:
224
262
 
225
263
  The main landing page is shown at the root of the website to greet new visitors. In order to show it you need to create a `index.md` file in the root of your website's folder and specify its layout as `layout: main`. The remaining customizations are specified in the `_config.yml` file.
226
264
 
227
- The main page states your site's title (`site.title`) or a logo instead. The logo can be configured through the `logo` property under the `main` property containing:
265
+ The main page states your site's title (`site.title`) or a logo instead. The logo can be configured through the `logo` property under the `main` property containing
228
266
 
229
267
  - a `img` property specifying the path to the image file relative to the `/assets/images/` folder.
230
268
 
@@ -286,7 +324,31 @@ conference:
286
324
  Try again next year.
287
325
  ```
288
326
 
289
- ### Talk Settings: Main Categories and Icons
327
+ ### Live Indications & Streaming
328
+
329
+ In order to help users navigating the program during the congress, a _Live_ indication can be shown next to talks which are currently taking place. A small JavaScript functions keeps the site automatically up-to-date (without the need to refresh) showing the indication as soon as the talk has started and hiding it once it is over (according to the timetable indicated in the `_data/program.yml` file).
330
+
331
+ 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).
332
+
333
+ In order to activate the functionality the `live` property has to be set containing
334
+
335
+ - the date of the day at which the conference takes place (`date`),
336
+ - the timezone in which the conference takes place (`timezone`),
337
+ - optionally if streaming is enabled (`streaming`) with indications how many minutes early the stream will begin and end, and
338
+ - optionally a demo mode setting, whereby the JavaScript function cycles through the entire program in five minutes for demonstration purposes (`demo: true`, default: `false`).
339
+
340
+ ```yaml
341
+ conference:
342
+ live:
343
+ date: 01.01.2020
344
+ timezone: GMT+1
345
+ streaming:
346
+ start_early: 15 # in minutes
347
+ end_late: 0 # in minutes
348
+ demo: false
349
+ ```
350
+
351
+ ### Talk Settings
290
352
 
291
353
  Each talk can have one or multiple categories associated via FrontMatter (see the _Individual Pages: Talks_ section below for more details). Some of these categories can be elevated to so called main categories". These are used to color group the talks across the entire website, particularly in the program. In order to do so add the `main_categories` property under the `talks` property. It consists of a list of all main categories. Each main category consists of:
292
354
 
@@ -314,11 +376,12 @@ conference:
314
376
  color: info
315
377
  - name: Cat B
316
378
  color: success
379
+
317
380
  # Hide icons on talk overview page
318
381
  hide_icons: false
319
382
  ```
320
383
 
321
- ### Speaker Settings: First name
384
+ ### Speaker Settings
322
385
 
323
386
  In the program as well as the speaker's overview the speaker's first name can be abbreviated to its first letter. Of course, you also have the option to not specify a first name for each speaker in the first place. In order to shorten the first name add the `show_firstname: true` setting (default: `false`) to the `speakers` property.
324
387
 
@@ -330,7 +393,7 @@ conference:
330
393
  show_firstname: false
331
394
  ```
332
395
 
333
- ### Location Settings: Hide all and configure map
396
+ ### Location Settings
334
397
 
335
398
  In case the location of your rooms is obvious (e.g. on a campus) you can decide to disable the location page and links to all the rooms. You still need to create the different rooms as files in the `_rooms/` directory, since they are needed as a reference. But there will not be any link pointing to it (effectively hiding them).
336
399
  In order to hide all rooms add the `hide: true` setting (default: `false`) to the `location` property.
@@ -353,7 +416,7 @@ conference:
353
416
  map_provider: "OpenStreetMap.Mapnik"
354
417
  ```
355
418
 
356
- The map is based on the JavaScript Library [Leaflet](https://leafletjs.com/) and can be customized by editing the `assets/js/main.js` file, e.g. adding additional layers with markers, text, or shapes to the map. To start, copy simply the file from this repository and make use of the initialized global variable `map` pointing to the Leaflet container.
419
+ The map is based on the JavaScript Library [Leaflet](https://leafletjs.com/) and can be customized by editing the `assets/js/main.js` file, e.g. adding additional layers with markers, text, or shapes to the map. To start, copy simply the file from this repository and make use of the initialized global variable `window.conference.map` pointing to the Leaflet container.
357
420
 
358
421
  Example:
359
422
 
@@ -364,11 +427,13 @@ Example:
364
427
  {% include js/conference.js %}
365
428
 
366
429
  (function() {
430
+ let map = window.conference.map;
431
+
367
432
  if (typeof map !== 'undefined') {
368
433
  var main_station = L.marker([47.37785, 8.54035], {
369
434
  icon: L.divIcon({
370
435
  className: '',
371
- html: '<span class="fas fa-train"></span> Bahnhof Zürich',
436
+ html: '<span class="fas fa-train"></span> Main Station',
372
437
  iconSize: [120, 56]
373
438
  })
374
439
  }).addTo(map);
@@ -454,8 +519,9 @@ Each speaker is represented by a file in the `_speakers/` directory. It must beg
454
519
 
455
520
  Each room is represented by a file in the `_rooms/` directory. It must begin with valid [YAML Front Matter](https://jekyllrb.com/docs/frontmatter/) containing
456
521
 
457
- - the room's `name`, and
458
- - optionally `hide: true` if the room's page should not be linked to.
522
+ - the room's `name`
523
+ - optionally `hide: true` if the room's page should not be linked to, and
524
+ - optionally a URL pointing to a live stream for the given room during the conference (`live`, see the section _Live Indications & Streaming_ above).
459
525
 
460
526
  ### Links
461
527
 
@@ -471,6 +537,11 @@ Links are used at different location throughout the theme: They can either be us
471
537
  + pointing to a file uploaded to the `/documents` folder (for talks `/documents/slides`, for speakers `/documents/bio`): `file:`
472
538
  + pointing to an external video: `video:`
473
539
 
540
+ Additionally, a navigation bar or main landing page link can also have the following properties:
541
+
542
+ - `menu` containing another list of links. This creates a dropdown menu of multiple sublinks. The sublinks have the same properties as regular links, or
543
+ - `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).
544
+
474
545
  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.
475
546
 
476
547
  Example:
@@ -0,0 +1,87 @@
1
+ let rooms = {
2
+ {%- for r in site.data.program -%}
3
+ {%- assign room = site.rooms | where: 'name', r.room | first -%}
4
+ {%- if room.live -%}
5
+
6
+ {%- assign t = r.talks | first -%}
7
+ {%- include partials/get_talk_time.html -%}
8
+ {%- assign time_start = talk_start -%}
9
+ {%- assign time_end = talk_end -%}
10
+ {%- include partials/get_timestamp.html -%}
11
+
12
+ {%- assign offset_start = site.conference.live.streaming.start_early | default: 0 -%}
13
+ {%- assign room_ts_start = offset_start | times: -60 | plus: timestamp_start -%}
14
+
15
+ {%- assign t = r.talks | last -%}
16
+ {%- include partials/get_talk_time.html -%}
17
+ {%- assign time_start = talk_start -%}
18
+ {%- assign time_end = talk_end -%}
19
+ {%- include partials/get_timestamp.html -%}
20
+
21
+ {%- assign offset_end = site.conference.live.streaming.end_late | default: 0 -%}
22
+ {%- assign room_ts_end = offset_end | times: 60 | plus: timestamp_end -%}
23
+
24
+ "{{ room.name }}": {
25
+ "id": {{ forloop.index }},
26
+ "name": "{{ room.name }}",
27
+ "href": "{{ room.live }}",
28
+ "start": {{ room_ts_start }},
29
+ "end": {{ room_ts_end }}
30
+ },
31
+ {%- endif -%}
32
+ {%- endfor -%}
33
+ };
34
+
35
+ let speakers = {
36
+ {%- for speaker in site.speakers -%}
37
+ "{{ speaker.name }}" : {
38
+ {%- if site.conference.speakers.show_firstname -%}
39
+ "name": "{{ speaker.first_name | append: ' ' | append: speaker.last_name }}",
40
+ {%- else -%}
41
+ "name": "{{ speaker.first_name | slice: 0 | append : '. ' | append: speaker.last_name }}",
42
+ {%- endif -%}
43
+ {%- if speaker.hide -%}
44
+ "href": "",
45
+ {%- else -%}
46
+ "href": "{{ speaker.url | prepend: site.baseurl }}",
47
+ {%- endif -%}
48
+ },
49
+ {%- endfor -%}
50
+ };
51
+
52
+ let talks = {
53
+ {%- for r in site.data.program -%}
54
+ {%- assign room = site.rooms | where: 'name', r.room | first -%}
55
+ {%- if room.live -%}
56
+ "{{ room.name | replace: '"', '\"' }}": [
57
+ {%- for t in r.talks -%}
58
+ {%- assign talk = site.talks | where: 'name', t.name | first -%}
59
+ {
60
+ "name": "{{ talk.name | replace: '"', '\"' }}",
61
+
62
+ {%- unless talk.hide -%}
63
+ "href": "{{ talk.url | prepend: site.baseurl }}",
64
+ {%- else -%}
65
+ "href": "",
66
+ {%- endunless -%}
67
+
68
+ {%- include partials/get_main_category.html -%}
69
+ "color": "{{ main_cat_color }}",
70
+
71
+ "speakers": [
72
+ {%- for speaker_name in talk.speakers -%}
73
+ "{{ speaker_name}}",
74
+ {%- endfor -%}
75
+ ],
76
+
77
+ {%- assign time_start = t.time_start -%}
78
+ {%- assign time_end = t.time_end -%}
79
+ {%- include partials/get_timestamp.html -%}
80
+ "start": {{ timestamp_start }},
81
+ "end": {{ timestamp_end }}
82
+ },
83
+ {%- endfor -%}
84
+ ],
85
+ {%- endif -%}
86
+ {%- endfor -%}
87
+ };
@@ -0,0 +1,533 @@
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 }};
9
+ let confDur = confEnd - confStart;
10
+
11
+ let talkAnnounce = 120; // in minutes
12
+
13
+ let freezeTime = false;
14
+ let timeFrozen = 0;
15
+ let timeOffset = 0;
16
+
17
+ let demo = {{ site.conference.live.demo | default: "false" }};
18
+ let durDemo = 5*60; // in seconds
19
+ let durPause = 10; // in seconds
20
+
21
+ let demoStart = confStart - confDur/durDemo*durPause;
22
+ let demoEnd = confEnd + confDur/durDemo*durPause;
23
+
24
+ let liveTimer;
25
+ let streamTimer;
26
+ let streamInfoTimer;
27
+
28
+ let mod = function (n, m) {
29
+ return ((n % m) + m) % m;
30
+ };
31
+
32
+ let timeNow = function () {
33
+ return Math.floor(Date.now() / 1000);
34
+ };
35
+
36
+ let timeCont = function () {
37
+ return timeNow() - timeOffset;
38
+ };
39
+
40
+ let timeCycle = function () {
41
+ let actTime = timeNow();
42
+ let relTime = mod(actTime, durDemo + 2*durPause) / durDemo;
43
+ let cycleTime = mod((demoEnd - demoStart) * relTime - timeOffset, (demoEnd - demoStart)) + demoStart;
44
+ return cycleTime;
45
+ };
46
+
47
+ let time = function () {
48
+ if (freezeTime) {
49
+ return timeFrozen;
50
+ }
51
+ else if (demo) {
52
+ return timeCycle();
53
+ }
54
+ else {
55
+ return timeCont();
56
+ }
57
+ };
58
+
59
+ let pauseTime = function () {
60
+ if (!freezeTime) {
61
+ timeFrozen = time();
62
+ freezeTime = true;
63
+
64
+ stopUpdate();
65
+ }
66
+ };
67
+
68
+ let continueTime = function () {
69
+ if (freezeTime) {
70
+ freezeTime = false;
71
+ timeOffset += time() - timeFrozen;
72
+ startUpdate();
73
+ }
74
+ };
75
+
76
+ let resetTime = function (timeStr) {
77
+ timeOffset = 0;
78
+ freezeTime = false;
79
+
80
+ startUpdate();
81
+ };
82
+
83
+ let setTime = function (newTime) {
84
+ pauseTime();
85
+
86
+ let d = new Date(confStart * 1000);
87
+ newTime = newTime.split(':');
88
+ d.setHours(newTime[0], newTime[1]);
89
+
90
+ timeFrozen = Math.floor(d.getTime() / 1000);
91
+
92
+ update();
93
+ };
94
+
95
+ let getTime = function (tConvert = time()) {
96
+ let d = new Date(tConvert * 1000);
97
+ let h = d.getHours();
98
+ let m = d.getMinutes();
99
+
100
+ return h + ":" + (m < 10 ? "0" : "") + m;
101
+ };
102
+
103
+ let timeUnit = function () {
104
+ if (demo) {
105
+ return 0.1;
106
+ }
107
+ else {
108
+ return 60;
109
+ }
110
+ };
111
+
112
+ let delayStart = function (startTime) {
113
+ let tNow = time();
114
+ let tUnit = timeUnit();
115
+
116
+ if (demo) {
117
+ // Convert virtual duration to real duration
118
+ return mod(startTime - tNow, demoEnd - demoStart) / (demoEnd - demoStart) * (durDemo + 2*durPause);
119
+ }
120
+ else {
121
+ if (startTime > tNow) {
122
+ return startTime - tNow;
123
+ }
124
+ else {
125
+ // Start on the unit
126
+ return (tUnit - (tNow % tUnit));
127
+ }
128
+ }
129
+ };
130
+
131
+ let toggleDemo = function () {
132
+ demo = !demo;
133
+ resetTime();
134
+ };
135
+
136
+ let demoOn = function () {
137
+ return demo;
138
+ };
139
+
140
+ let updateLive = function () {
141
+ let tNow = time();
142
+ let liveShow = document.getElementsByClassName('live-show');
143
+ let liveHide = document.getElementsByClassName('live-hide');
144
+ let liveTime = document.getElementsByClassName('live-time');
145
+ let livePast = document.getElementsByClassName('live-past');
146
+
147
+ for (let i = 0; i < liveShow.length; i++) {
148
+ let tStart = liveShow[i].dataset.start;
149
+ let tEnd = liveShow[i].dataset.end;
150
+
151
+ if (tNow >= tStart && tNow < tEnd) {
152
+ // Show when active
153
+ liveShow[i].classList.remove('d-none');
154
+ }
155
+ else if (!liveShow[i].classList.contains('d-none')) {
156
+ // Hide otherwise
157
+ liveShow[i].classList.add('d-none');
158
+ }
159
+ }
160
+
161
+ for (let i = 0; i < liveHide.length; i++) {
162
+ let tStart = liveHide[i].dataset.start;
163
+ let tEnd = liveHide[i].dataset.end;
164
+
165
+ if (tNow >= tStart && tNow < tEnd) {
166
+ // Hide when active
167
+ if (!liveHide[i].classList.contains('d-none')) {
168
+ liveHide[i].classList.add('d-none');
169
+ }
170
+ }
171
+ else {
172
+ // Show otherwise
173
+ liveHide[i].classList.remove('d-none');
174
+ }
175
+ }
176
+
177
+ for (let i = 0; i < liveTime.length; i++) {
178
+ let t = liveTime[i].dataset.time;
179
+ if (typeof t == "undefined") {
180
+ break;
181
+ }
182
+ let tRel = tNow - t;
183
+
184
+ let tStr;
185
+ if (tRel >= -60 && tRel < 0) {
186
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.soon | default: "soon" }}';
187
+ }
188
+ else if (tRel >= 0 && tRel < 60) {
189
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.now | default: "now" }}';
190
+ }
191
+ else {
192
+ if (tRel < 0) {
193
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.in | default: "in" }} ';
194
+ }
195
+ else {
196
+ tStr = '{{ site.data.lang[site.conference.lang].live.time.since | default: "since" }} ';
197
+ }
198
+ tRel = Math.abs(tRel);
199
+
200
+ let dWeeks = Math.floor(tRel / (7*24*60*60));
201
+ let dDays = Math.floor(tRel / (24*60*60));
202
+ let dHours = Math.floor(tRel / (60*60));
203
+ let dMins = Math.floor(tRel / (60));
204
+ if (dWeeks > 4) {
205
+ break;
206
+ }
207
+ else if (dWeeks > 1) {
208
+ tStr += dWeeks +' {{ site.data.lang[site.conference.lang].live.time.weeks | default: "weeks" }}';
209
+ }
210
+ else if (dWeeks == 1) {
211
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.week | default: "week" }}';
212
+ }
213
+ else if (dDays > 1) {
214
+ tStr += dDays +' {{ site.data.lang[site.conference.lang].live.time.days | default: "days" }}';
215
+ }
216
+ else if (dDays == 1) {
217
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.day | default: "day" }}';
218
+ }
219
+ else if (dHours > 1) {
220
+ tStr += dHours +' {{ site.data.lang[site.conference.lang].live.time.hours | default: "hours" }}';
221
+ }
222
+ else if (dHours == 1) {
223
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.hour | default: "hour" }}';
224
+ }
225
+ else if (dMins > 1) {
226
+ tStr += dMins +' {{ site.data.lang[site.conference.lang].live.time.minutes | default: "minutes" }}';
227
+ }
228
+ else {
229
+ tStr += '1 {{ site.data.lang[site.conference.lang].live.time.minute | default: "minute" }}';
230
+ }
231
+ }
232
+
233
+ liveTime[i].innerHTML = tStr;
234
+ }
235
+
236
+ for (let i = 0; i < livePast.length; i++) {
237
+ let t = livePast[i].dataset.time;
238
+ if (typeof t == "undefined") {
239
+ break;
240
+ }
241
+ let tRel = tNow - t;
242
+
243
+ if (tRel < 0) {
244
+ // Grey out when in past
245
+ if (!livePast[i].classList.contains('text-secondary')) {
246
+ livePast[i].classList.add('text-secondary');
247
+ }
248
+ }
249
+ else {
250
+ // Show normal otherwise
251
+ livePast[i].classList.remove('text-secondary');
252
+ }
253
+ }
254
+
255
+ if (tNow > confEnd && !demo) {
256
+ // Cancel timer after program is over
257
+ stopUpdateLive();
258
+ }
259
+ };
260
+
261
+ let startUpdateLive = function () {
262
+ stopUpdateLive();
263
+ updateLive();
264
+
265
+ if (demo) {
266
+ // Immediate start required since delayStart would wait for next wrap around
267
+ liveTimer = setInterval(updateLive, timeUnit() * 1000);
268
+ }
269
+ else {
270
+ setTimeout(function() {
271
+ liveTimer = setInterval(updateLive, timeUnit() * 1000);
272
+ updateLive();
273
+ }, delayStart(confStart) * 1000);
274
+ }
275
+ };
276
+
277
+ let stopUpdateLive = function () {
278
+ if (typeof liveTimer !== "undefined") {
279
+ clearInterval(liveTimer);
280
+ }
281
+ };
282
+
283
+ {% if site.conference.live.streaming -%}
284
+ {% include js/conference-data.js %}
285
+
286
+ let streamModal;
287
+
288
+ let getRoom = function (roomName) {
289
+ if (roomName in rooms) {
290
+ return rooms[roomName];
291
+ }
292
+ else {
293
+ return rooms[Object.keys(rooms)[0]];
294
+ }
295
+ };
296
+
297
+ let preStartStream = function (roomName) {
298
+ let room = getRoom(roomName);
299
+
300
+ streamModal.find('iframe').attr('src', '');
301
+ streamModal.find('iframe').addClass('d-none');
302
+ streamModal.find('#stream-placeholder > div').text('{{ site.data.lang[site.conference.lang].live.pre_stream | default: "Live stream has not started yet." }}');
303
+ streamModal.find('#stream-placeholder').addClass('d-flex');
304
+
305
+ if (typeof streamTimer !== "undefined") {
306
+ clearInterval(streamTimer);
307
+ }
308
+ if (!freezeTime) {
309
+ streamTimer = setTimeout(activeStream, delayStart(room.start) * 1000, roomName);
310
+ }
311
+ };
312
+
313
+ let activeStream = function (roomName) {
314
+ let room = getRoom(roomName);
315
+
316
+ streamModal.find('iframe').attr('src', room.href);
317
+ streamModal.find('#stream-placeholder').addClass('d-none').removeClass('d-flex');
318
+ streamModal.find('iframe').removeClass('d-none');
319
+
320
+ if (typeof streamTimer !== "undefined") {
321
+ clearInterval(streamTimer);
322
+ }
323
+ if (!freezeTime) {
324
+ streamTimer = setTimeout(postEndStream, delayStart(room.end) * 1000, roomName);
325
+ }
326
+ };
327
+
328
+ let postEndStream = function (roomName) {
329
+ let room = getRoom(roomName);
330
+
331
+ streamModal.find('iframe').attr('src', '');
332
+ streamModal.find('iframe').addClass('d-none');
333
+ streamModal.find('#stream-placeholder > div').text('{{ site.data.lang[site.conference.lang].live.post_stream | default: "Live stream has ended." }}');
334
+ streamModal.find('#stream-placeholder').addClass('d-flex');
335
+
336
+ if (typeof streamTimer !== "undefined") {
337
+ clearInterval(streamTimer);
338
+ }
339
+ if (!freezeTime && demo) {
340
+ streamTimer = setTimeout(preStartStream, delayStart(demoStart) * 1000, roomName);
341
+ }
342
+ };
343
+
344
+ let setStreamInfo = function (roomName) {
345
+ let timeNow = time();
346
+ let talksHere = talks[roomName];
347
+ let talkNow;
348
+
349
+ if (typeof streamInfoTimer !== "undefined") {
350
+ clearInterval(streamInfoTimer);
351
+ }
352
+
353
+ if (typeof talksHere !== "undefined") {
354
+ if (timeNow < talksHere[talksHere.length-1].end) {
355
+ for (var i = 0; i < talksHere.length; i++) {
356
+ if (timeNow < talksHere[i].end && timeNow >= talksHere[i].start - talkAnnounce*60) {
357
+ talkNow = talksHere[i];
358
+ break;
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ if (typeof talkNow !== "undefined") {
365
+ document.getElementById('stream-info').dataset.time = talkNow.start;
366
+ document.getElementById('stream-info-time').dataset.time = talkNow.start;
367
+ updateLive();
368
+
369
+ streamModal.find('#stream-info-color').removeClass(function (index, className) {
370
+ return (className.match(/(^|\s)border-soft-\S+/g) || []).join(' ');
371
+ });
372
+ streamModal.find('#stream-info-color').addClass('border-soft-' + talkNow.color);
373
+
374
+ streamModal.find('#stream-info-talk').text(talkNow.name).attr('href', talkNow.href);
375
+
376
+ let speakerStr = '';
377
+ for (var i = 0; i < talkNow.speakers.length; i++) {
378
+ let speaker = speakers[talkNow.speakers[i]];
379
+ if (speaker.href == '') {
380
+ speakerStr += speaker.name +', '
381
+ }
382
+ else {
383
+ speakerStr += '<a class="text-reset" href="'+ speaker.href +'">'+ speaker.name +'</a>, ';
384
+ }
385
+ }
386
+ speakerStr = speakerStr.slice(0, -2);
387
+ streamModal.find('#stream-info-speakers').html(speakerStr);
388
+
389
+ streamModal.find('#stream-info').removeClass('d-none');
390
+
391
+ if (!freezeTime) {
392
+ streamInfoTimer = setTimeout(setStreamInfo, delayStart(talkNow.end) * 1000, roomName);
393
+ }
394
+ }
395
+ else {
396
+ streamModal.find('#stream-info').addClass('d-none');
397
+ }
398
+ };
399
+
400
+ let setStream = function (roomName) {
401
+ streamModal.find('.modal-footer .btn').removeClass('active');
402
+ streamModal.find('#stream-select').selectedIndex = -1;
403
+
404
+ let room = getRoom(roomName);
405
+ roomName = room.name;
406
+ let tNow = time();
407
+
408
+ if (tNow < room.start) {
409
+ preStartStream(roomName);
410
+ }
411
+ else if (tNow > room.end) {
412
+ postEndStream(roomName);
413
+ }
414
+ else {
415
+ activeStream(roomName);
416
+ }
417
+ setStreamInfo(roomName);
418
+
419
+ streamModal.find('#stream-button' + room.id).addClass('active');
420
+ streamModal.find('#stream-select').selectedIndex = room.id;
421
+ };
422
+
423
+ let updateStream = function () {
424
+ if (streamModal.hasClass('show')) {
425
+ let activeButton = streamModal.find('.modal-footer .btn.active');
426
+ let roomName = activeButton.data('room');
427
+
428
+ if (typeof roomName !== "undefined") {
429
+ setStream(roomName);
430
+ }
431
+ }
432
+ };
433
+
434
+ let stopUpdateStream = function () {
435
+ if (typeof streamTimer !== "undefined") {
436
+ clearInterval(streamTimer);
437
+ }
438
+ if (typeof streamInfoTimer !== "undefined") {
439
+ clearInterval(streamInfoTimer);
440
+ }
441
+ };
442
+
443
+ let hideModal = function (event) {
444
+ streamModal.find('iframe').attr('src', '');
445
+ streamModal.find('.modal-footer .btn').removeClass('active');
446
+ streamModal.find('#stream-select').selectedIndex = -1;
447
+ };
448
+
449
+ let setupStream = function () {
450
+ streamModal = $('#stream-modal');
451
+
452
+ streamModal.on('show.bs.modal', function (event) {
453
+ let button = $(event.relatedTarget);
454
+ let roomName = button.data('room');
455
+ setStream(roomName);
456
+ });
457
+ streamModal.on('hide.bs.modal', function (event) {
458
+ hideModal(event);
459
+ });
460
+
461
+ streamModal.find('.modal-footer .btn').on('click', function(event) {
462
+ event.preventDefault();
463
+
464
+ let roomName = $(this).data('room');
465
+ setStream(roomName);
466
+ });
467
+
468
+ streamModal.find('#stream-select').on('change', function(event) {
469
+ event.preventDefault();
470
+
471
+ let roomName = $(this).children('option:selected').text();
472
+ setStream(roomName);
473
+ });
474
+ };
475
+
476
+ let setup = function () {
477
+ startUpdateLive();
478
+ setupStream();
479
+ };
480
+
481
+ let update = function () {
482
+ updateLive();
483
+ updateStream();
484
+ };
485
+
486
+ let startUpdate = function () {
487
+ startUpdateLive();
488
+ updateStream();
489
+ };
490
+
491
+ let stopUpdate = function () {
492
+ stopUpdateLive();
493
+ stopUpdateStream();
494
+ };
495
+
496
+ {%- else -%}
497
+
498
+ let setup = function () {
499
+ startUpdateLive();
500
+ };
501
+
502
+ let update = function () {
503
+ updateLive();
504
+ };
505
+
506
+ let startUpdate = function () {
507
+ startUpdateLive();
508
+ };
509
+
510
+ let stopUpdate = function () {
511
+ stopUpdateLive();
512
+ };
513
+
514
+ {%- endif %}
515
+
516
+ return {
517
+ init: setup,
518
+
519
+ pauseTime: pauseTime,
520
+ continueTime: continueTime,
521
+ resetTime: resetTime,
522
+ setTime: setTime,
523
+ getTime: getTime,
524
+
525
+ toggleDemo: toggleDemo,
526
+ demo: demoOn,
527
+ durDemo: durDemo,
528
+ durPause: durPause
529
+ };
530
+
531
+ })();
532
+
533
+ window.conference.live.init();