@oat-sa/tao-core-ui 1.5.4 → 1.6.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.
Files changed (43) hide show
  1. package/dist/ckeditor/ckConfigurator.js +9 -1
  2. package/dist/mediaEditor/mediaEditorComponent.js +5 -3
  3. package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +46 -25
  4. package/dist/mediaplayer/css/player.css +104 -14
  5. package/dist/mediaplayer/css/player.css.map +1 -1
  6. package/dist/mediaplayer/players/html5.js +767 -0
  7. package/dist/mediaplayer/players/youtube.js +470 -0
  8. package/dist/mediaplayer/players.js +35 -0
  9. package/dist/mediaplayer/support.js +134 -0
  10. package/dist/mediaplayer/utils/reminder.js +198 -0
  11. package/dist/mediaplayer/utils/timeObserver.js +149 -0
  12. package/dist/mediaplayer/youtubeManager.js +177 -0
  13. package/dist/mediaplayer.js +1251 -1912
  14. package/dist/previewer.js +25 -19
  15. package/package.json +1 -1
  16. package/scss/basic.scss +1 -0
  17. package/scss/inc/_jquery.nouislider.scss +254 -0
  18. package/src/ckeditor/ckConfigurator.js +10 -1
  19. package/src/itemButtonList/css/item-button-list.css +225 -0
  20. package/src/itemButtonList/css/item-button-list.css.map +1 -0
  21. package/src/mediaEditor/mediaEditorComponent.js +25 -26
  22. package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +83 -63
  23. package/src/mediaplayer/css/player.css +104 -14
  24. package/src/mediaplayer/css/player.css.map +1 -1
  25. package/src/mediaplayer/players/html5.js +564 -0
  26. package/src/mediaplayer/players/youtube.js +323 -0
  27. package/src/mediaplayer/players.js +29 -0
  28. package/src/mediaplayer/scss/player.scss +125 -16
  29. package/src/mediaplayer/support.js +126 -0
  30. package/src/mediaplayer/tpl/audio.tpl +6 -0
  31. package/src/mediaplayer/tpl/player.tpl +11 -32
  32. package/src/mediaplayer/tpl/source.tpl +1 -0
  33. package/src/mediaplayer/tpl/video.tpl +6 -0
  34. package/src/mediaplayer/tpl/youtube.tpl +1 -0
  35. package/src/mediaplayer/utils/reminder.js +184 -0
  36. package/src/mediaplayer/utils/timeObserver.js +143 -0
  37. package/src/mediaplayer/youtubeManager.js +161 -0
  38. package/src/mediaplayer.js +1217 -1901
  39. package/src/previewer.js +40 -33
  40. package/src/searchModal/css/advancedSearch.css +190 -0
  41. package/src/searchModal/css/advancedSearch.css.map +1 -0
  42. package/src/searchModal/css/searchModal.css +506 -0
  43. package/src/searchModal/css/searchModal.css.map +1 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * This program is free software; you can redistribute it and/or
