@internetstiftelsen/styleguide 2.24.30-beta.0.1 → 2.24.30-beta.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ export default class VideoGuideSubtitles {
2
+ constructor(element, video) {
3
+ this.element = element;
4
+ this.video = video;
5
+ this.subtitlesBtn = element.querySelector('.js-subtitles-btn');
6
+ this.subtitlesContainer = element.querySelector('.js-subtitles-container');
7
+
8
+ this.init();
9
+ this.attach();
10
+ }
11
+
12
+ init() {
13
+ this.subtitles = this.video.textTracks.getTrackById('video-subtitles');
14
+ }
15
+
16
+ attach() {
17
+ this.subtitlesBtn.addEventListener('click', this.toggleSubtitles);
18
+ this.subtitles.addEventListener('cuechange', this.onCueChange);
19
+ }
20
+
21
+ clearSubtitles() {
22
+ this.subtitlesContainer.innerHTML = '';
23
+ }
24
+
25
+ onEnded = () => {
26
+ this.clearSubtitles();
27
+ };
28
+
29
+ onCueChange = () => {
30
+ const { activeCues } = this.subtitles;
31
+
32
+ if (activeCues.length > 0) {
33
+ this.subtitlesContainer.innerHTML = `<span>${activeCues[0].text}</span>`;
34
+ } else {
35
+ this.clearSubtitles();
36
+ }
37
+ };
38
+
39
+ toggleSubtitles = () => {
40
+ this.subtitlesBtn.classList.toggle('is-active');
41
+ this.subtitlesContainer.classList.toggle('is-visible');
42
+ };
43
+ }
@@ -0,0 +1,76 @@
1
+ export default class VideoGuideTimeline {
2
+ constructor(element, video) {
3
+ this.element = element;
4
+ this.video = video;
5
+ this.container = element.querySelector('.js-timeline-posts');
6
+ this.posts = Array.from(element.querySelectorAll('.js-timeline-post'));
7
+ this.toggleBtn = element.querySelector('.js-show-timelineposts');
8
+ this.headlineTpl = element.querySelector('[data-video-headline-tpl]');
9
+ this.headlineCache = {};
10
+
11
+ this.init();
12
+ this.attach();
13
+ }
14
+
15
+ init() {
16
+ this.meta = this.video.textTracks.getTrackById('video-metadata');
17
+ }
18
+
19
+ attach() {
20
+ this.meta.addEventListener('cuechange', this.onCueChange);
21
+
22
+ if (this.toggleBtn) {
23
+ this.toggleBtn.addEventListener('click', this.togglePosts);
24
+ }
25
+ }
26
+
27
+ togglePosts = () => {
28
+ this.toggleBtn.classList.toggle('is-toggeled');
29
+ this.container.classList.toggle('is-visible');
30
+ };
31
+
32
+ createImageHeadline(activeCue, post) {
33
+ if (activeCue.text in this.headlineCache) {
34
+ return;
35
+ }
36
+
37
+ let element = post.querySelector('[data-video-headline-tpl]');
38
+
39
+ if (post.querySelector('[data-video-headline-tpl]')) {
40
+ this.headlineCache[activeCue.text] = element;
41
+ return;
42
+ }
43
+
44
+ element = this.headlineTpl.cloneNode(true);
45
+ const prevHeadline = element.querySelector('h1');
46
+ const headline = document.createElement('h2');
47
+
48
+ headline.className = prevHeadline.className;
49
+ headline.innerHTML = activeCue.id;
50
+
51
+ prevHeadline.parentNode.replaceChild(headline, prevHeadline);
52
+ post.appendChild(element);
53
+
54
+ this.headlineCache[activeCue.text] = element;
55
+ }
56
+
57
+ onCueChange = () => {
58
+ const { activeCues } = this.meta;
59
+
60
+ if (activeCues.length > 0) {
61
+ const activeCue = activeCues[0];
62
+
63
+ this.posts.forEach((post) => {
64
+ if (post.dataset.id === activeCue.text) {
65
+ post.classList.add('is-current');
66
+
67
+ if (post.classList.contains('js-timeline-image') && activeCue.id) {
68
+ this.createImageHeadline(activeCue, post);
69
+ }
70
+ } else {
71
+ post.classList.remove('is-current');
72
+ }
73
+ });
74
+ }
75
+ };
76
+ }
@@ -42,7 +42,6 @@
42
42
  bottom: 0;
