fcad-core-dragon 2.0.0-beta.3 → 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 (90) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/.eslintrc.cjs +81 -81
  4. package/CHANGELOG +13 -0
  5. package/README.md +71 -71
  6. package/bk.scss +117 -117
  7. package/package.json +8 -8
  8. package/src/$locales/en.json +145 -143
  9. package/src/$locales/fr.json +107 -105
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1150 -1054
  12. package/src/components/AppBaseButton.test.js +22 -0
  13. package/src/components/AppBaseButton.vue +93 -87
  14. package/src/components/AppBaseErrorDisplay.vue +438 -438
  15. package/src/components/AppBaseFlipCard.vue +84 -84
  16. package/src/components/AppBaseModule.vue +1657 -1673
  17. package/src/components/AppBasePage.vue +742 -779
  18. package/src/components/AppBasePopover.vue +41 -41
  19. package/src/components/AppCompAudio.vue +265 -234
  20. package/src/components/AppCompBranchButtons.vue +556 -552
  21. package/src/components/AppCompButtonProgress.vue +121 -126
  22. package/src/components/AppCompCarousel.vue +328 -298
  23. package/src/components/AppCompInputCheckBoxNext.vue +200 -195
  24. package/src/components/AppCompInputDropdownNext.vue +201 -159
  25. package/src/components/AppCompInputRadioNext.vue +152 -152
  26. package/src/components/AppCompInputTextNext.vue +125 -106
  27. package/src/components/AppCompInputTextTableNext.vue +142 -141
  28. package/src/components/AppCompInputTextToFillDropdownNext.vue +238 -230
  29. package/src/components/AppCompInputTextToFillNext.vue +171 -171
  30. package/src/components/AppCompJauge.vue +74 -74
  31. package/src/components/AppCompMenu.vue +25 -10
  32. package/src/components/AppCompMenuItem.vue +228 -228
  33. package/src/components/AppCompNavigation.vue +972 -960
  34. package/src/components/AppCompNoteCall.vue +159 -133
  35. package/src/components/AppCompNoteCredit.vue +490 -292
  36. package/src/components/AppCompPlayBar.vue +1217 -1218
  37. package/src/components/AppCompPlayBarNext.vue +2060 -2052
  38. package/src/components/AppCompPlayBarProgress.vue +82 -82
  39. package/src/components/AppCompPopUpNext.vue +500 -503
  40. package/src/components/AppCompQuizNext.vue +2908 -2904
  41. package/src/components/AppCompQuizRecall.vue +298 -276
  42. package/src/components/AppCompSVGNext.vue +347 -347
  43. package/src/components/AppCompSettingsMenu.vue +172 -172
  44. package/src/components/AppCompTableOfContent.vue +386 -387
  45. package/src/components/AppCompTranscript.vue +24 -24
  46. package/src/components/AppCompVideoPlayer.vue +368 -368
  47. package/src/components/BaseModule.vue +55 -72
  48. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  49. package/src/composables/useQuiz.js +206 -206
  50. package/src/externalComps/ModuleView.vue +22 -22
  51. package/src/externalComps/SummaryView.vue +91 -91
  52. package/src/main.js +272 -272
  53. package/src/mixins/$mediaMixins.js +819 -819
  54. package/src/mixins/timerMixin.js +155 -155
  55. package/src/module/stores/appStore.js +954 -893
  56. package/src/module/xapi/ADL.js +380 -376
  57. package/src/module/xapi/Crypto/Hasher.js +241 -241
  58. package/src/module/xapi/Crypto/WordArray.js +278 -278
  59. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  60. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  61. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  62. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  63. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  64. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  65. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  66. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  67. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  68. package/src/module/xapi/Statement/agent.js +55 -55
  69. package/src/module/xapi/Statement/index.js +259 -259
  70. package/src/module/xapi/Statement/statement.js +253 -253
  71. package/src/module/xapi/launch.js +157 -157
  72. package/src/module/xapi/utils.js +167 -167
  73. package/src/module/xapi/verbs.js +294 -294
  74. package/src/module/xapi/wrapper.js +1963 -1963
  75. package/src/module/xapi/xapiStatement.js +444 -444
  76. package/src/plugins/bus.js +8 -8
  77. package/src/plugins/gsap.js +14 -14
  78. package/src/plugins/helper.js +355 -308
  79. package/src/plugins/i18n.js +44 -44
  80. package/src/plugins/idb.js +227 -219
  81. package/src/plugins/save.js +37 -37
  82. package/src/plugins/scorm.js +287 -287
  83. package/src/plugins/xapi.js +11 -11
  84. package/src/public/index.html +33 -33
  85. package/src/router/index.js +48 -43
  86. package/src/router/routes.js +312 -312
  87. package/src/shared/generalfuncs.js +210 -210
  88. package/src/shared/validators.js +926 -1069
  89. package/vitest.config.js +19 -0
  90. package/vite.config.js +0 -27