3
+ * modify it under the terms of the GNU General Public License
4
+ * as published by the Free Software Foundation; under version 2
5
+ * of the License (non-upgradable).
6
+ *
7
+ * This program is distributed in the hope that it will be useful,
8
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ * GNU General Public License for more details.
11
+ *
12
+ * You should have received a copy of the GNU General Public License
13
+ * along with this program; if not, write to the Free Software
14
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
+ *
16
+ * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
17
+ */
18
+
19
+ import $ from 'jquery';
20
+ import eventifier from 'core/eventifier';
21
+ import support from 'ui/mediaplayer/support';
22
+ import youtubeManagerFactory from 'ui/mediaplayer/youtubeManager';
23
+ import youtubeTpl from 'ui/mediaplayer/tpl/youtube';
24
+
25
+ /**
26
+ * The polling interval used to update the progress bar while playing a YouTube video.
27
+ * Note : the YouTube API does not provide events to update this progress bar...
28
+ * @type {Number}
29
+ */
30
+ const youtubePolling = 100;
31
+
32
+ /**
33
+ * List of YouTube events that can be listened to for debugging
34
+ * @type {String[]}
35
+ */
36
+ const youtubeEvents = ['onApiChange', 'onError', 'onPlaybackQualityChange', 'onPlaybackRateChange', 'onStateChange'];
37
+
38
+ /**
39
+ * List of player events that can be listened to for debugging
40
+ * @type {String[]}
41
+ */
42
+ const playerEvents = ['end', 'error', 'pause', 'play', 'ready', 'resize', 'timeupdate'];
43
+
44
+ /**
45
+ * A local manager for Youtube players.
46
+ * Relies on https://developers.google.com/youtube/iframe_api_reference
47
+ * @type {Object}
48
+ */
49
+ const youtubeManager = youtubeManagerFactory();
50
+
51
+ /**
52
+ * Defines a player object dedicated to youtube media
53
+ * @param {jQuery} $container - Where to render the player
54
+ * @param {Object} config - The list of config entries
55
+ * @param {Array} config.sources - The list of media sources
56
+ * @param {Boolean} [config.debug] - Enables the debug mode
57
+ * @param {Number} [config.polling=100] - The polling interval used to update the progress bar while playing a YouTube video.
58
+ * @returns {Object} player
59
+ */
60
+ export default function youtubePlayerFactory($container, config = {}) {
61
+ const sources = config.sources || [];
62
+ const source = sources[0] || {};
63
+ const otherSources = sources.slice(1);
64
+
65
+ config.polling = config.polling || youtubePolling;
66
+
67
+ let $media;
68
+ let media;
69
+ let interval;
70
+ let destroyed;
71
+ let initWidth;
72
+ let initHeight;
73
+ let callbacks = [];
74
+
75
+ // eslint-disable-next-line
76
+ const debug = (action, ...args) => config.debug && window.console.log(`[youtube:${action}]`, ...args);
77
+
78
+ const queueMedia = (url, register) => {
79
+ const id = youtubeManager.extractYoutubeId(url);
80
+ if (id) {
81
+ if (media) {
82
+ register(id);
83
+ } else {
84
+ callbacks.push(() => register(id));
85
+ }
86
+ return true;
87
+ }
88
+ return false;
89
+ };
90
+
91
+ const player = {
92
+ init() {
93
+ $media = $(
94
+ youtubeTpl({
95
+ src: source.src,
96
+ id: youtubeManager.extractYoutubeId(source.src)
97
+ })
98
+ );
99
+ $container.append($media);
100
+ otherSources.forEach(source => this.addMedia(source.src));
101
+
102
+ media = null;
103
+ destroyed = false;
104
+
105
+ youtubeManager.add($media, this, {
106
+ controls: !support.canControl()
107
+ });
108
+
109
+ return true;
110
+ },
111
+
112
+ onReady(event) {
113
+ media = event.target;
114
+ $media = $(media.getIframe()); // the injected media placeholder is replaced by an iframe by the YouTube lib
115
+
116
+ if (!destroyed) {
117
+ // install debug logger
118
+ if (config.debug) {
119
+ debug('installed', media);
120
+ youtubeEvents.forEach(eventName =>
121
+ media.addEventListener(eventName, e => {
122
+ debug('media event', eventName, $media && $media.data('videoSrc'), e);
123
+ })
124
+ );
125
+ playerEvents.forEach(eventName => {
126
+ this.on(eventName, (...args) => {
127
+ debug('player event', eventName, $media && $media.data('videoSrc'), ...args);
128
+ });
129
+ });
130
+ }
131
+
132
+ if (initWidth && initHeight) {
133
+ this.setSize(initWidth, initHeight);
134
+ }
135
+
136
+ callbacks.forEach(cb => cb());
137
+ callbacks = [];
138
+
139
+ this.trigger('ready');
140
+ } else {
141
+ this.destroy();
142
+ }
143
+ },
144
+
145
+ onStateChange(event) {
146
+ this.stopPolling();
147
+
148
+ if (!destroyed) {
149
+ switch (event.data) {
150
+ // ended
151
+ case 0:
152
+ this.trigger('end');
153
+ break;
154
+
155
+ // playing
156
+ case 1:
157
+ this.trigger('play');
158
+ this.startPolling();
159
+ break;
160
+
161
+ // paused
162
+ case 2:
163
+ this.trigger('pause');
164
+ break;
165
+ }
166
+ }
167
+ },
168
+
169
+ stopPolling() {
170
+ if (interval) {
171
+ window.clearInterval(interval);
172
+ interval = null;
173
+ }
174
+ },
175
+
176
+ startPolling() {
177
+ interval = window.setInterval(() => this.trigger('timeupdate'), config.polling);
178
+ },
179
+
180
+ destroy() {
181
+ debug('api call', 'destroy');
182
+
183
+ destroyed = true;
184
+
185
+ this.stopPolling();
186
+ this.removeAllListeners();
187
+
188
+ if (media) {
189
+ youtubeEvents.forEach(ev => media.removeEventListener(ev));
190
+ media.destroy();
191
+ media = void 0;
192
+ } else {
193
+ youtubeManager.remove($media, this);
194
+ }
195
+
196
+ if ($media) {
197
+ $media.remove();
198
+ $media = void 0;
199
+ }
200
+ },
201
+
202
+ getMedia() {
203
+ debug('api call', 'getMedia', media);
204
+
205
+ return media;
206
+ },
207
+
208
+ getPosition() {
209
+ let position = 0;
210
+ if (media) {
211
+ position = media.getCurrentTime();
212
+ }
213
+
214
+ debug('api call', 'getPosition', position);
215
+ return position;
216
+ },
217
+
218
+ getDuration() {
219
+ let duration = 0;
220
+ if (media) {
221
+ duration = media.getDuration();
222
+ }
223
+
224
+ debug('api call', 'getDuration', duration);
225
+ return duration;
226
+ },
227
+
228
+ getVolume() {
229
+ let volume = 0;
230
+ if (media) {
231
+ volume = media.getVolume();
232
+ }
233
+
234
+ debug('api call', 'getVolume', volume);
235
+ return volume;
236
+ },
237
+
238
+ setVolume(volume) {
239
+ debug('api call', 'setVolume', volume);
240
+
241
+ if (media) {
242
+ media.setVolume(parseFloat(volume));
243
+ }
244
+ },
245
+
246
+ setSize(width, height) {
247
+ debug('api call', 'setSize', width, height);
248
+
249
+ this.trigger('resize', width, height);
250
+
251
+ if (!media) {
252
+ initWidth = width;
253
+ initHeight = height;
254
+ }
255
+ },
256
+
257
+ seek(time) {
258
+ debug('api call', 'seek', time);
259
+
260
+ if (media) {
261
+ media.seekTo(parseFloat(time), true);
262
+ }
263
+ },
264
+
265
+ play() {
266
+ debug('api call', 'play');
267
+
268
+ if (media) {
269
+ media.playVideo();
270
+ }
271
+ },
272
+
273
+ pause() {
274
+ debug('api call', 'pause');
275
+
276
+ if (media) {
277
+ media.pauseVideo();
278
+ }
279
+ },
280
+
281
+ stop() {
282
+ debug('api call', 'stop');
283
+
284
+ if (media) {
285
+ media.stopVideo();
286
+ this.trigger('end');
287
+ }
288
+ },
289
+
290
+ mute(state) {
291
+ debug('api call', 'mute', state);
292
+
293
+ if (media) {
294
+ media[state ? 'mute' : 'unMute']();
295
+ }
296
+ },
297
+
298
+ isMuted() {
299
+ let mute = false;
300
+ if (media) {
301
+ mute = media.isMuted();
302
+ }
303
+
304
+ debug('api call', 'isMuted', mute);
305
+ return mute;
306
+ },
307
+
308
+ addMedia(url) {
309
+ debug('api call', 'addMedia', url);
310
+
311
+ return queueMedia(url, id => media && media.cueVideoById(id));
312
+ },
313
+
314
+ setMedia(url) {
315
+ debug('api call', 'setMedia', url);
316
+
317
+ callbacks = [];
318
+ return queueMedia(url, id => media && media.loadVideoById(id));
319
+ }
320
+ };
321
+
322
+ return eventifier(player);
323
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * This program is free software; you can redistribute it and/or
3
+ * modify it under the terms of the GNU General Public License
4
+ * as published by the Free Software Foundation; under version 2
5
+ * of the License (non-upgradable).
6
+ *
7
+ * This program is distributed in the hope that it will be useful,
8
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ * GNU General Public License for more details.
11
+ *
12
+ * You should have received a copy of the GNU General Public License
13
+ * along with this program; if not, write to the Free Software
14
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
+ *
16
+ * Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
17
+ */
18
+
19
+ import html5PlayerFactory from 'ui/mediaplayer/players/html5';
20
+ import youtubePlayerFactory from 'ui/mediaplayer/players/youtube';
21
+
22
+ /**
23
+ * Defines the list of available players
24
+ */
25
+ export default {
26
+ audio: html5PlayerFactory,
27
+ video: html5PlayerFactory,
28
+ youtube: youtubePlayerFactory
29
+ };
@@ -12,13 +12,24 @@ $playerSliderBackground: $uiGeneralContentBg;
12
12
  $playerSliderColor: $darkBar;
13
13
  $playerSliderHandle: whiten($playerSliderColor, .4);
14
14
  $playerSliderHightlight: whiten($playerSliderColor, .2);
15
+ $controlsHeight: 36px;
15
16
 
16
17
  .mediaplayer {
17
18
  position: relative;
18
19
  @include simple-border($playerBorder);
19
20
  @include border-radius(2);
20
21
  background: $playerBackground;
22
+ max-width: 100%;
23
+ min-height: $controlsHeight;
24
+ min-width: 200px;
21
25
 
26
+ &.youtube {
27
+ .player {
28
+ width: 100%;
29
+ height: 0px;
30
+ padding-bottom: 56.25%; // 56.25% for widescreen 16:9 aspect ratio videos
31
+ }
32
+ }
22
33
  .icon-sound:before {
23
34
  @include icon-audio();
24
35
  }
@@ -32,12 +43,26 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
32
43
 
33
44
  .player {
34
45
  position: relative;
46
+ height: calc(100% - #{$controlsHeight});
47
+ width: 100%;
35
48
 
36
49
  iframe {
37
50
  pointer-events: none;
51
+ position: absolute;
52
+ left: 0;
53
+ top: 0;
54
+ width: 100%;
55
+ height: 100%
56
+ }
57
+
58
+ .media:not(.youtube) {
59
+ display: block;
60
+ width: 100%;
61
+ height: auto;
62
+ max-height: 100%;
38
63
  }
39
64
 
40
- .overlay {
65
+ .player-overlay {
41
66
  position: absolute;
42
67
  z-index: 1;
43
68
  top: 0;
@@ -53,10 +78,8 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
53
78
  z-index: 2;
54
79
  top: 50%;
55
80
  left: 50%;
56
- width: 64px;
57
- height: 64px;
81
+ transform: translate(-50%);
58
82
  margin-top: -32px;
59
- margin-left: -32px;
60
83
  text-align: center;
61
84
  text-decoration: none;
62
85
  display: none;
@@ -73,6 +96,30 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
73
96
  opacity: 0.6;
74
97
  }
75
98
  }
99
+
100
+ &.reload {
101
+ width: 100%;
102
+ font-size: 50px;
103
+ line-height: 30px;
104
+
105
+ &:hover {
106
+ .icon {
107
+ opacity: 1;
108
+ }
109
+ }
110
+
111
+ .icon {
112
+ opacity: 0.6;
113
+ background: none;
114
+ }
115
+ .message {
116
+ font-size: 20px;
117
+ }
118
+
119
+ .icon, .message {
120
+ text-shadow: 1px 0 0 #000, 0 -1px 0 #000, 0 1px 0 #000, -1px 0 0 #000;
121
+ }
122
+ }
76
123
  }
77
124
  }
78
125
 
@@ -84,6 +131,7 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
84
131
  table-layout: fixed;
85
132
  width: 100%;
86
133
  border-top: 1px solid $playerBorder;
134
+ height: $controlsHeight;
87
135
 
88
136
  .bar {
89
137
  display: table-row;
@@ -295,6 +343,43 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
295
343
  bottom: 0;
296
344
  }
297
345
  }
346
+
347
+ &.stalled {
348
+ .player {
349
+ .player-overlay {
350
+ [data-control="reload"] {
351
+ display: flex;
352
+ align-items: center;
353
+ background-color: #000;
354
+ margin: 0;
355
+ flex-wrap: wrap;
356
+ padding: 5px 5px 5px 50px;
357
+ text-align: left;
358
+ line-height: 2.3rem;
359
+ &.reload {
360
+ width: calc(100% + 2px);
361
+ font-size: 20px;
362
+ line-height: 20px;
363
+ min-height: 36px;
364
+
365
+ .icon {
366
+ text-shadow: none;
367
+ position: absolute;
368
+ left: 0;
369
+ font-size: 2rem;
370
+ font-weight: bold;
371
+ }
372
+
373
+ .message {
374
+ text-shadow: none;
375
+ font-size: 1.3rem;
376
+ margin-right: 5px;
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
298
383
  }
299
384
 
300
385
  &.ready {
@@ -303,17 +388,19 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
303
388
  }
304
389
 
305
390
  &.paused.canplay {
306
- .overlay {
391
+ .player-overlay {
307
392
  cursor: pointer;
308
393
  }
309
394
 
310
- .player:hover {
311
- [data-control="play"] {
312
- display: inline-block;
395
+ &:not(.audio) {
396
+ .player:hover {
397
+ [data-control="play"] {
398
+ display: inline-block;
399
+ }
313
400
  }
314
401
  }
315
402
 
316
- &.youtube.ended {
403
+ &.youtube.ended, &:not(.preview) {
317
404
  .player:hover {
318
405
  [data-control="play"] {
319
406
  display: none;
@@ -323,13 +410,15 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
323
410
  }
324
411
 
325
412
  &.playing.canpause {
326
- .overlay {
413
+ .player-overlay {
327
414
  cursor: pointer;
328
415
  }
329
416
 
330
- .player:hover {
331
- [data-control="pause"] {
332
- display: inline-block;
417
+ &:not(.audio) {
418
+ .player:hover {
419
+ [data-control="pause"] {
420
+ display: inline-block;
421
+ }
333
422
  }
334
423
  }
335
424
  }
@@ -371,7 +460,7 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
371
460
  pointer-events: inherit;
372
461
  }
373
462
  }
374
- .overlay {
463
+ .player-overlay {
375
464
  display: none !important;
376
465
  }
377
466
  .controls {
@@ -379,7 +468,7 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
379
468
  }
380
469
  }
381
470
 
382
- &.error {
471
+ &.error:not(.stalled) {
383
472
  .media, .controls {
384
473
  display: none;
385
474
  }
@@ -398,7 +487,7 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
398
487
  }
399
488
  }
400
489
 
401
- &.loading::before {
490
+ &.loading:not(.stalled)::before {
402
491
  @keyframes spinner {
403
492
  to { transform: rotate(360deg); }
404
493
  }
@@ -417,4 +506,24 @@ $playerSliderHightlight: whiten($playerSliderColor, .2);
417
506
  border-top-color: #07d;
418
507
  animation: spinner .6s linear infinite;
419
508
  }
509
+
510
+ &.stalled {
511
+ .video {
512
+ filter: blur(4px);
513
+ opacity: 0.4;
514
+ }
515
+ .player-overlay {
516
+ [data-control="reload"] {
517
+ display: inline-block;
518
+ }
519
+ }
520
+ }
521
+
522
+ &.video:not(.preview):not(.error) {
523
+ .player-overlay {
524
+ [data-control="start"] {
525
+ display: inline-block;
526
+ }
527
+ }
528
+ }
420
529
  }