@mindedge/vuetify-player 0.3.1 → 0.4.1

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.
@@ -1,71 +1,153 @@
1
1
  <template>
2
- <v-card>
3
- <v-card-actions class="justify-end">
4
- <v-tooltip top>
5
- <template v-slot:activator="{ on, attrs }">
6
- <v-btn
7
- color="primary"
8
- text
9
- v-bind="attrs"
10
- v-on="on"
11
- @click="onClickToggleParagraphView"
2
+ <v-card v-if="visibleState" :elevation="elevation">
3
+ <v-card-actions class="d-flex flex-wrap flex-row-reverse mb-0 pb-0">
4
+ <div class="d-flex ml-auto">
5
+ <v-tooltip v-if="!hideAutoscroll" top>
6
+ <template #activator="{ on, attrs }">
7
+ <div v-bind="attrs" v-on="on">
8
+ <v-switch
9
+ :input-value="autoscrollState"
10
+ color="primary"
11
+ text
12
+ class="d-flex align-self-center"
13
+ @click="onClickToggleAutoscroll"
14
+ >
15
+ <template #label>
16
+ <div v-if="autoscrollState">
17
+ <v-icon> mdi-lock-open-variant </v-icon>
18
+ <span class="sr-only">
19
+ {{
20
+ t(
21
+ language,
22
+ 'captions.autoscroll_enabled'
23
+ )
24
+ }}
25
+ </span>
26
+ </div>
27
+ <div v-else>
28
+ <v-icon>mdi-arrow-vertical-lock</v-icon>
29
+ <span class="sr-only">
30
+ {{
31
+ t(
32
+ language,
33
+ 'captions.autoscroll_disabled'
34
+ )
35
+ }}
36
+ </span>
37
+ </div>
38
+ </template>
39
+ </v-switch>
40
+ </div>
41
+ </template>
42
+ <span>{{
43
+ autoscrollState
44
+ ? t(language, 'captions.disable_autoscroll')
45
+ : t(language, 'captions.enable_autoscroll')
46
+ }}</span>
47
+ </v-tooltip>
48
+
49
+ <v-tooltip v-if="!hideParagraphView" top>
50
+ <template #activator="{ on, attrs }">
51
+ <v-btn
52
+ color="primary"
53
+ text
54
+ class="d-flex align-self-center"
55
+ v-bind="attrs"
56
+ v-on="on"
57
+ @click="onClickToggleParagraphView"
58
+ >
59
+ <v-icon>{{
60
+ paragraphViewState
61
+ ? 'mdi-closed-caption-outline'
62
+ : 'mdi-text-box-outline'
63
+ }}</v-icon>
64
+ <span class="sr-only">{{
65
+ paragraphViewState
66
+ ? t(language, 'captions.view_as_captions')
67
+ : t(language, 'captions.view_as_paragraph')
68
+ }}</span>
69
+ </v-btn></template
12
70
  >
13
- <v-icon>{{
14
- paragraphView
15
- ? 'mdi-closed-caption-outline'
16
- : 'mdi-text-box-outline'
17
- }}</v-icon>
18
- <span class="sr-only">{{
19
- paragraphView
20
- ? t(language, 'captions.view_as_captions')
21
- : t(language, 'captions.view_as_paragraph')
22
- }}</span>
23
- </v-btn></template
24
- >
25
- <span>{{
26
- paragraphView
27
- ? t(language, 'captions.view_as_captions')
28
- : t(language, 'captions.view_as_paragraph')
29
- }}</span>
30
- </v-tooltip>
31
- <v-tooltip top>
32
- <template v-slot:activator="{ on, attrs }">
33
- <v-btn
34
- color="primary"
35
- text
36
- v-bind="attrs"
37
- v-on="on"
38
- @click="onClickToggleExpand"
71
+ <span>{{
72
+ paragraphViewState
73
+ ? t(language, 'captions.view_as_captions')
74
+ : t(language, 'captions.view_as_paragraph')
75
+ }}</span>
76
+ </v-tooltip>
77
+
78
+ <v-tooltip v-if="!hideExpand" top>
79
+ <template #activator="{ on, attrs }">
80
+ <v-btn
81
+ color="primary"
82
+ text
83
+ class="d-flex align-self-center"
84
+ v-bind="attrs"
85
+ v-on="on"
86
+ @click="onClickToggleExpand"
87
+ >
88
+ <v-icon>{{
89
+ expandedState
90
+ ? 'mdi-arrow-collapse'
91
+ : 'mdi-arrow-expand'
92
+ }}</v-icon>
93
+ <span class="sr-only">{{
94
+ expandedState
95
+ ? t(language, 'captions.collapse')
96
+ : t(language, 'captions.expand')
97
+ }}</span>
98
+ </v-btn></template
99
+ >
100
+ <span>{{
101
+ expandedState
102
+ ? t(language, 'captions.collapse')
103
+ : t(language, 'captions.expand')
104
+ }}</span>
105
+ </v-tooltip>
106
+
107
+ <v-tooltip v-if="!hideClose" top>
108
+ <template #activator="{ on, attrs }">
109
+ <v-btn
110
+ color="primary"
111
+ text
112
+ class="d-flex align-self-center"
113
+ v-bind="attrs"
114
+ v-on="on"
115
+ @click="onClickClose"
116
+ >
117
+ <v-icon>mdi-close</v-icon>
118
+ </v-btn></template
39
119
  >
