fcad-core-dragon 2.0.0-beta.4 → 2.0.0-beta.5

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 (44) hide show
  1. package/CHANGELOG +377 -373
  2. package/package.json +61 -61
  3. package/src/$locales/en.json +3 -1
  4. package/src/$locales/fr.json +3 -1
  5. package/src/components/AppBase.vue +20 -17
  6. package/src/components/AppBaseButton.test.js +22 -0
  7. package/src/components/AppBaseButton.vue +11 -5
  8. package/src/components/AppBaseModule.vue +48 -27
  9. package/src/components/AppBasePage.vue +7 -44
  10. package/src/components/AppCompAudio.vue +31 -0
  11. package/src/components/AppCompBranchButtons.vue +20 -16
  12. package/src/components/AppCompButtonProgress.vue +4 -9
  13. package/src/components/AppCompCarousel.vue +120 -90
  14. package/src/components/AppCompInputCheckBoxNext.vue +5 -0
  15. package/src/components/AppCompInputDropdownNext.vue +50 -8
  16. package/src/components/AppCompInputTextNext.vue +21 -2
  17. package/src/components/AppCompInputTextTableNext.vue +1 -0
  18. package/src/components/AppCompInputTextToFillDropdownNext.vue +8 -0
  19. package/src/components/AppCompMenu.vue +428 -423
  20. package/src/components/AppCompNavigation.vue +41 -28
  21. package/src/components/AppCompNoteCall.vue +64 -38
  22. package/src/components/AppCompNoteCredit.vue +303 -105
  23. package/src/components/AppCompPlayBar.vue +4 -5
  24. package/src/components/AppCompPlayBarNext.vue +20 -12
  25. package/src/components/AppCompPopUpNext.vue +1 -4
  26. package/src/components/AppCompQuizNext.vue +8 -4
  27. package/src/components/AppCompQuizRecall.vue +44 -22
  28. package/src/components/AppCompTableOfContent.vue +61 -62
  29. package/src/components/AppCompViewDisplay.vue +6 -6
  30. package/src/components/BaseModule.vue +1 -18
  31. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  32. package/src/main.js +3 -3
  33. package/src/module/stores/appStore.js +58 -5
  34. package/src/module/xapi/Crypto/index.js +53 -53
  35. package/src/module/xapi/Statement/activity.js +47 -47
  36. package/src/module/xapi/Statement/group.js +26 -26
  37. package/src/module/xapi/Statement/statementRef.js +23 -23
  38. package/src/module/xapi/Statement/substatement.js +22 -22
  39. package/src/module/xapi/Statement/verb.js +36 -36
  40. package/src/module/xapi/activitytypes.js +17 -17
  41. package/src/plugins/helper.js +53 -12
  42. package/src/router/index.js +6 -1
  43. package/src/shared/validators.js +36 -179
  44. package/vitest.config.js +19 -0
@@ -17,80 +17,205 @@
17
17
  </svg>
18
18
  </app-base-button>
19
19
  <div class="box-nc">
20
- <div v-if="getNote != null" class="ctn-note">
21
- <p class="t-note">{{ $t('text.title_note') }}</p>
22
- <ol id="notes-list">
23
- <li
24
- v-for="(note, index) of getNote"
25
- :id="`nt_${index + 1}`"
26
- :key="`note_${index}`"
27
- :dataRef="`rnt_${index + 1}`"
28
- :class="note.hasError ? 'widget-note note-error' : 'widget-note'"
29
- >
30
- <button
31
- :noteRef="`rnt_${index + 1}`"
32
- class="btn backToNoteRef"
33
- :title="$t('text.title_link_bas')"
34
- @click="focusNote($event)"
20
+ <app-base-error-display
21
+ v-if="errorData.length"
22
+ :error-group="'component'"
23
+ :error-title="'ERREUR: NOTES ET CREDITS'"
24
+ :errors-list="errorData"
25
+ :error-text="`Vous avez une/des erreur(s) dans la création des notes/crédits.`"
26
+ ></app-base-error-display>
27
+ <template v-else>
28
+ <div v-if="notes" class="ctn-note">
29
+ <p class="t-note">{{ $t('text.title_note') }}</p>
30
+ <div id="notes-list">
31
+ <template
32
+ v-for="(note, index) of notes"
33
+ :key="`nt_${note.page_ref}__${note.id.substring(3)}`"
35
34
  >
