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

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