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

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 (95) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/.eslintrc.cjs +81 -81
  4. package/CHANGELOG +373 -364
  5. package/README.md +71 -71
  6. package/bk.scss +117 -117
  7. package/package.json +61 -61
  8. package/src/$locales/en.json +143 -143
  9. package/src/$locales/fr.json +105 -105
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1147 -1054
  12. package/src/components/AppBaseButton.vue +87 -87
  13. package/src/components/AppBaseErrorDisplay.vue +438 -438
  14. package/src/components/AppBaseFlipCard.vue +84 -84
  15. package/src/components/AppBaseModule.vue +1636 -1673
  16. package/src/components/AppBasePage.vue +779 -779
  17. package/src/components/AppBasePopover.vue +41 -41
  18. package/src/components/AppCompAudio.vue +234 -234
  19. package/src/components/AppCompBranchButtons.vue +552 -552
  20. package/src/components/AppCompButtonProgress.vue +126 -126
  21. package/src/components/AppCompCarousel.vue +298 -298
  22. package/src/components/AppCompInputCheckBoxNext.vue +195 -195
  23. package/src/components/AppCompInputDropdownNext.vue +159 -159
  24. package/src/components/AppCompInputRadioNext.vue +152 -152
  25. package/src/components/AppCompInputTextNext.vue +106 -106
  26. package/src/components/AppCompInputTextTableNext.vue +141 -141
  27. package/src/components/AppCompInputTextToFillDropdownNext.vue +230 -230
  28. package/src/components/AppCompInputTextToFillNext.vue +171 -171
  29. package/src/components/AppCompJauge.vue +74 -74
  30. package/src/components/AppCompMenu.vue +423 -413
  31. package/src/components/AppCompMenuItem.vue +228 -228
  32. package/src/components/AppCompNavigation.vue +959 -960
  33. package/src/components/AppCompNoteCall.vue +133 -133
  34. package/src/components/AppCompNoteCredit.vue +292 -292
  35. package/src/components/AppCompPlayBar.vue +1218 -1218
  36. package/src/components/AppCompPlayBarNext.vue +2052 -2052
  37. package/src/components/AppCompPlayBarProgress.vue +82 -82
  38. package/src/components/AppCompPopUpNext.vue +503 -503
  39. package/src/components/AppCompQuizNext.vue +2904 -2904
  40. package/src/components/AppCompQuizRecall.vue +276 -276
  41. package/src/components/AppCompSVGNext.vue +347 -347
  42. package/src/components/AppCompSettingsMenu.vue +172 -172
  43. package/src/components/AppCompTableOfContent.vue +387 -387
  44. package/src/components/AppCompTranscript.vue +24 -24
  45. package/src/components/AppCompVideoPlayer.vue +368 -368
  46. package/src/components/AppCompViewDisplay.vue +6 -6
  47. package/src/components/BaseModule.vue +72 -72
  48. package/src/composables/useQuiz.js +206 -206
  49. package/src/externalComps/ModuleView.vue +22 -22
  50. package/src/externalComps/SummaryView.vue +91 -91
  51. package/src/main.js +272 -272
  52. package/src/mixins/$mediaMixins.js +819 -819
  53. package/src/mixins/timerMixin.js +155 -155
  54. package/src/module/stores/appStore.js +901 -893
  55. package/src/module/xapi/ADL.js +380 -376
  56. package/src/module/xapi/Crypto/Hasher.js +241 -241
  57. package/src/module/xapi/Crypto/WordArray.js +278 -278
  58. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  59. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  60. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  61. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  62. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  63. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  64. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  65. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  66. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  67. package/src/module/xapi/Crypto/index.js +53 -53
  68. package/src/module/xapi/Statement/activity.js +47 -47
  69. package/src/module/xapi/Statement/agent.js +55 -55
  70. package/src/module/xapi/Statement/group.js +26 -26
  71. package/src/module/xapi/Statement/index.js +259 -259
  72. package/src/module/xapi/Statement/statement.js +253 -253
  73. package/src/module/xapi/Statement/statementRef.js +23 -23
  74. package/src/module/xapi/Statement/substatement.js +22 -22
  75. package/src/module/xapi/Statement/verb.js +36 -36
  76. package/src/module/xapi/activitytypes.js +17 -17
  77. package/src/module/xapi/launch.js +157 -157
  78. package/src/module/xapi/utils.js +167 -167
  79. package/src/module/xapi/verbs.js +294 -294
  80. package/src/module/xapi/wrapper.js +1963 -1963
  81. package/src/module/xapi/xapiStatement.js +444 -444
  82. package/src/plugins/bus.js +8 -8
  83. package/src/plugins/gsap.js +14 -14
  84. package/src/plugins/helper.js +314 -308
  85. package/src/plugins/i18n.js +44 -44
  86. package/src/plugins/idb.js +227 -219
  87. package/src/plugins/save.js +37 -37
  88. package/src/plugins/scorm.js +287 -287
  89. package/src/plugins/xapi.js +11 -11
  90. package/src/public/index.html +33 -33
  91. package/src/router/index.js +43 -43
  92. package/src/router/routes.js +312 -312
  93. package/src/shared/generalfuncs.js +210 -210
  94. package/src/shared/validators.js +1069 -1069
  95. package/vite.config.js +0 -27
@@ -1,552 +1,552 @@
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.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>