36
- <span class="note-txt" v-html="note.text"></span>
37
- </button>
38
- </li>
39
- </ol>
40
- </div>
41
- <div v-if="getCredit != null" class="ctn-credit">
42
- <p class="t-crdt">{{ $t('text.title_credit') }}</p>
43
-
44
- <ul id="credits-list">
45
- <li
46
- v-for="(credit, index) of getCredit"
47
- :key="`note_${index}`"
48
- :ref="`#nt_${index + 1}`"
49
- v-html="credit"
50
- ></li>
51
- </ul>
52
- </div>
35
+ <div class="note-item">
36
+ <span>{{ index + 1 }}.</span>
37
+ <app-base-button
38
+ :id="`nt_${note.page_ref}__${note.id.substring(3)}`"
39
+ type="button"
40
+ :title="$t('text.title_link_bas')"
41
+ class="backToNoteRef"
42
+ :data-ref="`rnt_${note.page_ref}__${note.id.substring(3)}`"
43
+ :note-ref="`rnt_${note.page_ref}__${note.id.substring(3)}`"
44
+ :pageref="note.page_ref"
45
+ :class="
46
+ note.hasError ? 'widget-note note-error' : 'widget-note'
47
+ "
48
+ @click="
49
+ handleNoteDisplay(
50
+ `nt_${note.page_ref}__${note.id.substring(3)}`
51
+ )
52
+ "
53
+ >
54
+ <div
55
+ class="btref-content"
56
+ data-content=""
57
+ v-html="`${note.text}`"
58
+ ></div>
59
+ </app-base-button>
60
+ </div>
61
+ </template>
62
+ </div>
63
+ </div>
64
+ <div v-if="credits" class="ctn-credit">
65
+ <p class="t-crdt">{{ $t('text.title_credit') }}</p>
66
+
67
+ <ul id="credits-list">
68
+ <li
69
+ v-for="(credit, index) of credits"
70
+ :key="`credit_${index + 1}`"
71
+ :ref="`#nt_${index + 1}`"
72
+ v-html="credit"
73
+ ></li>
74
+ </ul>
75
+ </div>
76
+ </template>
53
77
  </div>
54
78
  </div>
55
79
  </template>
56
80
  <script>
57
81
  import { mapState, mapActions } from 'pinia'
58
82
  import { useAppStore } from '../module/stores/appStore'