43
43
  z-index: 1;
44
44
  justify-content: center;
45
- opacity: 0;
46
45
  transition: opacity 0.25s ease-out;
47
46
  margin: 0;
48
47
  padding: 0;
@@ -52,6 +51,10 @@
52
51
  opacity: 1;
53
52
  }
54
53
 
54
+ &.is-current ~ & {
55
+ opacity: 0;
56
+ }
57
+
55
58
  img {
56
59
  border-radius: 0;
57
60
  object-fit: cover;
@@ -381,6 +384,7 @@
381
384
 
382
385
  @include e(buttons) {
383
386
  display: flex;
387
+ align-items: center;
384
388
  }
385
389
 
386
390
  @include e(button) {
@@ -393,7 +397,7 @@
393
397
 
394
398
  &:disabled,
395
399
  &:disabled:focus,
396
- &:not([class*="play"]):not([class*="subtitles"]):not([data-id]),
400
+ &:not([class*="play"]):not([class*="subtitles"]):not([class*="chapter"]),
397
401
  &:disabled:focus-visible {
398
402
  pointer-events: none;
399
403
  cursor: not-allowed;
@@ -418,6 +422,12 @@
418
422
  }
419
423
  }
420
424
 
425
+ @include e(countdown) {
426
+ font-size: $size-medium-plus;
427
+ padding-left: rhythm(1);
428
+ padding-right: rhythm(1);
429
+ }
430
+
421
431
  @include e(play-icon) {
422
432
  width: $icon-size-large * 1.5;
423
433
  height: $icon-size-large * 1.5;
@@ -505,4 +515,4 @@
505
515
  font-size: initial;
506
516
  }
507
517
  }