40
- <v-icon>{{
41
- expanded ? 'mdi-arrow-collapse' : 'mdi-arrow-expand'
42
- }}</v-icon>
43
- <span class="sr-only">{{
44
- expanded
45
- ? t(language, 'captions.collapse')
46
- : t(language, 'captions.expand')
47
- }}</span>
48
- </v-btn></template
120
+ <span>{{ t(language, 'captions.close') }}</span>
121
+ </v-tooltip>
122
+ </div>
123
+
124
+ <div class="d-flex flex-grow-1">
125
+ <v-text-field
126
+ id="captions-search"
127
+ v-model="search"
128
+ :label="t(language, 'captions.search')"
129
+ append-icon="mdi-magnify"
130
+ class="ml-2 mr-12"
131
+ clearable
49
132
  >
50
- <span>{{
51
- expanded
52
- ? t(language, 'captions.collapse')
53
- : t(language, 'captions.expand')
54
- }}</span>
55
- </v-tooltip>
133
+ </v-text-field>
134
+ </div>
56
135
  </v-card-actions>
57
- <v-card-text>
136
+ <v-card-text class="mt-0 pt-0">
137
+ <span v-if="search && !filteredCues.length" class="caption">
138
+ {{ t(language, 'captions.none_found', [search]) }}
139
+ </span>
58
140
  <v-list ref="captionList" :class="captionsList">
59
141
  <v-list-item-group v-model="captionIndex">
60
142
  <v-list-item
61
143
  ref="captionItems"
62
- v-for="(cue, index) in cues"
144
+ v-for="(cue, index) in filteredCues"
63
145
  :key="index"
64
- :two-line="expanded"
146
+ :two-line="expandedState"
65
147
  @click="onCueClick(cue.startTime)"
66
148
  >
67
- <template v-if="!expanded">
68
- <v-list-item-icon v-if="!paragraphView">
149
+ <template v-if="!expandedState">
150
+ <v-list-item-icon v-if="!paragraphViewState">
69
151
  <v-icon