83
+ import { validateObjType } from '../shared/validators'
84
+
59
85
  export default {
86
+ props: {},
60
87
  data() {
61
88
  return {
62
- notes: null,
63
- credits: null,
64
89
  current: false,
65
- showWidget: false
90
+ showWidget: false,
91
+ sideBarIsOpen: null,
92
+ prevNote: null,
93
+ shouldDeactivate: false,
94
+ errorData: [],
95
+ notes: null,
96
+ credits: null
66
97
  }
67
98
  },
68
99
  computed: {
69
- ...mapState(useAppStore, ['getDataNoteCredit']),
70
- getNote() {
71
- return this.error(this.getDataNoteCredit.note)
72
- },
73
- getCredit() {
74
- return this.getDataNoteCredit.credit
75
- }
100
+ ...mapState(useAppStore, [
101
+ 'getDataNoteCredit',
102
+ 'getAllActivities',
103
+ 'getCurrentPage',
104
+ 'getNotes',
105
+ 'getCredits'
106
+ ])
76
107
  },
77
- async created() {},
108
+ created() {},
78
109
  beforeUnmount() {
79
110
  this.$bus.$off('credit-note', this.onCreditNote)
80
111
  this.$bus.$off('toggle-widget', this.onToggleWidget)
81
- this.$bus.$off('close-widget', this.onCloseWidget)
112
+ this.$bus.$off('close-widget', this.close)
82
113
  this.$bus.$off('note-to-show', this.onNoteToShow)
114
+ this.$bus.$off('side-bar-open', this.onSidebarVisible)
83
115
  },
84
116
  mounted() {
85
117
  // close or open widget depending which btn was click
86
118
  this.$bus.$on('toggle-widget', this.onToggleWidget)
87
119
  //close widget
88
- this.$bus.$on('close-widget', this.onCloseWidget)
120
+ this.$bus.$on('close-widget', this.close)
89
121
  // show widget when note click and higligth note
90
122
  this.$bus.$on('note-to-show', this.onNoteToShow)
123
+ this.$bus.$on('side-bar-open', this.onSidebarVisible)
91
124
  },
92
125
  methods: {
93
126
  ...mapActions(useAppStore, ['updateWidgetOpen']),
127
+ getPageNotes(data) {
128
+ if (!data) return
129
+ const _notes = data
130
+ let formatedNotes = []
131
+ _notes.forEach((g) => {
132
+ let count = 0
133
+ Object.values(g)[0].map((n) => {
134
+ count++
135
+ if (
136
+ this.validateData('note', {
137
+ n,
138
+ details: `${count} ${Object.keys(g)[0]} `
139
+ })
140
+ )
141
+ return
142
+ n['page_ref'] = `${Object.keys(g)[0]}`
143
+ })
144
+ //Add all the note to the temp array
145
+ formatedNotes.push(...Object.values(g)[0])
146
+ })
147
+
148
+ return formatedNotes
149
+ },
150
+
151
+ getPageCredits(data) {
152
+ if (!data) return
153
+
154
+ let credits = data
155
+ let count = 0
156
+ for (const c of credits) {
157
+ count++
158
+ if (this.validateData('credit', { c, details: count })) return
159
+ }
160
+
161
+ return credits
162
+ },
163
+ setPageNotesAndCredits(data) {
164
+ const { notes, credits } = data
165
+ this.notes = this.getPageNotes(notes)
166
+ this.credits = this.getPageCredits(credits)
167
+ },
168
+ /**
169
+ * @description method to validate that the data for the note and credit.
170
+ * @param {String} ctx context of the validation : credit | note.
171
+ * @param {String| Object} data the data to be validate- Object type for note and String type for credit
172
+ * @return {boolean}
173
+ */
174
+ validateData(ctx, data) {
175
+ let errConsole = []
176
+ if (this.errorData.length) this.errorData = [] //reset the error tracker
177
+
178
+ switch (ctx) {
179
+ case 'note': {
180
+ if (data.constructor !== Object) {
181
+ this.errorData.push(
182
+ "Mauvais d'éclaration de la note. Doit être de type <i>Objet</i>"
183
+ )
184
+ throw Error(
185
+ `Unexpected definition the note. Expecting Object but received ${typeof data}`
186
+ )
187
+ }
188
+
189
+ let stringType = ['id', 'text'] //expected attribute in the note declaration
190
+
191
+ let { errorInConsole, errorList } = validateObjType(
192
+ data.n,
193
+ {
194
+ stringType
195
+ },
196
+ null,
197
+ `Note ${data.details}`
198
+ )
199
+
200
+ if (errorInConsole.length) errConsole.push(...errorInConsole)
201
+ if (errorList.length) this.errorData.push(...errorList)
202
+ break
203
+ }
204
+ case 'credit':
205
+ if (data.c.constructor !== String) {
206
+ this.errorData.push(
207
+ `Mauvaise déclaration du <b>crédit ${data.details}</b>. Doit être de type <i>String</i>`
208
+ )
209
+ console.warn(
210
+ `\n 💥 Invalid type declaration for 👉\x1B[1mcredit ${data.details} 👈\x1B[0m. Expecting type String \uD83D\uDEABtype ${typeof data.c}`
211
+ )
212
+ }
213
+ }
214
+ if (errConsole.length) {
215
+ errConsole.forEach((err) => console.warn(err))
216
+ }
217
+ return this.errorData.length
218
+ },
94
219
  onNoteToShow(el) {
95
220
  this.showWidget = true
96
221
  this.updateWidgetOpen(true)
@@ -98,6 +223,13 @@ export default {
98
223
  if (el) {
99
224
  let ref = el.getAttribute('data-Ref')
100
225
 
226
+ const reg1 = /.*(P\d{2}(_E\d{2})?).*/gm //reg for the content of data-ref
227
+ const id = `$1`
228
+ const pageRef = ref.replace(reg1, id) //replace all to page id
229
+
230
+ if (this.sideBarIsOpen && pageRef.length == 3)
231
+ this.$bus.$emit('close-sidebar', 'ctxBranching') //send close signal if sibebar is open and note to focus is in parent page
232
+
101
233
  this.removehighlight()
102
234
 
103
235
  let elAtt = document.getElementsByClassName('widget-note')
@@ -105,29 +237,20 @@ export default {
105
237
 
106
238
  for (let element of arr) {
107
239
  if (ref == element.getAttribute('id')) {
108
- element.setAttribute('dataCurrent', true)
240
+ element.setAttribute('datacurrent', true)
109
241
  element.focus()
110
242
  }
111
243
  }
112
244
  }
113
245
  },
114
- onCloseWidget() {
115
- this.showWidget = false
116
- this.updateWidgetOpen(false)
117
- let el = document.getElementsByClassName('widget-note')
118
246
 
119
- for (let element of el) {
120
- element.setAttribute('datacurrent', false)
121
- }
122
- },
123
247
  onToggleWidget(data) {
124
- if (data == 'noteCredit') {
125
- this.showWidget = !this.showWidget
248
+ if (data.type !== 'noteCredit') return (this.showWidget = false)
249
+ const { notes, credits } = data.content
126
250
 
127
- if (this.showWidget) this.updateWidgetOpen(true)
128
- } else {
129
- this.showWidget = false
130
- }
251
+ this.setPageNotesAndCredits({ notes, credits })
252
+ this.showWidget = !this.showWidget
253
+ this.updateWidgetOpen(this.showWidget) //update data in store
131
254
  },
132
255
  close() {
133
256
  let elNote = document.getElementsByClassName('widget-note')
@@ -137,63 +260,123 @@ export default {
137
260
  let arrCall = Array.from(elCall)
138
261
 
139
262
  for (let element of arrNote) {
140
- element.setAttribute('dataCurrent', false)
263
+ element.setAttribute('datacurrent', false)
141
264
  }
142
265
 
143
266
  for (let element of arrCall) {
144
267
  element.setAttribute('datacurrentnote', false)
145
268
  }
146
- this.showWidget = !this.showWidget
147
- this.$bus.$emit('close-widget')
269
+ this.showWidget = false
270
+ this.updateWidgetOpen(false)
148
271
  },
149
272
  removehighlight() {
150
- let elAtt = document.querySelectorAll("[dataCurrent='true']")
273
+ let elAtt = document.querySelectorAll("[datacurrent='true']")
151
274
 
152
275
  let arr = Array.from(elAtt)
153
276
 
154
277
  for (let element of arr) {
155
- element.setAttribute('dataCurrent', false)
278
+ element.setAttribute('datacurrent', false)
156
279
  }
157
280
  },
158
- focusNote(el) {
281
+
282
+ focusNote(el, page) {
159
283
  this.remove()
284
+ const DOMPages = document.getElementsByClassName(`app-page`)
285
+ const targetPage = DOMPages.namedItem(page) //get the desired page
286
+
160
287
  let e = document.getElementsByClassName('callEndNote')
161
- let t = el.target.getAttribute('noteref')
162
- let s = document.querySelectorAll(`[dataref="${t}"]`)
163
- s[0].setAttribute('dataCurrent', true)
288
+
289
+ if (!targetPage) return
290
+ let nts = targetPage.getElementsByClassName('callEndNote')
291
+
292
+ let t = el.getAttribute('note-ref')
293
+ let s = document.querySelectorAll(`[data-ref="${t}"]`)
294
+
295
+ if (s && s[0]) s[0].setAttribute('datacurrent', true)
164
296
 
165
297
  for (let element of e) {
166
- element.setAttribute('dataCurrentnote', false)
167
- if (element.id === t) {
168
- element.setAttribute('dataCurrentnote', true)
169
- element.childNodes[0].focus()
170
- }
298
+ element.setAttribute('datacurrentnote', false) //reset the data attribute of all notes
171
299
  }
300
+ let targetNote = nts.namedItem(t)
301
+
302
+ if (targetNote) {
303
+ targetNote.setAttribute('datacurrentnote', true)
304
+
305
+ targetNote.childNodes[0].focus()
306
+ } else {
307
+ el.setAttribute('aria-disabled', 'true')
308
+ el.setAttribute('disabled', 'true')
309
+ el.querySelector('.btref-content').setAttribute(
310
+ 'data-content',
311
+ '\u26A0\uFE0F'
312
+ )
313
+ el.querySelector('div').style =
314
+ 'color:red; text-decoration:line-through;'
315
+ el.style.color = 'red'
316
+
317
+ el.removeEventListener('click', this.handleNoteDisplay)
318
+ }
319
+ this.prevNote = el
172
320
  },
321
+
173
322
  remove() {
174
323
  let e = document.getElementsByClassName('widget-note')
175
324
  for (let element of e) {
176
- element.setAttribute('dataCurrent', false)
325
+ element.setAttribute('datacurrent', false)
177
326
  }
178
327
  },
179
- error(e) {
180
- const returnArray = []
181
- // ---------------- MUST BE REDO BECAUSE INFO CHANGE ----------------------
182
- let arr = e
183
- let err = false
184
- if (e) {
185
- arr.forEach((element, index) => {
186
- err = false
187
- if (!document.getElementById(`rnt_${index + 1}`)) {
188
- err = true
189
- }
190
- returnArray.push({
191
- text: err ? `${element} : ${this.$t('text.err_credit')}` : element,
192
- hasError: err
328
+
329
+ onSidebarVisible(state) {
330
+ this.sideBarIsOpen = state
331
+ },
332
+
333
+ /**
334
+ * @description method handle the behavior of note displaying
335
+ * @param {Object} evt - the event that trigger the action
336
+ * @emits event - 'open-sidebar' to display notes in branch | 'close-sidebar' to display not in parent when branch open
337
+ *
338
+ */
339
+ handleNoteDisplay(ref) {
340
+ /*
341
+ * Note:
342
+ * Element target (button) is wrongly referenced when Screen-reader is active and element selected With TAB
343
+ * The Div in the button is selected not the button.
344
+ * This ensure that the button is always set as targeted element
345
+ */
346
+ const widgetContent = document.getElementsByClassName('backToNoteRef') //target all the button of in the notes widget
347
+ const elTarget = widgetContent.namedItem(ref) // Target current button
348
+ const pageRef = elTarget.getAttribute('pageref')
349
+
350
+ if (!this.sideBarIsOpen) this.prevNote = null //reset previous when sidebar is opened
351
+
352
+ let prevPageref = this.prevNote
353
+ ? this.prevNote.getAttribute('pageref')
354
+ : null
355
+
356
+ switch (true) {
357
+ case pageRef && pageRef.length > 3: {
358
+ //this is the branching page Send message to open the side bar
359
+ this.$bus.$emit('open-sidebar', {
360
+ ctx: 'ctxBranching',
361
+ e: pageRef,
362
+ persist: this.sideBarIsOpen || pageRef == prevPageref //this will prevent the sidebar to close when the note is in same page
193
363
  })
194
- })
364
+ //Should set focus after side bar is opened. Need to delay to not interfere with side bar focus
365
+ pageRef == prevPageref
366
+ ? setTimeout(() => this.focusNote(elTarget, pageRef), 100)
367
+ : setTimeout(() => this.focusNote(elTarget, pageRef), 400)
368
+
369
+ break
370
+ }
371
+ default:
372
+ //Should have the side bar close if it is opened and note is in parent page
373
+ if (this.sideBarIsOpen && pageRef.length == 3)
374
+ this.$bus.$emit('close-sidebar', 'ctxBranching')
375
+
376
+ return setTimeout(() => this.focusNote(elTarget, pageRef), 100) //Need to delay to not have side bar close animation finished
195
377
  }
196
- return returnArray.length === 0 ? null : returnArray
378
+
379
+ this.prevNote = elTarget
197
380
  }
198
381
  }
199
382
  }
@@ -225,9 +408,9 @@ export default {
225
408
  }
226
409
  }
227
410
 
228
- .note-txt {
229
- pointer-events: none;
230
- }
411
+ // .note-txt {
412
+ // pointer-events: none;
413
+ // }
231
414
 
232
415
  .box-nc {
233
416
  margin-top: 54px;
@@ -241,35 +424,50 @@ export default {
241
424
  }
242
425
 
243
426
  #notes-list {
244
- margin-bottom: 10px;
427
+ .note-item {
428
+ display: flex;
429
+ flex-direction: row;
430
+ flex-wrap: wrap;
431
+ margin-bottom: 5px;
432
+
433
+ color: #deeff8;
434
+
435
+ span {
436
+ font-size: 0.65rem;
437
+ padding: 8px 0 0 0;
438
+ }
245
439
 
246
- li {
247
440
  .backToNoteRef {
248
- text-align: left;
441
+ width: 90%;
442
+ margin-left: 5px;
443
+ padding-left: 5px;
444
+
445
+ .btref-content {
446
+ text-align: left;
447
+ }
249
448
  }
250
449
  }
251
450
  }
252
451
  }
253
452
 
254
- ul#credits-list {
453
+ #credits-list {
255
454
  list-style-type: none;
256
455
  padding: 0;
257
456
  margin: 0;
258
457
  }
259
- ol#notes-list {
260
- list-style-position: inside;
458
+ #notes-list {
261
459
  padding: 0;
262
460
  margin: 0;
461
+
263
462
  .widget-note {
264
463
  padding: 5px 0;
464
+
265
465
  &.note-error {
266
466
  background-color: firebrick;
267
467
  }
268
- .backToNoteRef {
269
- display: inline-block;
270
- width: 90%;
271
- vertical-align: top;
272
- padding: 0 4px;
468
+
469
+ .btref-content {
470
+ pointer-events: none;
273
471
  }
274
472
  }
275
473
  }
@@ -292,7 +292,7 @@ mediaMixins is used for all the methods/data shared between audio and video. In
292
292
  class="btn subtitleBtns"
293
293
  :aria-label="ccLabel"
294
294
  :data-title="`${ccLabel} (c)`"
295
- :disabled="!hasSubtitle"
295
+ :aria-disabled="!hasSubtitle"
296
296
  :class="{
297
297
  md_disabled: !hasSubtitle,
298
298
  displayLabel: btnsLabelDisplay['btn-subtitles'] === true
@@ -320,7 +320,7 @@ mediaMixins is used for all the methods/data shared between audio and video. In
320
320
  class="btn-transcript"
321
321
  :aria-label="transcriptLabel"
322
322
  :data-title="`${transcriptLabel} (t)`"
323
- :disabled="!hasTranscript || fullscreenOn"
323
+ :aria-disabled="!hasTranscript || fullscreenOn"
324
324
  :class="{
325
325
  md_disabled: !hasTranscript || fullscreenOn,
326
326
  displayLabel: btnsLabelDisplay['btn-transcript'] === true
@@ -350,7 +350,7 @@ mediaMixins is used for all the methods/data shared between audio and video. In
350
350
  ref="$btn-fullscreen"
351
351
  class="fullscreenBtns"
352
352
  :aria-label="fullscreenLabel"
353
- :disabled="transcriptEnabled"
353
+ :aria-disabled="transcriptEnabled"
354
354
  :class="{
355
355
  md_disabled: transcriptEnabled,
356
356
  displayLabel: btnsLabelDisplay['btn-fullscreen'] === true
@@ -491,9 +491,8 @@ export default {
491
491
  if (this.firefoxTrack) {
492
492
  this.firefoxTrack.removeEventListener('cuechange', this.fireFoxMoveCC)
493
493
  }
494
- //this.$bus.$off('play-media', this.handleMediaControls)
494
+
495
495
  this.$bus.$off('transcript-hidden', this.resetTranscript)
496
- return this.$bus.$off('close-sidebar', 'ctxTranscript')
497
496
  },
498
497
  methods: {
499
498
  ...mapActions(useAppStore, [