508
- }
518
+ }
@@ -0,0 +1,6 @@
1
+ export default function getCurrentCueIndex(target) {
2
+ const activeCue = target.activeCues[0];
3
+ const { cues } = target;
4
+
5
+ return Math.max(Array.prototype.indexOf.call(cues, activeCue), 0);
6
+ }
@@ -2,8 +2,8 @@ module.exports = {
2
2
  status: 'prototype',
3
3
 
4
4
  context: {
5
- videoSrc: '/assets/video/Internets-Historia-HEVC-h265-vp9-chrome.webm',
6
- videoSrc2: '/assets/video/Internets-Historia-HEVC-h265-HEVC-h265.mov',
5
+ videoSrc: 'https://ny.internetmuseum.se/app/uploads/2023/01/guidad-tur-nar-internet-kom-till-svenska-hem.mp4',
6
+ videoSrc2: 'https://ny.internetmuseum.se/app/uploads/2023/01/guidad-tur-nar-internet-kom-till-svenska-hem-vp9-chrome.webm',
7
7
 
8
8
  variants: [
9
9
  {
@@ -14,4 +14,4 @@ module.exports = {
14
14
  }
15
15
  ]
16
16
  }
17
- }
17
+ }
@@ -1,264 +1,68 @@
1
- const video = document.querySelector('.js-video-guide');
2
- const playBtn = document.querySelector('.js-play-btn');
3
- const playIcon = document.querySelector('.js-play-icon');
4
- const pauseIcon = document.querySelector('.js-pause-icon');
5
- const subtitlesBtn = document.querySelector('.js-subtitles-btn');
6
- const abortButton = document.querySelector('.js-abort-guide');
7
- const subtitlesElement = document.getElementById('video-subtitles');
8
- const subtitlesContainer = document.querySelector('.js-subtitles-container');
9
- const chapterTrackElement = document.getElementById('video-chapters');
10
- const trackMetadataElement = document.getElementById('video-metadata');
11
- const subtitlesTrack = (subtitlesElement === null) ? '' : subtitlesElement.track;
12
- const chapterTrack = (chapterTrackElement === null) ? '' : chapterTrackElement.track;
13
- const metadataTrack = (trackMetadataElement === null) ? '' : trackMetadataElement.track;
14
- const forwardsButton = document.querySelector('.js-next-chapter');
15
- const backwardsButton = document.querySelector('.js-previous-chapter');
16
- const timelinePosts = document.querySelectorAll('.js-timeline-post');
17
- const navigationButton = document.querySelector('.js-show-timelineposts');
18
- const timeLinePosts = document.querySelector('.js-timeline-posts');
19
- let currentChapter = 0;
20
- let manualStep = false;
1
+ import VideoGuideSubtitles from './VideoGuideSubtitles';
2
+ import VideoGuideTimeline from './VideoGuideTimeline';
3
+ import VideoGuidePlayback from './VideoGuidePlayback';
4
+
5
+ class VideoGuide {
6
+ constructor(element) {
7
+ this.element = element;
8
+ this.video = element.querySelector('.js-video-guide');
9
+ this.abortBtn = element.querySelector('.js-abort-guide');
10
+ this.playback = null;
11
+ this.subtitles = null;
12
+ this.timeline = null;
21
13
 
22
- // Set sessionStorage if video has started playing
23
- function saveState() {
24
- if (video.currentTime > 0) {
25
- const currentGuideURL = window.location.href;
26
- const currentGuideImage = video.dataset.featuredImage;
27
- sessionStorage.setItem('InmsCurrentTime', video.currentTime);
28
- sessionStorage.setItem('InmsDuration', video.duration); // Get totalt duration of video
29
- sessionStorage.setItem('InmsCurrentGuideURL', currentGuideURL);
30
- sessionStorage.setItem('InmsCurrentGuideImage', currentGuideImage);
31
- }
32
- }
33
-
34
- // Has src attributes been set already?
35
- if (video) {
36
- // Store current time in on page reload
37
- window.addEventListener('visibilitychange', saveState);
38
- window.addEventListener('beforeunload', saveState);
14
+ // Set all track elements to hidden mode to allow scripting
15
+ [].forEach.call(this.video.textTracks, (txtTrack) => {
16
+ txtTrack.mode = 'hidden';
17
+ });
39
18
 
40
- // Get value from sessionStorage if present
41
- if (sessionStorage.getItem('InmsCurrentTime')) {
42
- const videoCurrentTime = sessionStorage.getItem('InmsCurrentTime');
19
+ this.attach();
43
20
 
44
- if (videoCurrentTime > 0) {
45
- video.currentTime = videoCurrentTime;
21
+ // loadedmetadata might not be fired if the video is already loaded
22
+ if (this.video.readyState >= 1) {
23
+ this.onLoadedMetadata();
46
24
  }
47
25
  }
48
26
 
49
- // Toggle subtitles
50
- if (subtitlesBtn) {
51
- subtitlesBtn.addEventListener('click', () => {
52
- subtitlesBtn.classList.toggle('is-active');
53
- subtitlesContainer.classList.toggle('is-visible');
54
- });
27
+ attach() {
28
+ this.video.addEventListener('loadedmetadata', this.onLoadedMetadata);
29
+ this.video.addEventListener('play', this.onPlay);
30
+ this.video.addEventListener('pause', this.onPause);
31
+ this.video.addEventListener('ended', this.onEnded);
32
+ this.video.addEventListener('timeupdate', this.onTimeUpdate);
33
+ this.abortBtn.addEventListener('click', this.onAbort);
55
34
  }
56
35
 
57
- // Play / pause
58
- if (playBtn) {
59
- playBtn.addEventListener('click', () => {
60
- if (video.paused) {
61
- video.play();
62
- pauseIcon.classList.remove('is-hidden');
63
- playIcon.classList.add('is-hidden');
64
- manualStep = false;
65
- } else {
66
- video.pause();
67
- pauseIcon.classList.add('is-hidden');
68
- playIcon.classList.remove('is-hidden');
69
- manualStep = true;
70
- }
71
- });
72
-
73
- video.addEventListener('playing', () => {
74
- pauseIcon.classList.remove('is-hidden');
75
- playIcon.classList.add('is-hidden');
76
- manualStep = false;
77
- });
36
+ onLoadedMetadata = () => {
37
+ this.playback = new VideoGuidePlayback(this.element, this.video);
38
+ this.subtitles = new VideoGuideSubtitles(this.element, this.video);
39
+ this.timeline = new VideoGuideTimeline(this.element, this.video);
78
40
 
79
- video.addEventListener('ended', () => {
80
- pauseIcon.classList.add('is-hidden');
81
- playIcon.classList.remove('is-hidden');
82
- video.currentTime = 0;
83
- currentChapter = 1;
84
- manualStep = false;
85
- forwardsButton.removeAttribute('disabled');
86
- subtitlesContainer.innerHTML = '';
87
- sessionStorage.removeItem('InmsCurrentTime');
88
- sessionStorage.removeItem('InmsDuration');
89
- sessionStorage.removeItem('InmsCurrentGuideURL');
90
- sessionStorage.removeItem('InmsCurrentGuideImage');
91
- });
41
+ this.video.classList.remove('is-loading');
92
42
  }
93
43
 
94
- if (abortButton) {
95
- abortButton.addEventListener('click', (e) => {
96
- e.preventDefault();
97
- const urlTarget = abortButton.getAttribute('href');
98
- video.pause();
99
- video.currentTime = 0;
100
- forwardsButton.removeAttribute('disabled');
101
- currentChapter = 1;
102
- manualStep = false;
103
- sessionStorage.removeItem('InmsCurrentTime');
104
- sessionStorage.removeItem('InmsDuration');
105
- sessionStorage.removeItem('InmsCurrentGuideURL');
106
- sessionStorage.removeItem('InmsCurrentGuideImage');
107
- window.location.href = urlTarget;
108
- });
109
- }
110
-
111
- if (navigationButton) {
112
- navigationButton.addEventListener('click', () => {
113
- navigationButton.classList.toggle('is-toggeled');
114
- timeLinePosts.classList.toggle('is-visible');
44
+ dispatchEvent = (eventName) => {
45
+ [this.playback, this.subtitles, this.timeline].forEach((instance) => {
46
+ if (instance && eventName in instance) {
47
+ instance[eventName]();
48
+ }
115
49
  });
116
50
  }
117
- }
118
-
119
- function displayChapters() {
120
- if (chapterTrackElement && trackMetadataElement) {
121
- // Set all track elements to hidden mode to allow scripting
122
- [].forEach.call(video.textTracks, (txtTrack) => {
123
- txtTrack.mode = 'hidden';
124
- });
125
51
 
126
- if (chapterTrack.kind === 'chapters') {
127
- video.addEventListener('loadedmetadata', () => {
128
- // Loop through chapters and create chapter list
129
- // Let data load
130
- setTimeout(() => {
131
- video.classList.remove('is-loading');
52
+ onPlay = () => this.dispatchEvent('onPlay');
132
53
 
133
- // If not set in sessionStorgare, set first cue on forward button on page load
134
- if (!sessionStorage.getItem('InmsCurrentTime')) {
135
- forwardsButton.setAttribute('data-id', chapterTrack.cues[0].endTime);
136
- }
137
- }, 100);
138
- });
54
+ onPause = () => this.dispatchEvent('onPause');
139
55
 
140
- forwardsButton.addEventListener('click', () => {
141
- const dataId = forwardsButton.dataset.id;
142
- let currentTime = parseInt(dataId, 10);
143
- manualStep = true;
144
- currentTime += 1;
145
- video.currentTime = currentTime;
146
- currentChapter += 1;
147
- });
56
+ onEnded = () => this.dispatchEvent('onEnded');
148
57
 
149
- backwardsButton.addEventListener('click', () => {
150
- const dataId = backwardsButton.dataset.id;
151
- let lastTime = parseInt(dataId, 10);
152
- lastTime -= 1;
153
- video.currentTime = lastTime;
154
- forwardsButton.removeAttribute('disabled');
155
- manualStep = true;
156
- currentChapter -= 1;
58
+ onTimeUpdate = () => this.dispatchEvent('onTimeUpdate');
157
59
 
158
- if (video.currentTime <= 0) {
159
- backwardsButton.removeAttribute('data-id');
160
- }
161
- });
162
-
163
- chapterTrack.addEventListener('cuechange', () => {
164
- // Fire this whenever the chapters changes
165
- const myCues = chapterTrack.activeCues;
166
- if (myCues.length > 0) {
167
- const currentLocation = chapterTrack.activeCues[0].startTime;
168
- const nextLocation = chapterTrack.activeCues[0].endTime;
169
- const cueMatch = chapterTrack.activeCues[0].text;
170
- const matchingCueArray = document.querySelectorAll(`[rel="${currentLocation}"]`);
171
-
172
- // Set Forward and backwards buttons timestamps
173
- forwardsButton.setAttribute('data-id', nextLocation);
174
- backwardsButton.setAttribute('data-id', currentLocation);
175
-
176
- // Add chapter stepping even when video is played
177
- if (manualStep === false) {
178
- currentChapter += 1;
179
- }
180
-
181
- // Disable forwardsbutton when on last chapter
182
- if (currentChapter >= chapterTrack.cues.length) {
183
- forwardsButton.setAttribute('disabled', 'disabled');
184
- }
185
-
186
- // Handle current and watched items
187
- [].forEach.call(matchingCueArray, (matchingCue) => {
188
- const thisChapter = matchingCue;
189
- if (thisChapter.innerHTML === cueMatch) {
190
- const chapter = thisChapter;
191
-
192
- if (chapter === thisChapter) {
193
- // get the chapter <li> elements based on the currentLocation
194
- const locations = [].slice.call(chapter.closest('figure')
195
- .querySelectorAll('.js-chapters li'));
196
-
197
- [].forEach.call(locations, (location) => {
198
- // remove current classes from all items on page refresh
199
- location.classList.remove('is-current-item');
200
- location.querySelector('a').classList.remove('is-current');
201
- });
202
- chapter.parentNode.classList.add('is-current-item');
203
- chapter.classList.add('is-current');
204
- }
205
- }
206
- });
207
- }
208
- }, false);
209
-
210
- // Get timeline post IDs from metadata.vtt
211
- metadataTrack.addEventListener('cuechange', () => {
212
- const metadataCues = metadataTrack.activeCues;
213
- const chapterCues = chapterTrack.activeCues[0];
214
-
215
- if (metadataCues.length > 0) {
216
- const metadataCueMatch = metadataTrack.activeCues[0].text;
217
-
218
- [].forEach.call(timelinePosts, (timelinePost) => {
219
- timelinePost.classList.remove('is-current');
220
- });
221
-
222
- const idSelectors = document.querySelectorAll(`[data-id="${metadataCueMatch}"]`);
223
-
224
- [].forEach.call(idSelectors, (idSelector) => {
225
- idSelector.classList.add('is-current');
226
- });
227
-
228
- if (chapterCues) {
229
- const chapterStartTime = chapterCues.startTime;
230
-
231
- // Let stuff load
232
- let listElement;
233
- let timeOut = null;
234
-
235
- setTimeout(() => { listElement = document.getElementById(chapterStartTime); }, 100);
236
-
237
- timeOut = function wait(condition, callback) {
238
- if (typeof condition() !== 'undefined' && listElement) {
239
- listElement.classList.add('is-current-item');
240
- } else {
241
- setTimeout(() => {
242
- wait(condition, callback);
243
- }, 0);
244
- }
245
- };
246
- timeOut(() => listElement, () => { });
247
- }
248
- }
249
- }, false);
250
-
251
- // Get subtitles cues from subtitles.vtt
252
- subtitlesTrack.addEventListener('cuechange', () => {
253
- const subtitlesCues = subtitlesTrack.activeCues;
254
-
255
- if (subtitlesCues.length > 0) {
256
- const subtitlesCuesMatch = subtitlesTrack.activeCues[0].text;
257
- subtitlesContainer.innerHTML = `<span>${subtitlesCuesMatch}</span>`;
258
- }
259
- }, false);
260
- }
261
- }
60
+ onAbort = () => {
61
+ this.dispatchEvent('onAbort');
62
+ window.location.href = this.abortBtn.href;
63
+ };
262
64
  }
263
65
 
264
- displayChapters(chapterTrackElement);
66
+ const elements = document.querySelectorAll('[data-video-guide]');
67
+
68
+ elements.forEach((element) => new VideoGuide(element));
@@ -1,25 +0,0 @@
1
- WEBVTT
2
-
3
- 00:00:00.000 --> 00:00:39.000
4
- Kapitel 1 / 8: Introduktion
5
-
6
- 00:00:39.000 --> 00:01:51.500
7
- Kapitel 2 / 8: Idén om internet
8
-
9
- 00:01:51.500 --> 00:02:53.000
10
- Kapitel 3 / 8: Arpanet blir till
11
-
12
- 00:02:53.000 --> 00:03:27.000
13
- Kapitel 4 / 8: TCP/IP uppfinns
14
-
15
- 00:03:27.000 --> 00:03:56.500
16
- Kapitel 5 / 8: Sveriges första internetnod
17
-
18
- 00:03:56.500 --> 00:04:34.000
19
- Kapitel 6 / 8: Toppdomänen .se registreras
20
-
21
- 00:04:34.000 --> 00:05:17.000
22
- Kapitel 7 / 8: World Wide Web
23
-
24
- 00:05:17.000 --> 00:06:28.000
25
- Kapitel 8 / 8: Mosaic - den grafiska webbläsaren
@@ -1,28 +0,0 @@
1
- WEBVTT
2
-
3
- 00:00:00.000 --> 00:00:25.000
4
- 89108
5
-
6
- 00:00:25.000 --> 00:00:39.000
7
- 91027
8
-
9
- 00:00:39.000 --> 00:01:51.500
10
- 81345
11
-
12
- 00:01:51.500 --> 00:02:23.000
13
- 76810
14
-
15
- 00:02:23.000 --> 00:03:27.000
16
- 86454
17
-
18
- 00:03:27.000 --> 00:03:56.500
19
- 80127
20
-
21
- 00:03:56.500 --> 00:04:34.000
22
- 76325
23
-
24
- 00:04:34.000 --> 00:05:17.000
25
- 76280
26
-
27
- 00:05:17.000 --> 00:06:28.000
28
- 80725
Binary file
@@ -1,25 +0,0 @@
1
- WEBVTT
2
-
3
- 1
4
- 00:00:00.500 --> 00:00:02.000
5
- Lorem ipsum dolor sit amet, consectetur adipiscing elit
6
-
7
- 2
8
- 00:00:02.100 --> 00:00:03.000
9
- Phasellus eget lorem dui
10
-
11
- 3
12
- 00:00:03.100 --> 00:00:04.500
13
- Morbi ac nisi ac purus maximus semper a vitae diam fusce sit amet libero non tortor
14
-
15
- 4
16
- 00:00:04.600 --> 00:00:05.500
17
- Ut scelerisque sagittis justo vitae viverra
18
-
19
- 5
20
- 00:00:05.600 --> 00:00:06.500
21
- unc blandit justo sed odio tempus ultrices. Pellentesque sollicitudin justo.
22
-
23
- 6
24
- 00:00:06.100 --> 00:00:08.000
25
- Etiam ut volutpat tortor. Nunc iaculis luctus sapien.