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 +4 -4
- data/README.md +101 -30
- data/_includes/js/conference-data.js +87 -0
- data/_includes/js/conference-live.js +533 -0
- data/_includes/js/conference-map.js +31 -10
- data/_includes/js/conference-modal.js +38 -17
- data/_includes/js/conference.js +9 -1
- data/_includes/partials/checks.html +2 -2
- data/_includes/partials/footer.html +8 -3
- data/_includes/partials/get_conf_time.html +54 -0
- data/_includes/partials/get_link.html +8 -2
- data/_includes/partials/get_timestamp.html +4 -0
- data/_includes/partials/header.html +13 -0
- data/_includes/partials/live-modal.html +51 -0
- data/_includes/partials/live_button.html +19 -0
- data/_includes/partials/navbar.html +31 -0
- data/_includes/partials/show_room.html +2 -2
- data/_includes/partials/show_talk_duration.html +2 -2
- data/_includes/partials/show_talk_time.html +2 -2
- data/_layouts/delete_hidden.html +25 -0
- data/_layouts/home.html +56 -6
- data/_layouts/program.html +7 -47
- data/_layouts/room.html +9 -3
- data/_layouts/speaker.html +10 -8
- data/_layouts/talk-overview.html +8 -1
- data/_layouts/talk.html +20 -9
- data/_sass/conference.scss +23 -22
- data/assets/icons/live.svg +81 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbbe3c769ff41772597a229dc226732d868691b98d006c4767a569a4ef0b4d14
|
4
|
+
data.tar.gz: ffd668e698af13d076e91823355b69550a0e0486cee4b0be54c0db7f5c1a5916
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
10
|
-
|
11
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
130
|
+
### Automatic Build
|
108
131
|
|
109
|
-
|
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
|
-
###
|
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
|
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
|
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>
|
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
|
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();
|