70
152
  >{{
71
153
  index === captionIndex
@@ -80,7 +162,7 @@
80
162
  class="caption-text"
81
163
  ></v-list-item-title>
82
164
  </v-list-item-content>
83
- <v-list-item-action v-if="!paragraphView">
165
+ <v-list-item-action v-if="!paragraphViewState">
84
166
  <span aria-hidden="true">
85
167
  {{
86
168
  filters.playerShortDuration(
@@ -94,13 +176,15 @@
94
176
  </span>
95
177
  </v-list-item-action>
96
178
  </template>
97
- <template v-if="expanded">
179
+ <template v-if="expandedState">
98
180
  <v-list-item-content>
99
181
  <v-list-item-title
100
182
  v-html="cue.rawText || cue.text"
101
183
  class="caption-text"
102
184
  ></v-list-item-title>
103
- <v-list-item-subtitle v-if="!paragraphView">
185
+ <v-list-item-subtitle
186
+ v-if="!paragraphViewState"
187
+ >
104
188
  <v-icon
105
189
  >{{
106
190
  index === captionIndex
@@ -139,10 +223,43 @@ export default {
139
223
  props: {
140
224
  value: { type: [Object, Array], required: true },
141
225
  language: { type: String, required: false, default: 'en-US' },
226
+ expanded: { type: Boolean, required: false, default: undefined },
227
+ hideExpand: { type: Boolean, required: false, default: true },
228
+ paragraphView: { type: Boolean, required: false, default: undefined },
229
+ hideParagraphView: { type: Boolean, required: false, default: false },
230
+ autoscroll: { type: Boolean, required: false, default: undefined },
231
+ hideAutoscroll: { type: Boolean, required: false, default: false },
232
+ visible: { type: Boolean, required: false, default: undefined },
233
+ hideClose: { type: Boolean, required: false, default: false },
234
+ elevation: { type: [Number, String], required: false, default: 2 },
142
235
  },
236
+ emits: [
237
+ 'click:cue',
238
+ 'click:expand',
239
+ 'click:paragraph',
240
+ 'click:autoscroll',
241
+ 'click:close',
242
+ 'update:expanded',
243
+ 'update:paragraph-view',
244
+ 'update:autoscroll',
245
+ 'update:visible',
246
+ ],
143
247
  computed: {
248
+ filteredCues() {
249
+ // Cues are an object with keys of 0,1,2,3...
250
+ const cues = Object.values(this.cues)
251
+ if (this.search !== '') {
252
+ return cues.filter((c) =>
253
+ c.text
254
+ .toLowerCase()
255
+ .includes((this.search || '').toLowerCase())
256
+ )
257
+ } else {
258
+ return cues
259
+ }
260
+ },
144
261
  captionsList() {
145
- return !this.expanded
262
+ return !this.expandedState
146
263
  ? 'captions-list captions-list--state-collapsed'
147
264
  : 'captions-list captions-list--state-expanded'
148
265
  },
@@ -150,12 +267,12 @@ export default {
150
267
  // Normal cues view
151
268
  if (
152
269
  typeof this.captions.cues !== 'undefined' &&
153
- !this.paragraphView
270
+ !this.paragraphViewState
154
271
  ) {
155
272
  return this.captions.cues
156
273
  } else if (
157
274
  typeof this.captions.cues !== 'undefined' &&
158
- this.paragraphView
275
+ this.paragraphViewState
159
276
  ) {
160
277
  // Paragraph view
161
278
  let cues = this.captions.cues
@@ -182,20 +299,23 @@ export default {
182
299
  }
183
300
 
184
301
  // Create a new paragraph every 3 sentences
185
- if (puncuationCount > 3) {
186
- // Find the first puncuation and include it in the slice
302
+ if (
303
+ puncuationCount > 3 &&
304
+ typeof cues[i + 1] !== 'undefined'
305
+ ) {
306
+ // Find the first puncuation and include it in the slice so the NEXT paragraph doesn't start mid sentence
187
307
  const breakIndex = cues[i].text.search(/[.?!]/) + 1
188
308
 
189
309
  // Append the first part to the previous paragraph so it ends on a period
190
310
  paragraphs[paragraphs.length - 1].text +=
191
- ' ' + cues[i].text.slice(0, breakIndex)
311
+ ' ' + cues[i].text.slice(0, breakIndex).trim()
192
312
 
193
313
  // Use `new VTTCue` to break the reference. Otherwise the below appends will duplicate text
194
314
  // Also grab from the breakIndex afterwards to get the potential next sentence
195
315
  paragraphs.push(
196
316
  new VTTCue(
197
- cues[i].startTime,
198
- cues[i].endTime,
317
+ cues[i + 1].startTime,
318
+ cues[i + 1].endTime,
199
319
  cues[i].text.slice(breakIndex).trim()
200
320
  )
201
321
  )
@@ -215,15 +335,70 @@ export default {
215
335
  return []
216
336
  }
217
337
  },
338
+ expandedState: {
339
+ get() {
340
+ if (typeof this.expanded !== 'undefined') {
341
+ return this.expanded
342
+ } else {
343
+ return this.localExpanded
344
+ }
345
+ },
346
+ set(v) {
347
+ this.localExpanded = v
348
+ this.$emit('update:expanded', v)
349
+ },
350
+ },
351
+ paragraphViewState: {
352
+ get() {
353
+ if (typeof this.paragraphView !== 'undefined') {
354
+ return this.paragraphView
355
+ } else {
356
+ return this.localParagraphView
357
+ }
358
+ },
359
+ set(v) {
360
+ this.$emit('update:paragraph-view', v)
361
+ this.localParagraphView = v
362
+ },
363
+ },
364
+ autoscrollState: {
365
+ get() {
366
+ if (typeof this.autoscroll !== 'undefined') {
367
+ return this.autoscroll
368
+ } else {
369
+ return this.localAutoscroll
370
+ }
371
+ },
372
+ set(v) {
373
+ this.$emit('update:autoscroll', v)
374
+ this.localAutoscroll = v
375
+ },
376
+ },
377
+ visibleState: {
378
+ get() {
379
+ if (typeof this.visible !== 'undefined') {
380
+ return this.visible
381
+ } else {
382
+ return this.localVisible
383
+ }
384
+ },
385
+ set(v) {
386
+ this.$emit('update:visible', v)
387
+ this.localVisible = v
388
+ },
389
+ },
218
390
  },
219
391
  data() {
220
392
  return {
221
393
  t,
222
394
  filters,
395
+ search: '',
223
396
  captions: {},
224
397
  captionIndex: 0,
225
- expanded: false,
226
- paragraphView: false,
398
+ localExpanded: false,
399
+ localParagraphView: false,
400
+ localAutoscroll: true,
401
+ localVisible: true,
227
402
  }
228
403
  },
229
404
  watch: {
@@ -235,6 +410,11 @@ export default {
235
410
  },
236
411
  },
237
412
  },
413
+
414
+ mounted() {
415
+ this.captions = this.value
416
+ this.captionIndex = this.currentCue(this.captions)
417
+ },
238
418
  methods: {
239
419
  currentCue(captions) {
240
420
  let currentIndex = 0
@@ -242,10 +422,13 @@ export default {
242
422
  if (
243
423
  typeof captions.cues !== 'undefined' &&
244
424
  typeof captions.activeCues !== 'undefined' &&
245
- captions.activeCues.length
425
+ captions.activeCues.length &&
426
+ this.filteredCues.length
246
427
  ) {
247
- for (let i = 0; i < captions.cues.length; i++) {
248
- const cue = captions.cues[i]
428
+ currentIndex = -1
429
+ // Loop over the filtered cues and see if we can find the index
430
+ for (let i = 0; i < this.filteredCues.length; i++) {
431
+ const cue = this.filteredCues[i]
249
432
  if (captions.activeCues[0].startTime === cue.startTime) {
250
433
  currentIndex = i
251
434
  }
@@ -258,6 +441,7 @@ export default {
258
441
  // If the captions ref and index is available and the list ref is available
259
442
  // Auto-scroll the list to the current caption
260
443
  if (
444
+ this.autoscrollState &&
261
445
  this.$refs.captionItems &&
262
446
  this.$refs.captionItems[currentIndex] &&
263
447
  this.$refs.captionItems[currentIndex].$el &&
@@ -274,17 +458,21 @@ export default {
274
458
  this.$emit('click:cue', time)
275
459
  },
276
460
  onClickToggleExpand() {
277
- this.expanded = !this.expanded
278
- this.$emit('click:expand', this.expanded)
461
+ this.expandedState = !this.expandedState
462
+ this.$emit('click:expand', this.expandedState)
279
463
  },
280
464
  onClickToggleParagraphView() {
281
- this.paragraphView = !this.paragraphView
282
- this.$emit('click:paragraph', this.paragraphView)
465
+ this.paragraphViewState = !this.paragraphViewState
466
+ this.$emit('click:paragraph-view', this.paragraphViewState)
467
+ },
468
+ onClickToggleAutoscroll() {
469
+ this.autoscrollState = !this.autoscrollState
470
+ this.$emit('click:autoscroll', !this.autoscroll)
471
+ },
472
+ onClickClose() {
473
+ this.visibleState = false
474
+ this.$emit('click:close')
283
475
  },
284
- },
285
- mounted() {
286
- this.captions = this.value
287
- this.captionIndex = this.currentCue(this.captions)
288
476
  },
289
477
  }
290
478
  </script>
@@ -294,28 +482,10 @@ export default {
294
482
  overflow-y: scroll;
295
483
  }
296
484
  .captions-list--state-collapsed {
297
- max-height: 10em;
298
- /* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
299
- mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
300
- linear-gradient(
301
- 0deg,
302
- rgba(0, 0, 0, 0) 0%,
303
- rgba(0, 0, 0, 1) 20%,
304
- rgba(0, 0, 0, 1) 80%,
305
- rgba(0, 0, 0, 0) 100%
306
- );
485
+ max-height: 15em;
307
486
  }
308
487
  .captions-list--state-expanded {
309
488
  aspect-ratio: 16 / 9;
310
- /* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
311
- mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
312
- linear-gradient(
313
- 0deg,
314
- rgba(0, 0, 0, 0) 0%,
315
- rgba(0, 0, 0, 1) 5%,
316
- rgba(0, 0, 0, 1) 95%,
317
- rgba(0, 0, 0, 0) 100%
318
- );
319
489
  }
320
490
  .caption-text {
321
491
  overflow: visible;