@@ -1,552 +1,556 @@
1
- <!-- Composante qui génère les boutons d'embranchment d'une page.
2
-
3
- **props**
4
- *********************************
5
- customButtons (optionel):
6
- Tableau d'objets contenant les informations à propos des boutons custom à créer pour chaque embranchement.
7
- exemple pour une page avec 2 embranchments:
8
- [
9
- { name: 'bouton_A02_P01_E01', brchName: 'P02_E01' },
10
- { name: 'bouton_A02_P01_E02', brchName: 'P02_E02' }
11
- ]
12
- un template custom peut ensuite être donner pour habiller chaque bouton en faisant reférence au nom dynamique du slot
13
- <template #P02_E01></template>
14
- *********************************
15
- card (optionel):
16
- Objet qui défini le contenu de la composante b-card
17
- exemple:
18
- {
19
- imgFile: '/src/assets/img/imgCarte.jpg', (l'image doit être appelé par un import ex : import imgCarte1 from '@/assets/img/imgCarte.jpg')
20
- title: 'Titre embranchment',
21
- text: 'Ceci est un embranchment factice, affiché sous la forme de carte.'
22
- brchName: 'P02_E01'
23
- btnTitle: ''
24
- }
25
- *********************************
26
- Si la composante est appelée sans prop, les boutons par défaut seront générés.
27
- -->
28
- <template>
29
- <v-row id="branch-buttons-component">
30
- <app-base-error-display
31
- v-if="hasErrors"
32
- :error-group="'component'"
33
- :error-title="`ERREUR: COMPOSANT D'EMBRANCHEMENT`"
34
- :errors-list="hasErrors"
35
- :error-text="`Vous avez une/des erreur(s) lors de la création des
36
- ${(() => (isCard ? 'cartes' : `boutons`))()} d'embranchments.`"
37
- />
38
- <v-row v-else>
39
- <v-col
40
- v-for="branch of branchsStateData"
41
- :id="branch.id"
42
- :key="branch.id"
43
- class="branch-btn"
44
- >
45
- <div
46
- v-if="isCustomButton"
47
- :id="getMatchingElement(branch.id).name"
48
- class="branch-btn-wrapper"
49
- >
50
- <slot :name="branch.id">
51
- <p class="name-btn-branch">
52
- {{ getMatchingElement(branch.id).name }}
53
- </p>
54
- </slot>
55
-
56
- <app-comp-button-progress
57
- :set-target="sidebar"
58
- :percent="branch.progression ? branch.progression : 0"
59
- :branch-data="branch"
60
- :btn-title="getMatchingElement(branch.id).btnTitle"
61
- no-interaction
62
- ></app-comp-button-progress>
63
- </div>
64
- <div
65
- v-else-if="isCard && cards.length"
66
- class="branch-btn-wrapper branch-btn-card"
67
- >
68
- <v-card class="mx-auto" max-width="20rem">
69
- <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover>
70
- <v-toolbar color="transparent">
71
- <template #append>
72
- <app-comp-button-progress
73
- :set-target="sidebar"
74
- :percent="branch.progression ? branch.progression : 0"
75
- :branch-data="branch"
76
- :btn-title="getMatchingElement(branch.id).btnTitle"
77
- ></app-comp-button-progress>
78
- </template>
79
- </v-toolbar>
80
- </v-img>
81
-
82
- <v-card-title>{{ branch.title }}</v-card-title>
83
- <v-card-text>
84
- {{ branch.text }}
85
- </v-card-text>
86
- </v-card>
87
- </div>
88
- <div v-else class="branch-btn-wrapper">
89
- <app-comp-button-progress
90
- :set-target="sidebar"
91
- :percent="branch.progression ? branch.progression : 0"
92
- :branch-data="branch"
93
- :btn-title="branch.text"
94
- ></app-comp-button-progress>
95
- </div>
96
- </v-col>
97
- </v-row>
98
- </v-row>
99
- </template>
100
- <script>
101
- // ...
102
- import { mapState } from 'pinia'
103
- import { useAppStore } from '../module/stores/appStore'
104
- import AppCompButtonProgress from './AppCompButtonProgress.vue'
105
-
106
- export default {
107
- components: {
108
- AppCompButtonProgress
109
- },
110
- props: {
111
- consigne: {
112
- type: Boolean,
113
- default: false
114
- },
115
- sidebar: {
116
- type: String,
117
- default: 'branch-viewer'
118
- },
119
- btnTitle: {
120
- type: String,
121
- default: ' '
122
- },
123
- cards: {
124
- type: Array,
125
- default: () => null,
126
- validator(dataArray) {
127
- let isValid = true
128
- if (import.meta.env.DEV) {
129
- let errMsg = false
130
-
131
- //card must have at least 2 elements
132
- if (dataArray.length < 2) {
133
- isValid = false
134
- errMsg = `\n card require at least 2 element`
135
- } else {
136
- for (let el of dataArray) {
137
- //Each element in card must be of type Object and can not be empty
138
- if (el.constructor !== Object) {
139
- isValid = false
140
- errMsg = `\n Element in card must be of type Object`
141
- break
142
- }
143
- }
144
- //Start validation of required keys
145
- if (isValid) {
146
- const requiredKeys = ['imgFile', 'title', 'text', 'brchName']
147
-
148
- for (let el of dataArray) {
149
- //Validate that require key exist for each element in card
150
- for (let k of requiredKeys) {
151
- let index = dataArray.indexOf(el)
152
-
153
- if (!el[k]) {
154
- isValid = false
155
- errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
156
- index + 1
157
- } in ➡ card \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
158
- break
159
- } else {
160
- if (el[k].constructor !== String) {
161
- errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
162
- index + 1
163
- } of card. \n 🚩 Must be of type {String}`
164
- isValid = false
165
- break
166
- }
167
- }
168
- if (!isValid) break
169
- }
170
- if (!isValid) break
171
- }
172
- }
173
- }
174
-
175
- if (errMsg) console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
176
- }
177
- return isValid
178
- }
179
- },
180
- customButtons: {
181
- type: Array,
182
- default: () => null,
183
- validator: (dataArray) => {
184
- let isValid = true
185
- if (import.meta.env.DEV) {
186
- let errMsg = false
187
- //card must have at least 2 elements
188
- if (dataArray.length < 2) {
189
- isValid = false
190
- errMsg = `\n customButton require at least 2 element`
191
- } else {
192
- for (let el of dataArray) {
193
- //Each element in card must be of type Object and can not be empty
194
- if (el.constructor !== Object) {
195
- isValid = false
196
- errMsg = `\n Element in customButton must be of type Object`
197
- break
198
- }
199
- }
200
- //Start validation of keys if no error detected
201
- if (isValid) {
202
- const requiredKeys = ['name', 'brchName']
203
- for (let el of dataArray) {
204
- //Validate that require key exist for each element in card
205
- for (let k of requiredKeys) {
206
- let index = dataArray.indexOf(el)
207
-
208
- if (!el[k]) {
209
- isValid = false
210
- errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
211
- index + 1
212
- } in ➡ customButtom \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
213
- break
214
- } else {
215
- if (el[k].constructor !== String) {
216
- errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
217
- index + 1
218
- } of customButton. \n 🚩 Must be of type {String}`
219
- isValid = false
220
- break
221
- }
222
- }
223
- if (!isValid) break
224
- }
225
- if (!isValid) break
226
- }
227
- }
228
- if (errMsg)
229
- console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
230
- }
231
- }
232
- return isValid
233
- }
234
- }
235
- },
236
- data() {
237
- const svgRadius = 23
238
- return {
239
- errors: [],
240
- radius: svgRadius,
241
- circumference: svgRadius * 2 * Math.PI,
242
- branchs: null,
243
- branchsStateData: null,
244
- idActivity: null,
245
- isCustomButton: false,
246
- isCard: false,
247
- customButtonStyle: null,
248
- status: {
249
- NEW: 'new',
250
- STARTED: 'started',
251
- COMPLETE: 'complete'
252
- },
253
- hasErr: null
254
- }
255
- },
256
- computed: {
257
- ...mapState(useAppStore, [
258
- 'getAllCompleted',
259
- 'getAllActivities',
260
- 'getConnectionInfo'
261
- ]),
262
- hasErrors() {
263
- let error = false
264
- if (import.meta.env.DEV) {
265
- if (this.isCard) error = this.validateProp('cards')
266
- else if (this.isCustomButton) error = this.validateProp('customButtons')
267
- else error = false
268
- }
269
-
270
- return error
271
- }
272
- },
273
- watch: {
274
- getAllCompleted: {
275
- deep: true,
276
- //watch the completions to update the branches progression
277
- handler() {
278
- if (
279
- this.getAllCompleted[this.idActivity] &&
280
- this.getAllCompleted[this.idActivity].length
281
- )
282
- this.getbranchsData()
283
- }
284
- }
285
- },
286
- mounted() {
287
- this.isCustomButton = this.customButtons && this.customButtons.length > 0
288
- this.isCard = this.cards && Object.keys(this.cards).length != 0
289
- this.idActivity = this.$router.currentRoute.value.meta.activity_ref
290
- this.branchs = this.$router.currentRoute.value.meta.children
291
- this.getbranchsData()
292
- },
293
- methods: {
294
- /**
295
- * @description Method to get the progression for a branch
296
- *
297
- */
298
- getbranchsData() {
299
- this.branchsStateData = []
300
- this.branchs.forEach((branch, index) => {
301
- let branchData = {}
302
- branchData.id = branch._ref
303
- branchData.route = branch._namedRoute
304
- let { state = 'new', progression = 0 } = this.getBranchProgression(
305
- branchData.id
306
- )
307
- // if (!state || !progression) return
308
-
309
- branchData.state = state
310
- branchData.progression = progression
311
- if (isNaN(branchData.progression)) branchData.progression = 0
312
-
313
- if (this.isCard) {
314
- const { title, text, imgFile, imgAlt, btnTitle } =
315
- this.getMatchingElement(branchData.id)
316
-
317
- branchData = {
318
- ...branchData,
319
- text,
320
- title,
321
- imgFile,
322
- imgAlt,
323
- btnTitle
324
- }
325
-
326
- if (typeof branchData.imgAlt === 'undefined' && import.meta.env.DEV) {
327
- branchData.imgAlt = ''
328
- console.warn(
329
- `Bouton d’embranchement: ALT image sans valeur définie pour ${branchData.id}`
330
- )
331
- }
332
- }
333
- this.branchsStateData[index] = branchData
334
- })
335
- },
336
-
337
- /**
338
- * @description Method to get the progression for a branch
339
- * @param {String} idBranch the id of the branch ex: "P01_E01"
340
- * @return {Number} Return the percentage of completion (from 0 to 100) and the current state of a specific branch
341
- */
342
- getBranchProgression(idBranch) {
343
- let result = { state: null, progression: null }
344
-
345
- if (!this.getAllCompleted[this.idActivity]) return result // no progression found for this activity return result
346
-
347
- const total = this.getBranchPagesCount(idBranch) // get all pages for in this branch
348
- //get all completed branches pages
349
- const completedPages = this.getAllCompleted[this.idActivity].filter(
350
- (page) => {
351
- return Object.keys(page)[0].search(idBranch) !== -1
352
- }
353
- )
354
- result.progression = Math.floor((completedPages.length / total) * 100)
355
-
356
- if (isNaN(result.progression)) result.progression = 0
357
-
358
- if (result.progression === 0) {
359
- result.state = this.status.NEW
360
- } else if (result.progression === 100) {
361
- result.state = this.status.COMPLETE
362
- } else {
363
- result.state = this.status.STARTED
364
- }
365
- return result
366
- },
367
-
368
- /**
369
- * @description Method to get the numbers of pages in a branch
370
- * @param {String} idBranch the id of the branch ex: "A02_E01_P01"
371
- * @return {Array} /Return the total of pages in a specific branch
372
- */
373
- getBranchPagesCount(idBranch) {
374
- if (this.$router.currentRoute.value.meta.type !== 'branching') return
375
-
376
- const allPages = this.getAllActivities()
377
- .list.get(this.idActivity)
378
- .get(this.$router.currentRoute.value.meta.id)
379
- .get(idBranch)
380
-
381
- return allPages.size ? allPages.size : 1
382
- },
383
-
384
- /**
385
- * @description Method to validate data in the prop card|customButton.
386
- * @param {String} propName The name of the property card|customButton
387
- * @return {String} errMsg false | string message
388
- */
389
- validateProp(propName) {
390
- let isValid = true
391
- let errMsg = false
392
-
393
- switch (propName) {
394
- case 'cards': {
395
- // verify that numbers of element is equal to number
396
- //card must have at least 2 elements
397
- if (
398
- this.cards.constructor !== Array ||
399
- this.cards.length !== this.branchs.length
400
- ) {
401
- isValid = false
402
- errMsg = `La propriété cards doit être un tableau contenant ${this.branchs.length} elements`
403
- } else {
404
- //Start validation of content of each object in cards
405
- for (let el of this.cards) {
406
- //Each element in card must be of type Object and can not be empty
407
- if (el.constructor !== Object) {
408
- isValid = false
409
- let index = this.cards.indexOf(el)
410
- errMsg = `Le data pour la carte ${index + 1} n'est pas de type Object`
411
- }
412
- if (!isValid) break
413
- }
414
- //Start validation of required keys
415
- if (isValid) {
416
- const requiredKeys = ['imgFile', 'title', 'text', 'brchName']
417
-
418
- for (let el of this.cards) {
419
- let index = this.cards.indexOf(el)
420
- //Validate that require key exist for each element in card
421
- for (let k of requiredKeys) {
422
- if (!el[k]) {
423
- isValid = false
424
- errMsg = `l'Attribut 👉 ${k} 👈 pour la carte ${index + 1} n'as pas été défini`
425
- break
426
- } else {
427
- if (el[k].constructor !== String) {
428
- errMsg = `l'Attribut 👉 ${k} 👈 pour la carte ${
429
- index + 1
430
- } doit être de type {String}`
431
- isValid = false
432
- break
433
- }
434
- }
435
- if (!isValid) break
436
- }
437
-
438
- // Start validation of existing branch for this element
439
- let searchEl = this.branchs.find((b) =>
440
- b._ref.includes(el.brchName)
441
- )
442
-
443
- if (!searchEl) {
444
- errMsg = `Aucune branch correspondante avec le nom ${el.brchName} pour la carte ${
445
- index + 1
446
- }. Êtes vous sûre d'avoir indiqué la bonne branch?`
447
- isValid = false
448
- }
449
-
450
- if (!isValid) break
451
- }
452
- }
453
- }
454
- break
455
- }
456
-
457
- case 'customButtons': {
458
- if (
459
- this.customButtons.constructor !== Array ||
460
- this.customButtons.length !== this.branchs.length
461
- ) {
462
- isValid = false
463
- errMsg = `La propriété customButtons doit être un tableau contenant ${this.branchs.length} elements`
464
- } else {
465
- //Start validation of content of each object in customButtons
466
- for (let el of this.customButtons) {
467
- //Each element in card must be of type Object and can not be empty
468
- if (el.constructor !== Object) {
469
- isValid = false
470
- let index = this.customButtons.indexOf(el)
471
- errMsg = `Le data pour le button ${index + 1} n'est pas de type {Object}`
472
- }
473
- if (!isValid) break
474
- }
475
-
476
- //Start validation of required keys
477
- if (isValid) {
478
- const requiredKeys = ['name', 'brchName']
479
-
480
- for (let el of this.customButtons) {
481
- //Validate that require key exist for each element for customButton
482
- let index = this.customButtons.indexOf(el)
483
- for (let k of requiredKeys) {
484
- if (!el[k]) {
485
- isValid = false
486
- errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${index + 1} n'as pas été défini`
487
- break
488
- } else {
489
- // Expecting String for properties name and brchName
490
- if (
491
- ['name', 'brchName'].includes(k) &&
492
- el[k].constructor !== String
493
- ) {
494
- errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${
495
- index + 1
496
- } doit être de type {String}`
497
- isValid = false
498
- break
499
- }
500
- }
501
- if (!isValid) break
502
- }
503
-
504
- // Start validation of existing branch for this element
505
- let searchEl = this.branchs.find((b) =>
506
- b._ref.includes(el.brchName)
507
- )
508
-
509
- if (!searchEl) {
510
- errMsg = `Aucune branch correspondante avec le nom ${
511
- el.brchName
512
- } pour le bouton ${index + 1}. Êtes vous sûre d'avoir indiqué la bonne branch?`
513
- isValid = false
514
- }
515
-
516
- if (!isValid) break
517
- }
518
- }
519
- }
520
- break
521
- }
522
- }
523
-
524
- return errMsg
525
- },
526
-
527
- /**
528
- * @description methode to retive matching data of branch in the prop card| customButton
529
- * @param {String} id The id of the branch
530
- * @return {Object} Matching element from
531
- */
532
- getMatchingElement(id) {
533
- if (this.isCustomButton) {
534
- return this.customButtons.find((c) => c.brchName === id)
535
- }
536
- if (this.isCard) {
537
- return this.cards.find((c) => c.brchName === id)
538
- }
539
- }
540
- }
541
- }
542
- </script>
543
-
544
- <style lang="scss">
545
- .branch-btn-wrapper {
546
- position: relative;
547
- .branch-btn-custom {
548
- background-color: transparent;
549
- border: none;
550
- }
551
- }
552
- </style>
1
+ <!-- Composante qui génère les boutons d'embranchment d'une page.
2
+
3
+ **props**
4
+ *********************************
5
+ customButtons (optionel):
6
+ Tableau d'objets contenant les informations à propos des boutons custom à créer pour chaque embranchement.
7
+ exemple pour une page avec 2 embranchments:
8
+ [
9
+ { name: 'bouton_A02_P01_E01', brchName: 'P02_E01' },
10
+ { name: 'bouton_A02_P01_E02', brchName: 'P02_E02' }
11
+ ]
12
+ un template custom peut ensuite être donner pour habiller chaque bouton en faisant reférence au nom dynamique du slot
13
+ <template #P02_E01></template>
14
+ *********************************
15
+ card (optionel):
16
+ Objet qui défini le contenu de la composante b-card
17
+ exemple:
18
+ {
19
+ imgFile: '/src/assets/img/imgCarte.jpg', (l'image doit être appelé par un import ex : import imgCarte1 from '@/assets/img/imgCarte.jpg')
20
+ title: 'Titre embranchment',
21
+ text: 'Ceci est un embranchment factice, affiché sous la forme de carte.'
22
+ brchName: 'P02_E01'
23
+ btnTitle: ''
24
+ }
25
+ *********************************
26
+ Si la composante est appelée sans prop, les boutons par défaut seront générés.
27
+ -->
28
+ <template>
29
+ <v-row id="branch-buttons-component">
30
+ <app-base-error-display
31
+ v-if="hasErrors"
32
+ :error-group="'component'"
33
+ :error-title="`ERREUR: COMPOSANT D'EMBRANCHEMENT`"
34
+ :errors-list="hasErrors"
35
+ :error-text="`Vous avez une/des erreur(s) lors de la création des
36
+ ${(() => (isCard ? 'cartes' : `boutons`))()} d'embranchments.`"
37
+ />
38
+ <v-row v-else>
39
+ <v-col
40
+ v-for="branch of branchsStateData"
41
+ :id="`branch_${branch.id}`"
42
+ :key="branch.id"
43
+ class="branch-btn"
44
+ >
45
+ <div
46
+ v-if="isCustomButton"
47
+ :id="getMatchingElement(branch.id).name"
48
+ class="branch-btn-wrapper"
49
+ >
50
+ <slot :name="branch.id">
51
+ <p class="name-btn-branch">
52
+ {{ getMatchingElement(branch.id).name }}
53
+ </p>
54
+ </slot>
55
+
56
+ <app-comp-button-progress
57
+ :set-target="sidebar"
58
+ :percent="branch.progression ? branch.progression : 0"
59
+ :branch-data="branch"
60
+ :btn-title="getMatchingElement(branch.id).btnTitle"
61
+ no-interaction
62
+ ></app-comp-button-progress>
63
+ </div>
64
+ <div
65
+ v-else-if="isCard && cards.length"
66
+ class="branch-btn-wrapper branch-btn-card"
67
+ >
68
+ <v-card class="mx-auto" max-width="15rem">
69
+ <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover />
70
+
71
+ <v-card-title>{{ branch.title }}</v-card-title>
72
+ <v-card-text>
73
+ {{ branch.text }}
74
+ </v-card-text>
75
+
76
+ <app-comp-button-progress
77
+ :set-target="sidebar"
78
+ :percent="branch.progression ? branch.progression : 0"
79
+ :branch-data="branch"
80
+ :btn-title="getMatchingElement(branch.id).btnTitle"
81
+ ></app-comp-button-progress>
82
+ </v-card>
83
+ </div>
84
+ <div v-else class="branch-btn-wrapper">
85
+ <app-comp-button-progress
86
+ :set-target="sidebar"
87
+ :percent="branch.progression ? branch.progression : 0"
88
+ :branch-data="branch"
89
+ :btn-title="branch.text"
90
+ ></app-comp-button-progress>
91
+ </div>
92
+ </v-col>
93
+ </v-row>
94
+ </v-row>
95
+ </template>
96
+ <script>
97
+ import { mapState } from 'pinia'
98
+ import { useAppStore } from '../module/stores/appStore'
99
+ import AppCompButtonProgress from './AppCompButtonProgress.vue'
100
+
101
+ export default {
102
+ components: {
103
+ AppCompButtonProgress
104
+ },
105
+ props: {
106
+ consigne: {
107
+ type: Boolean,
108
+ default: false
109
+ },
110
+ sidebar: {
111
+ type: String,
112
+ default: 'branch-viewer'
113
+ },
114
+ btnTitle: {
115
+ type: String,
116
+ default: ' '
117
+ },
118
+ cards: {
119
+ type: Array,
120
+ default: () => null,
121
+ validator(dataArray) {
122
+ let isValid = true
123
+ if (import.meta.env.DEV) {
124
+ let errMsg = false
125
+
126
+ //card must have at least 2 elements
127
+ if (dataArray.length < 2) {
128
+ isValid = false
129
+ errMsg = `\n card require at least 2 element`
130
+ } else {
131
+ for (let el of dataArray) {
132
+ //Each element in card must be of type Object and can not be empty
133
+ if (el.constructor !== Object) {
134
+ isValid = false
135
+ errMsg = `\n Element in card must be of type Object`
136
+ break
137
+ }
138
+ }
139
+ //Start validation of required keys
140
+ if (isValid) {
141
+ const requiredKeys = ['imgFile', 'title', 'text', 'brchName']
142
+
143
+ for (let el of dataArray) {
144
+ //Validate that require key exist for each element in card
145
+ for (let k of requiredKeys) {
146
+ let index = dataArray.indexOf(el)
147
+
148
+ if (!el[k]) {
149
+ isValid = false
150
+ errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
151
+ index + 1
152
+ } in ➡ card \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
153
+ break
154
+ } else {
155
+ if (el[k].constructor !== String) {
156
+ errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
157
+ index + 1
158
+ } ➡ of card. \n 🚩 Must be of type {String}`
159
+ isValid = false
160
+ break
161
+ }
162
+ }
163
+ if (!isValid) break
164
+ }
165
+ if (!isValid) break
166
+ }
167
+ }
168
+ }
169
+
170
+ if (errMsg) console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
171
+ }
172
+ return isValid
173
+ }
174
+ },
175
+ customButtons: {
176
+ type: Array,
177
+ default: () => null,
178
+ validator: (dataArray) => {
179
+ let isValid = true
180
+ if (import.meta.env.DEV) {
181
+ let errMsg = false
182
+ //card must have at least 2 elements
183
+ if (dataArray.length < 2) {
184
+ isValid = false
185
+ errMsg = `\n customButton require at least 2 element`
186
+ } else {
187
+ for (let el of dataArray) {
188
+ //Each element in card must be of type Object and can not be empty
189
+ if (el.constructor !== Object) {
190
+ isValid = false
191
+ errMsg = `\n Element in customButton must be of type Object`
192
+ break
193
+ }
194
+ }
195
+ //Start validation of keys if no error detected
196
+ if (isValid) {
197
+ const requiredKeys = ['name', 'brchName']
198
+ for (let el of dataArray) {
199
+ //Validate that require key exist for each element in card
200
+ for (let k of requiredKeys) {
201
+ let index = dataArray.indexOf(el)
202
+
203
+ if (!el[k]) {
204
+ isValid = false
205
+ errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
206
+ index + 1
207
+ } in ➡ customButtom \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
208
+ break
209
+ } else {
210
+ if (el[k].constructor !== String) {
211
+ errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
212
+ index + 1
213
+ } ➡ of customButton. \n 🚩 Must be of type {String}`
214
+ isValid = false
215
+ break
216
+ }
217
+ }
218
+ if (!isValid) break
219
+ }
220
+ if (!isValid) break
221
+ }
222
+ }
223
+ if (errMsg)
224
+ console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
225
+ }
226
+ }
227
+ return isValid
228
+ }
229
+ }
230
+ },
231
+ data() {
232
+ const svgRadius = 23
233
+ return {
234
+ errors: [],
235
+ radius: svgRadius,
236
+ circumference: svgRadius * 2 * Math.PI,
237
+ branchs: null,
238
+ branchsStateData: null,
239
+ idActivity: null,
240
+ isCustomButton: false,
241
+ isCard: false,
242
+ customButtonStyle: null,
243
+ status: {
244
+ NEW: 'new',
245
+ STARTED: 'started',
246
+ COMPLETE: 'complete'
247
+ },
248
+ hasErr: null
249
+ }
250
+ },
251
+ computed: {
252
+ ...mapState(useAppStore, [
253
+ 'getAllCompleted',
254
+ 'getAllActivities',
255
+ 'getConnectionInfo'
256
+ ]),
257
+ hasErrors() {
258
+ let error = false
259
+ if (import.meta.env.DEV) {
260
+ if (this.isCard) error = this.validateProp('cards')
261
+ else if (this.isCustomButton) error = this.validateProp('customButtons')
262
+ else error = false
263
+ }
264
+
265
+ return error
266
+ }
267
+ },
268
+ watch: {
269
+ getAllCompleted: {
270
+ deep: true,
271
+ //watch the completions to update the branches progression
272
+ handler() {
273
+ if (
274
+ this.getAllCompleted[this.idActivity] &&
275
+ this.getAllCompleted[this.idActivity].length
276
+ )
277
+ this.getbranchsData()
278
+ }
279
+ }
280
+ },
281
+ mounted() {
282
+ this.isCustomButton = this.customButtons && this.customButtons.length > 0
283
+ this.isCard = this.cards && Object.keys(this.cards).length != 0
284
+ this.idActivity = this.$router.currentRoute.value.meta.activity_ref
285
+ this.branchs = this.$router.currentRoute.value.meta.children
286
+ this.getbranchsData()
287
+ },
288
+ methods: {
289
+ /**
290
+ * @description Method to get the progression for a branch
291
+ *
292
+ */
293
+ getbranchsData() {
294
+ this.branchsStateData = []
295
+ this.branchs.forEach((branch, index) => {
296
+ let branchData = {}
297
+ branchData.id = branch._ref
298
+ branchData.route = branch._namedRoute
299
+ let { state = 'new', progression = 0 } = this.getBranchProgression(
300
+ branchData.id
301
+ )
302
+
303
+ branchData.state = state
304
+ branchData.progression = progression
305
+ if (isNaN(branchData.progression)) branchData.progression = 0
306
+
307
+ if (this.isCard) {
308
+ const { title, text, imgFile, imgAlt, btnTitle } =
309
+ this.getMatchingElement(branchData.id)
310
+
311
+ branchData = {
312
+ ...branchData,
313
+ text,
314
+ title,
315
+ imgFile,
316
+ imgAlt,
317
+ btnTitle
318
+ }
319
+
320
+ if (typeof branchData.imgAlt === 'undefined' && import.meta.env.DEV) {
321
+ branchData.imgAlt = ''
322
+ console.warn(
323
+ `Bouton d’embranchement: ALT image sans valeur définie pour ${branchData.id}`
324
+ )
325
+ }
326
+ }
327
+ this.branchsStateData[index] = branchData
328
+ })
329
+ },
330
+
331
+ /**
332
+ * @description Method to get the progression for a branch
333
+ * @param {String} idBranch the id of the branch ex: "P01_E01"
334
+ * @return {Number} Return the percentage of completion (from 0 to 100) and the current state of a specific branch
335
+ */
336
+ getBranchProgression(idBranch) {
337
+ let result = { state: null, progression: null }
338
+
339
+ if (!this.getAllCompleted[this.idActivity]) return result // no progression found for this activity return result
340
+
341
+ const total = this.getBranchPagesCount(idBranch) // get all pages for in this branch
342
+ //get all completed branches pages
343
+ const completedPages = this.getAllCompleted[this.idActivity].filter(
344
+ (page) => {
345
+ return Object.keys(page)[0].search(idBranch) !== -1
346
+ }
347
+ )
348
+ result.progression = Math.floor((completedPages.length / total) * 100)
349
+
350
+ if (isNaN(result.progression)) result.progression = 0
351
+
352
+ if (result.progression === 0) {
353
+ result.state = this.status.NEW
354
+ } else if (result.progression === 100) {
355
+ result.state = this.status.COMPLETE
356
+ } else {
357
+ result.state = this.status.STARTED
358
+ }
359
+ return result
360
+ },
361
+
362
+ /**
363
+ * @description Method to get the numbers of pages in a branch
364
+ * @param {String} idBranch the id of the branch ex: "A02_E01_P01"
365
+ * @return {Array} /Return the total of pages in a specific branch
366
+ */
367
+ getBranchPagesCount(idBranch) {
368
+ if (this.$router.currentRoute.value.meta.type !== 'branching') return
369
+
370
+ const allPages = this.getAllActivities()
371
+ .list.get(this.idActivity)
372
+ .get(this.$router.currentRoute.value.meta.id)
373
+ .get(idBranch)
374
+
375
+ return allPages.size ? allPages.size : 1
376
+ },
377
+
378
+ /**
379
+ * @description Method to validate data in the prop card|customButton.
380
+ * @param {String} propName The name of the property card|customButton
381
+ * @return {String} errMsg false | string message
382
+ */
383
+ validateProp(propName) {
384
+ let isValid = true
385
+ let errMsg = false
386
+
387
+ switch (propName) {
388
+ case 'cards': {
389
+ // verify that numbers of element is equal to number
390
+ //card must have at least 2 elements
391
+ if (
392
+ this.cards.constructor !== Array ||
393
+ this.cards.length !== this.branchs.length
394
+ ) {
395
+ isValid = false
396
+ errMsg = `La propriété cards doit être un tableau contenant ${this.branchs.length} elements`
397
+ } else {
398
+ //Start validation of content of each object in cards
399
+ for (let el of this.cards) {
400
+ //Each element in card must be of type Object and can not be empty
401
+ if (el.constructor !== Object) {
402
+ isValid = false
403
+ let index = this.cards.indexOf(el)
404
+ errMsg = `Le data pour la carte ${index + 1} n'est pas de type Object`
405
+ }
406
+ if (!isValid) break
407
+ }
408
+ //Start validation of required keys
409
+ if (isValid) {
410
+ const requiredKeys = ['imgFile', 'title', 'text', 'brchName']
411
+
412
+ for (let el of this.cards) {
413
+ let index = this.cards.indexOf(el)
414
+ //Validate that require key exist for each element in card
415
+ for (let k of requiredKeys) {
416
+ if (!el[k]) {
417
+ isValid = false
418
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la carte ${index + 1} n'as pas été défini`
419
+ break
420
+ } else {
421
+ if (el[k].constructor !== String) {
422
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la carte ${
423
+ index + 1
424
+ } doit être de type {String}`
425
+ isValid = false
426
+ break
427
+ }
428
+ }
429
+ if (!isValid) break
430
+ }
431
+
432
+ // Start validation of existing branch for this element
433
+ let searchEl = this.branchs.find((b) =>
434
+ b._ref.includes(el.brchName)
435
+ )
436
+
437
+ if (!searchEl) {
438
+ errMsg = `Aucune branch correspondante avec le nom ${el.brchName} pour la carte ${
439
+ index + 1
440
+ }. Êtes vous sûre d'avoir indiqué la bonne branch?`
441
+ isValid = false
442
+ }
443
+
444
+ if (!isValid) break
445
+ }
446
+ }
447
+ }
448
+ break
449
+ }
450
+
451
+ case 'customButtons': {
452
+ if (
453
+ this.customButtons.constructor !== Array ||
454
+ this.customButtons.length !== this.branchs.length
455
+ ) {
456
+ isValid = false
457
+ errMsg = `La propriété customButtons doit être un tableau contenant ${this.branchs.length} elements`
458
+ } else {
459
+ //Start validation of content of each object in customButtons
460
+ for (let el of this.customButtons) {
461
+ //Each element in card must be of type Object and can not be empty
462
+ if (el.constructor !== Object) {
463
+ isValid = false
464
+ let index = this.customButtons.indexOf(el)
465
+ errMsg = `Le data pour le button ${index + 1} n'est pas de type {Object}`
466
+ }
467
+ if (!isValid) break
468
+ }
469
+
470
+ //Start validation of required keys
471
+ if (isValid) {
472
+ const requiredKeys = ['name', 'brchName']
473
+
474
+ for (let el of this.customButtons) {
475
+ //Validate that require key exist for each element for customButton
476
+ let index = this.customButtons.indexOf(el)
477
+ for (let k of requiredKeys) {
478
+ if (!el[k]) {
479
+ isValid = false
480
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${index + 1} n'as pas été défini`
481
+ break
482
+ } else {
483
+ // Expecting String for properties name and brchName
484
+ if (
485
+ ['name', 'brchName'].includes(k) &&
486
+ el[k].constructor !== String
487
+ ) {
488
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${
489
+ index + 1
490
+ } doit être de type {String}`
491
+ isValid = false
492
+ break
493
+ }
494
+ }
495
+ if (!isValid) break
496
+ }
497
+
498
+ // Start validation of existing branch for this element
499
+ let searchEl = this.branchs.find((b) =>
500
+ b._ref.includes(el.brchName)
501
+ )
502
+
503
+ if (!searchEl) {
504
+ errMsg = `Aucune branch correspondante avec le nom ${
505
+ el.brchName
506
+ } pour le bouton ${index + 1}. Êtes vous sûre d'avoir indiqué la bonne branch?`
507
+ isValid = false
508
+ }
509
+
510
+ if (!isValid) break
511
+ }
512
+ }
513
+ }
514
+ break
515
+ }
516
+ }
517
+
518
+ return errMsg
519
+ },
520
+
521
+ /**
522
+ * @description methode to retive matching data of branch in the prop card| customButton
523
+ * @param {String} id The id of the branch
524
+ * @return {Object} Matching element from
525
+ */
526
+ getMatchingElement(id) {
527
+ if (this.isCustomButton) {
528
+ return this.customButtons.find((c) => c.brchName === id)
529
+ }
530
+ if (this.isCard) {
531
+ return this.cards.find((c) => c.brchName === id)
532
+ }
533
+ }
534
+ }
535
+ }
536
+ </script>
537
+
538
+ <style lang="scss">
539
+ .branch-btn-wrapper {
540
+ position: relative;
541
+
542
+ .branch-btn-custom {
543
+ background-color: transparent;
544
+ border: none;
545
+ }
546
+ }
547
+
548
+ .v-card {
549
+ position: relative;
550
+ .button-progress-wrapper {
551
+ position: absolute;
552
+ right: 55px;
553
+ top: 15px;
554
+ }
555
+ }
556
+ </style>