fcad-core-dragon 2.1.1 → 2.1.2

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 (160) hide show
  1. package/.editorconfig +7 -7
  2. package/.gitlab-ci.yml +124 -0
  3. package/.prettierrc +11 -11
  4. package/.vscode/extensions.json +8 -8
  5. package/.vscode/settings.json +46 -16
  6. package/CHANGELOG +520 -520
  7. package/README.md +57 -57
  8. package/documentation/.vitepress/config.js +114 -114
  9. package/documentation/api-examples.md +49 -49
  10. package/documentation/composants/app-base-button.md +58 -58
  11. package/documentation/composants/app-base-error-display.md +59 -59
  12. package/documentation/composants/app-base-popover.md +68 -68
  13. package/documentation/composants/app-comp-audio.md +75 -75
  14. package/documentation/composants/app-comp-branch-buttons.md +111 -111
  15. package/documentation/composants/app-comp-button-progress.md +53 -53
  16. package/documentation/composants/app-comp-carousel.md +53 -53
  17. package/documentation/composants/app-comp-container.md +53 -53
  18. package/documentation/composants/app-comp-input-checkbox-next.md +42 -42
  19. package/documentation/composants/app-comp-input-dropdown-next.md +34 -34
  20. package/documentation/composants/app-comp-input-radio-next.md +39 -39
  21. package/documentation/composants/app-comp-input-text-next.md +35 -35
  22. package/documentation/composants/app-comp-input-text-table-next.md +34 -34
  23. package/documentation/composants/app-comp-input-text-to-fill-dropdown-next.md +53 -53
  24. package/documentation/composants/app-comp-input-text-to-fill-next.md +31 -31
  25. package/documentation/composants/app-comp-jauge.md +31 -31
  26. package/documentation/composants/app-comp-menu-item.md +55 -55
  27. package/documentation/composants/app-comp-menu.md +29 -29
  28. package/documentation/composants/app-comp-navigation.md +41 -41
  29. package/documentation/composants/app-comp-note-call.md +53 -53
  30. package/documentation/composants/app-comp-note-credit.md +53 -53
  31. package/documentation/composants/app-comp-play-bar-next.md +53 -53
  32. package/documentation/composants/app-comp-pop-up-next.md +93 -93
  33. package/documentation/composants/app-comp-quiz-next.md +235 -235
  34. package/documentation/composants/app-comp-quiz-recall.md +53 -53
  35. package/documentation/composants/app-comp-svg-next.md +53 -53
  36. package/documentation/composants/app-comp-table-of-content.md +50 -50
  37. package/documentation/composants/app-comp-video-player.md +82 -82
  38. package/documentation/composants.md +46 -46
  39. package/documentation/composants_critiques/ModelPageComposant.md +53 -53
  40. package/documentation/composants_critiques/app-base-module.md +43 -43
  41. package/documentation/composants_critiques/app-base-page.md +48 -48
  42. package/documentation/composants_critiques/app-base.md +311 -311
  43. package/documentation/composants_critiques/main.md +15 -15
  44. package/documentation/demarrage.md +50 -50
  45. package/documentation/deploiement.md +57 -57
  46. package/documentation/index.md +33 -33
  47. package/documentation/markdown-examples.md +85 -85
  48. package/documentation/public/vite.svg +14 -14
  49. package/documentation/public/vuejs.svg +1 -1
  50. package/documentation/public/vuetify.svg +5 -5
  51. package/eslint.config.js +60 -60
  52. package/junit-report.xml +182 -0
  53. package/package.json +66 -59
  54. package/playwright/index.html +12 -0
  55. package/playwright/index.js +21 -0
  56. package/playwright-ct.config.js +95 -0
  57. package/src/$locales/en.json +157 -157
  58. package/src/$locales/fr.json +120 -120
  59. package/src/assets/data/onboardingMessages.json +47 -47
  60. package/src/components/AppBase.vue +1171 -1169
  61. package/src/components/AppBaseButton.vue +90 -95
  62. package/src/components/AppBaseErrorDisplay.vue +438 -438
  63. package/src/components/AppBaseFlipCard.vue +84 -84
  64. package/src/components/AppBaseModule.vue +1639 -1634
  65. package/src/components/AppBasePage.vue +867 -866
  66. package/src/components/AppBasePopover.vue +41 -41
  67. package/src/components/AppBaseSkeleton.vue +66 -66
  68. package/src/components/AppCompAudio.vue +261 -256
  69. package/src/components/AppCompBranchButtons.vue +508 -508
  70. package/src/components/AppCompButtonProgress.vue +137 -132
  71. package/src/components/AppCompCarousel.vue +342 -336
  72. package/src/components/AppCompContainer.vue +29 -29
  73. package/src/components/AppCompInputCheckBoxNx.vue +325 -323
  74. package/src/components/AppCompInputDropdownNx.vue +302 -299
  75. package/src/components/AppCompInputRadioNx.vue +287 -284
  76. package/src/components/AppCompInputTextNx.vue +156 -153
  77. package/src/components/AppCompInputTextTableNx.vue +205 -202
  78. package/src/components/AppCompInputTextToFillDropdownNx.vue +343 -340
  79. package/src/components/AppCompInputTextToFillNx.vue +316 -313
  80. package/src/components/AppCompJauge.vue +81 -81
  81. package/src/components/AppCompMenu.vue +6 -1
  82. package/src/components/AppCompMenuItem.vue +246 -240
  83. package/src/components/AppCompNavigation.vue +977 -972
  84. package/src/components/AppCompNoteCall.vue +167 -161
  85. package/src/components/AppCompNoteCredit.vue +496 -491
  86. package/src/components/AppCompPlayBarNext.vue +2290 -2288
  87. package/src/components/AppCompPopUpNext.vue +508 -504
  88. package/src/components/AppCompQuizNext.vue +515 -510
  89. package/src/components/AppCompQuizRecall.vue +355 -350
  90. package/src/components/AppCompSVGNext.vue +346 -346
  91. package/src/components/AppCompSettingsMenu.vue +177 -172
  92. package/src/components/AppCompTableOfContent.vue +433 -427
  93. package/src/components/AppCompVideoPlayer.vue +377 -377
  94. package/src/components/AppCompViewDisplay.vue +6 -6
  95. package/src/components/BaseModule.vue +55 -55
  96. package/src/composables/useIdleDetector.js +56 -56
  97. package/src/composables/useQuiz.js +89 -89
  98. package/src/composables/useTimer.js +172 -172
  99. package/src/directives/nvdaFix.js +53 -53
  100. package/src/externalComps/ModuleView.vue +22 -22
  101. package/src/externalComps/SummaryView.vue +91 -91
  102. package/src/main.js +493 -476
  103. package/src/module/stores/appStore.js +960 -947
  104. package/src/module/xapi/ADL.js +520 -520
  105. package/src/module/xapi/Crypto/Hasher.js +241 -241
  106. package/src/module/xapi/Crypto/WordArray.js +278 -278
  107. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  108. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  109. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  110. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  111. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  112. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  113. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  114. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  115. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  116. package/src/module/xapi/Crypto/index.js +53 -53
  117. package/src/module/xapi/Statement/activity.js +47 -47
  118. package/src/module/xapi/Statement/agent.js +55 -55
  119. package/src/module/xapi/Statement/group.js +26 -26
  120. package/src/module/xapi/Statement/index.js +259 -259
  121. package/src/module/xapi/Statement/statement.js +253 -253
  122. package/src/module/xapi/Statement/statementRef.js +23 -23
  123. package/src/module/xapi/Statement/substatement.js +22 -22
  124. package/src/module/xapi/Statement/verb.js +36 -36
  125. package/src/module/xapi/activitytypes.js +17 -17
  126. package/src/module/xapi/launch.js +157 -157
  127. package/src/module/xapi/utils.js +167 -167
  128. package/src/module/xapi/verbs.js +294 -294
  129. package/src/module/xapi/wrapper.js +1895 -1895
  130. package/src/module/xapi/xapiStatement.js +444 -444
  131. package/src/plugins/analytics.js +34 -34
  132. package/src/plugins/bus.js +12 -8
  133. package/src/plugins/gsap.js +17 -17
  134. package/src/plugins/helper.js +355 -358
  135. package/src/plugins/i18n.js +27 -26
  136. package/src/plugins/idb.js +227 -227
  137. package/src/plugins/save.js +37 -37
  138. package/src/plugins/scorm.js +287 -287
  139. package/src/plugins/xapi.js +11 -11
  140. package/src/public/index.html +33 -33
  141. package/src/router/index.js +57 -57
  142. package/src/router/routes.js +312 -312
  143. package/src/shared/generalfuncs.js +344 -344
  144. package/src/shared/validators.js +1018 -1018
  145. package/tests/component/AppBaseButton.spec.js +53 -0
  146. package/tests/component/pinia.spec.js +24 -0
  147. package/{src/components/tests__ → tests/unit}/AppBaseButton.spec.js +53 -53
  148. package/tests/unit/AppCompInputCheckBoxNx.spec.js +59 -0
  149. package/tests/unit/AppCompInputDropdownNx.spec.js +51 -0
  150. package/tests/unit/AppCompInputRadioNx.spec.js +59 -0
  151. package/tests/unit/AppCompInputTextNx.spec.js +44 -0
  152. package/tests/unit/AppCompInputTextTableNx.spec.js +77 -0
  153. package/tests/unit/AppCompInputTextToFillDropdownNx.spec.js +60 -0
  154. package/tests/unit/AppCompInputTextToFillNx.spec.js +45 -0
  155. package/tests/unit/AppCompQuizNext.spec.js +114 -0
  156. package/tests/unit/AppCompVideoPlayer.spec.js +177 -0
  157. package/{src/components/tests__ → tests/unit}/useTimer.spec.js +91 -91
  158. package/vitest.config.js +28 -19
  159. package/vitest.setup.js +28 -0
  160. package/src/components/AppBaseButton.test.js +0 -21
@@ -1,508 +1,508 @@
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-card-title v-html="branch.title"></v-card-title>
70
- <app-comp-button-progress
71
- :set-target="sidebar"
72
- :percent="branch.progression ? branch.progression : 0"
73
- :branch-data="branch"
74
- :btn-title="getMatchingElement(branch.id).btnTitle"
75
- ></app-comp-button-progress>
76
- <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover />
77
- <v-card-text v-html="branch.text"></v-card-text>
78
- </v-card>
79
- </div>
80
- <div v-else class="branch-btn-wrapper">
81
- <app-comp-button-progress
82
- :set-target="sidebar"
83
- :percent="branch.progression ? branch.progression : 0"
84
- :branch-data="branch"
85
- :btn-title="branch.text"
86
- ></app-comp-button-progress>
87
- </div>
88
- </v-col>
89
- </v-row>
90
- </v-row>
91
- </template>
92
- <script>
93
- import { mapState } from 'pinia'
94
- import { useAppStore } from '../module/stores/appStore'
95
- import AppCompButtonProgress from './AppCompButtonProgress.vue'
96
-
97
- export default {
98
- components: {
99
- AppCompButtonProgress
100
- },
101
- props: {
102
- consigne: {
103
- type: Boolean,
104
- default: false
105
- },
106
- sidebar: {
107
- type: String,
108
- default: 'branch-viewer'
109
- },
110
- btnTitle: {
111
- type: String,
112
- default: ' '
113
- },
114
- cards: {
115
- type: Array,
116
- default: () => null,
117
- validator(dataArray) {
118
- let isValid = true
119
- if (import.meta.env.DEV) {
120
- let errMsg = false
121
-
122
- //card must have at least 2 elements
123
- if (dataArray.length < 1) {
124
- isValid = false
125
- errMsg = `\n card require at least 1 element`
126
- } else {
127
- for (let el of dataArray) {
128
- //Each element in card must be of type Object and can not be empty
129
- if (el.constructor !== Object) {
130
- isValid = false
131
- errMsg = `\n Element in card must be of type Object`
132
- break
133
- }
134
- }
135
- }
136
-
137
- if (errMsg) console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
138
- }
139
- return isValid
140
- }
141
- },
142
- customButtons: {
143
- type: Array,
144
- default: () => null,
145
- validator: (dataArray) => {
146
- let isValid = true
147
- if (import.meta.env.DEV) {
148
- let errMsg = false
149
- //card must have at least 1 elements
150
- if (dataArray.length < 1) {
151
- isValid = false
152
- errMsg = `\n customButton require at least 1 element`
153
- } else {
154
- for (let el of dataArray) {
155
- //Each element in card must be of type Object and can not be empty
156
- if (el.constructor !== Object) {
157
- isValid = false
158
- errMsg = `\n Element in customButton must be of type Object`
159
- break
160
- }
161
- }
162
- //Start validation of keys if no error detected
163
- if (isValid) {
164
- const requiredKeys = ['name', 'brchName']
165
- for (let el of dataArray) {
166
- //Validate that require key exist for each element in card
167
- for (let k of requiredKeys) {
168
- let index = dataArray.indexOf(el)
169
-
170
- if (!el[k]) {
171
- isValid = false
172
- errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
173
- index + 1
174
- } in ➡ customButtom \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
175
- break
176
- } else {
177
- if (el[k].constructor !== String) {
178
- errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
179
- index + 1
180
- } ➡ of customButton. \n 🚩 Must be of type {String}`
181
- isValid = false
182
- break
183
- }
184
- }
185
- if (!isValid) break
186
- }
187
- if (!isValid) break
188
- }
189
- }
190
- if (errMsg)
191
- console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
192
- }
193
- }
194
- return isValid
195
- }
196
- }
197
- },
198
- data() {
199
- const svgRadius = 23
200
- return {
201
- errors: [],
202
- radius: svgRadius,
203
- circumference: svgRadius * 2 * Math.PI,
204
- branchs: null,
205
- branchsStateData: null,
206
- idActivity: null,
207
- isCustomButton: false,
208
- isCard: false,
209
- customButtonStyle: null,
210
- status: {
211
- NEW: 'new',
212
- STARTED: 'started',
213
- COMPLETE: 'complete'
214
- },
215
- hasErr: null
216
- }
217
- },
218
- computed: {
219
- ...mapState(useAppStore, [
220
- 'getAllCompleted',
221
- 'getAllActivities',
222
- 'getConnectionInfo'
223
- ]),
224
- hasErrors() {
225
- let error = false
226
- if (import.meta.env.DEV) {
227
- if (this.isCard) error = this.validateProp('cards')
228
- else if (this.isCustomButton) error = this.validateProp('customButtons')
229
- else error = false
230
- }
231
-
232
- return error
233
- }
234
- },
235
- watch: {
236
- getAllCompleted: {
237
- deep: true,
238
- //watch the completions to update the branches progression
239
- handler() {
240
- if (
241
- this.getAllCompleted[this.idActivity] &&
242
- this.getAllCompleted[this.idActivity].length
243
- )
244
- this.getbranchsData()
245
- }
246
- }
247
- },
248
- mounted() {
249
- this.isCustomButton = this.customButtons && this.customButtons.length > 0
250
- this.isCard = this.cards && Object.keys(this.cards).length != 0
251
- this.idActivity = this.$router.currentRoute.value.meta.activity_ref
252
- this.branchs = this.$router.currentRoute.value.meta.children
253
- this.getbranchsData()
254
- },
255
- methods: {
256
- /**
257
- * @description Method to get the progression for a branch
258
- *
259
- */
260
- getbranchsData() {
261
- this.branchsStateData = []
262
- this.branchs.forEach((branch, index) => {
263
- let branchData = {}
264
- branchData.id = branch._ref
265
- branchData.route = branch._namedRoute
266
- let { state = 'new', progression = 0 } = this.getBranchProgression(
267
- branchData.id
268
- )
269
-
270
- branchData.state = state
271
- branchData.progression = progression
272
- if (isNaN(branchData.progression)) branchData.progression = 0
273
-
274
- if (this.isCard) {
275
- const { title, text, imgFile, imgAlt, btnTitle } =
276
- this.getMatchingElement(branchData.id)
277
-
278
- branchData = {
279
- ...branchData,
280
- text,
281
- title,
282
- imgFile,
283
- imgAlt,
284
- btnTitle
285
- }
286
-
287
- if (
288
- typeof branchData.imgAlt === 'undefined' &&
289
- branchData.imgFile === 'undefined' &&
290
- import.meta.env.DEV
291
- ) {
292
- branchData.imgAlt = ''
293
- console.warn(
294
- `Bouton d’embranchement: ALT image sans valeur définie pour ${branchData.id}`
295
- )
296
- }
297
- }
298
- this.branchsStateData[index] = branchData
299
- })
300
- },
301
-
302
- /**
303
- * @description Method to get the progression for a branch
304
- * @param {String} idBranch the id of the branch ex: "P01_E01"
305
- * @return {Number} Return the percentage of completion (from 0 to 100) and the current state of a specific branch
306
- */
307
- getBranchProgression(idBranch) {
308
- let result = { state: null, progression: null }
309
-
310
- if (!this.getAllCompleted[this.idActivity]) return result // no progression found for this activity return result
311
-
312
- const total = this.getBranchPagesCount(idBranch) // get all pages for in this branch
313
- //get all completed branches pages
314
- const completedPages = this.getAllCompleted[this.idActivity].filter(
315
- (page) => {
316
- return Object.keys(page)[0].search(idBranch) !== -1
317
- }
318
- )
319
- result.progression = Math.floor((completedPages.length / total) * 100)
320
-
321
- if (isNaN(result.progression)) result.progression = 0
322
-
323
- if (result.progression === 0) {
324
- result.state = this.status.NEW
325
- } else if (result.progression === 100) {
326
- result.state = this.status.COMPLETE
327
- } else {
328
- result.state = this.status.STARTED
329
- }
330
- return result
331
- },
332
-
333
- /**
334
- * @description Method to get the numbers of pages in a branch
335
- * @param {String} idBranch the id of the branch ex: "A02_E01_P01"
336
- * @return {Array} /Return the total of pages in a specific branch
337
- */
338
- getBranchPagesCount(idBranch) {
339
- if (this.$router.currentRoute.value.meta.type !== 'branching') return
340
-
341
- const allPages = this.getAllActivities()
342
- .list.get(this.idActivity)
343
- .get(this.$router.currentRoute.value.meta.id)
344
- .get(idBranch)
345
-
346
- return allPages.size ? allPages.size : 1
347
- },
348
-
349
- /**
350
- * @description Method to validate data in the prop card|customButton.
351
- * @param {String} propName The name of the property card|customButton
352
- * @return {String} errMsg false | string message
353
- */
354
- validateProp(propName) {
355
- let isValid = true
356
- let errMsg = false
357
-
358
- switch (propName) {
359
- case 'cards': {
360
- // verify that numbers of element is equal to number
361
- //card must have at least 2 elements
362
- if (
363
- this.cards.constructor !== Array ||
364
- this.cards.length !== this.branchs.length
365
- ) {
366
- isValid = false
367
- errMsg = `La propriété cards doit être un tableau contenant ${this.branchs.length} elements`
368
- } else {
369
- //Start validation of content of each object in cards
370
- for (let el of this.cards) {
371
- //Each element in card must be of type Object and can not be empty
372
- if (el.constructor !== Object) {
373
- isValid = false
374
- let index = this.cards.indexOf(el)
375
- errMsg = `Le data pour la carte ${index + 1} n'est pas de type Object`
376
- }
377
- if (!isValid) break
378
- }
379
- //Start validation of required keys
380
- if (isValid) {
381
- for (let el of this.cards) {
382
- let index = this.cards.indexOf(el)
383
-
384
- // Start validation of existing branch for this element
385
- let searchEl = this.branchs.find((b) =>
386
- b._ref.includes(el.brchName)
387
- )
388
-
389
- if (!searchEl) {
390
- errMsg = `Aucune branch correspondante avec le nom ${el.brchName} pour la carte ${
391
- index + 1
392
- }. Êtes vous sûre d'avoir indiqué la bonne branch?`
393
- isValid = false
394
- }
395
-
396
- if (!isValid) break
397
- }
398
- }
399
- }
400
- break
401
- }
402
-
403
- case 'customButtons': {
404
- if (
405
- this.customButtons.constructor !== Array ||
406
- this.customButtons.length !== this.branchs.length
407
- ) {
408
- isValid = false
409
- errMsg = `La propriété customButtons doit être un tableau contenant ${this.branchs.length} elements`
410
- } else {
411
- //Start validation of content of each object in customButtons
412
- for (let el of this.customButtons) {
413
- //Each element in card must be of type Object and can not be empty
414
- if (el.constructor !== Object) {
415
- isValid = false
416
- let index = this.customButtons.indexOf(el)
417
- errMsg = `Le data pour le button ${index + 1} n'est pas de type {Object}`
418
- }
419
- if (!isValid) break
420
- }
421
-
422
- //Start validation of required keys
423
- if (isValid) {
424
- const requiredKeys = ['name', 'brchName']
425
-
426
- for (let el of this.customButtons) {
427
- //Validate that require key exist for each element for customButton
428
- let index = this.customButtons.indexOf(el)
429
- for (let k of requiredKeys) {
430
- if (!el[k]) {
431
- isValid = false
432
- errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${index + 1} n'as pas été défini`
433
- break
434
- } else {
435
- // Expecting String for properties name and brchName
436
- if (
437
- ['name', 'brchName'].includes(k) &&
438
- el[k].constructor !== String
439
- ) {
440
- errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${
441
- index + 1
442
- } doit être de type {String}`
443
- isValid = false
444
- break
445
- }
446
- }
447
- if (!isValid) break
448
- }
449
-
450
- // Start validation of existing branch for this element
451
- let searchEl = this.branchs.find((b) =>
452
- b._ref.includes(el.brchName)
453
- )
454
-
455
- if (!searchEl) {
456
- errMsg = `Aucune branch correspondante avec le nom ${
457
- el.brchName
458
- } pour le bouton ${index + 1}. Êtes vous sûre d'avoir indiqué la bonne branch?`
459
- isValid = false
460
- }
461
-
462
- if (!isValid) break
463
- }
464
- }
465
- }
466
- break
467
- }
468
- }
469
-
470
- return errMsg
471
- },
472
-
473
- /**
474
- * @description methode to retive matching data of branch in the prop card| customButton
475
- * @param {String} id The id of the branch
476
- * @return {Object} Matching element from
477
- */
478
- getMatchingElement(id) {
479
- if (this.isCustomButton) {
480
- return this.customButtons.find((c) => c.brchName === id)
481
- }
482
- if (this.isCard) {
483
- return this.cards.find((c) => c.brchName === id)
484
- }
485
- }
486
- }
487
- }
488
- </script>
489
-
490
- <style lang="scss">
491
- .branch-btn-wrapper {
492
- position: relative;
493
-
494
- .branch-btn-custom {
495
- background-color: transparent;
496
- border: none;
497
- }
498
- }
499
-
500
- .v-card {
501
- position: relative;
502
- .button-progress-wrapper {
503
- position: absolute;
504
- right: 55px;
505
- top: 5px;
506
- }
507
- }
508
- </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-card-title v-html="branch.title"></v-card-title>
70
+ <app-comp-button-progress
71
+ :set-target="sidebar"
72
+ :percent="branch.progression ? branch.progression : 0"
73
+ :branch-data="branch"
74
+ :btn-title="getMatchingElement(branch.id).btnTitle"
75
+ ></app-comp-button-progress>
76
+ <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover />
77
+ <v-card-text v-html="branch.text"></v-card-text>
78
+ </v-card>
79
+ </div>
80
+ <div v-else class="branch-btn-wrapper">
81
+ <app-comp-button-progress
82
+ :set-target="sidebar"
83
+ :percent="branch.progression ? branch.progression : 0"
84
+ :branch-data="branch"
85
+ :btn-title="branch.text"
86
+ ></app-comp-button-progress>
87
+ </div>
88
+ </v-col>
89
+ </v-row>
90
+ </v-row>
91
+ </template>
92
+ <script>
93
+ import { mapState } from 'pinia'
94
+ import { useAppStore } from '../module/stores/appStore'
95
+ import AppCompButtonProgress from './AppCompButtonProgress.vue'
96
+
97
+ export default {
98
+ components: {
99
+ AppCompButtonProgress
100
+ },
101
+ props: {
102
+ consigne: {
103
+ type: Boolean,
104
+ default: false
105
+ },
106
+ sidebar: {
107
+ type: String,
108
+ default: 'branch-viewer'
109
+ },
110
+ btnTitle: {
111
+ type: String,
112
+ default: ' '
113
+ },
114
+ cards: {
115
+ type: Array,
116
+ default: () => null,
117
+ validator(dataArray) {
118
+ let isValid = true
119
+ if (import.meta.env.DEV) {
120
+ let errMsg = false
121
+
122
+ //card must have at least 2 elements
123
+ if (dataArray.length < 1) {
124
+ isValid = false
125
+ errMsg = `\n card require at least 1 element`
126
+ } else {
127
+ for (let el of dataArray) {
128
+ //Each element in card must be of type Object and can not be empty
129
+ if (el.constructor !== Object) {
130
+ isValid = false
131
+ errMsg = `\n Element in card must be of type Object`
132
+ break
133
+ }
134
+ }
135
+ }
136
+
137
+ if (errMsg) console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
138
+ }
139
+ return isValid
140
+ }
141
+ },
142
+ customButtons: {
143
+ type: Array,
144
+ default: () => null,
145
+ validator: (dataArray) => {
146
+ let isValid = true
147
+ if (import.meta.env.DEV) {
148
+ let errMsg = false
149
+ //card must have at least 1 elements
150
+ if (dataArray.length < 1) {
151
+ isValid = false
152
+ errMsg = `\n customButton require at least 1 element`
153
+ } else {
154
+ for (let el of dataArray) {
155
+ //Each element in card must be of type Object and can not be empty
156
+ if (el.constructor !== Object) {
157
+ isValid = false
158
+ errMsg = `\n Element in customButton must be of type Object`
159
+ break
160
+ }
161
+ }
162
+ //Start validation of keys if no error detected
163
+ if (isValid) {
164
+ const requiredKeys = ['name', 'brchName']
165
+ for (let el of dataArray) {
166
+ //Validate that require key exist for each element in card
167
+ for (let k of requiredKeys) {
168
+ let index = dataArray.indexOf(el)
169
+
170
+ if (!el[k]) {
171
+ isValid = false
172
+ errMsg = `\n 💥 Missing 👉 ${k} 👈 in object ${
173
+ index + 1
174
+ } in ➡ customButtom \n 🚩Allowed indexes are: 👉 ${requiredKeys}`
175
+ break
176
+ } else {
177
+ if (el[k].constructor !== String) {
178
+ errMsg = `\n 💥 Invalid 👉 ${k} 👈 declaration in ➡ object ${
179
+ index + 1
180
+ } ➡ of customButton. \n 🚩 Must be of type {String}`
181
+ isValid = false
182
+ break
183
+ }
184
+ }
185
+ if (!isValid) break
186
+ }
187
+ if (!isValid) break
188
+ }
189
+ }
190
+ if (errMsg)
191
+ console.error(`🧱 app-comp-branch-buttons \n ${errMsg}`)
192
+ }
193
+ }
194
+ return isValid
195
+ }
196
+ }
197
+ },
198
+ data() {
199
+ const svgRadius = 23
200
+ return {
201
+ errors: [],
202
+ radius: svgRadius,
203
+ circumference: svgRadius * 2 * Math.PI,
204
+ branchs: null,
205
+ branchsStateData: null,
206
+ idActivity: null,
207
+ isCustomButton: false,
208
+ isCard: false,
209
+ customButtonStyle: null,
210
+ status: {
211
+ NEW: 'new',
212
+ STARTED: 'started',
213
+ COMPLETE: 'complete'
214
+ },
215
+ hasErr: null
216
+ }
217
+ },
218
+ computed: {
219
+ ...mapState(useAppStore, [
220
+ 'getAllCompleted',
221
+ 'getAllActivities',
222
+ 'getConnectionInfo'
223
+ ]),
224
+ hasErrors() {
225
+ let error = false
226
+ if (import.meta.env.DEV) {
227
+ if (this.isCard) error = this.validateProp('cards')
228
+ else if (this.isCustomButton) error = this.validateProp('customButtons')
229
+ else error = false
230
+ }
231
+
232
+ return error
233
+ }
234
+ },
235
+ watch: {
236
+ getAllCompleted: {
237
+ deep: true,
238
+ //watch the completions to update the branches progression
239
+ handler() {
240
+ if (
241
+ this.getAllCompleted[this.idActivity] &&
242
+ this.getAllCompleted[this.idActivity].length
243
+ )
244
+ this.getbranchsData()
245
+ }
246
+ }
247
+ },
248
+ mounted() {
249
+ this.isCustomButton = this.customButtons && this.customButtons.length > 0
250
+ this.isCard = this.cards && Object.keys(this.cards).length != 0
251
+ this.idActivity = this.$router.currentRoute.value.meta.activity_ref
252
+ this.branchs = this.$router.currentRoute.value.meta.children
253
+ this.getbranchsData()
254
+ },
255
+ methods: {
256
+ /**
257
+ * @description Method to get the progression for a branch
258
+ *
259
+ */
260
+ getbranchsData() {
261
+ this.branchsStateData = []
262
+ this.branchs.forEach((branch, index) => {
263
+ let branchData = {}
264
+ branchData.id = branch._ref
265
+ branchData.route = branch._namedRoute
266
+ let { state = 'new', progression = 0 } = this.getBranchProgression(
267
+ branchData.id
268
+ )
269
+
270
+ branchData.state = state
271
+ branchData.progression = progression
272
+ if (isNaN(branchData.progression)) branchData.progression = 0
273
+
274
+ if (this.isCard) {
275
+ const { title, text, imgFile, imgAlt, btnTitle } =
276
+ this.getMatchingElement(branchData.id)
277
+
278
+ branchData = {
279
+ ...branchData,
280
+ text,
281
+ title,
282
+ imgFile,
283
+ imgAlt,
284
+ btnTitle
285
+ }
286
+
287
+ if (
288
+ typeof branchData.imgAlt === 'undefined' &&
289
+ branchData.imgFile === 'undefined' &&
290
+ import.meta.env.DEV
291
+ ) {
292
+ branchData.imgAlt = ''
293
+ console.warn(
294
+ `Bouton d’embranchement: ALT image sans valeur définie pour ${branchData.id}`
295
+ )
296
+ }
297
+ }
298
+ this.branchsStateData[index] = branchData
299
+ })
300
+ },
301
+
302
+ /**
303
+ * @description Method to get the progression for a branch
304
+ * @param {String} idBranch the id of the branch ex: "P01_E01"
305
+ * @return {Number} Return the percentage of completion (from 0 to 100) and the current state of a specific branch
306
+ */
307
+ getBranchProgression(idBranch) {
308
+ let result = { state: null, progression: null }
309
+
310
+ if (!this.getAllCompleted[this.idActivity]) return result // no progression found for this activity return result
311
+
312
+ const total = this.getBranchPagesCount(idBranch) // get all pages for in this branch
313
+ //get all completed branches pages
314
+ const completedPages = this.getAllCompleted[this.idActivity].filter(
315
+ (page) => {
316
+ return Object.keys(page)[0].search(idBranch) !== -1
317
+ }
318
+ )
319
+ result.progression = Math.floor((completedPages.length / total) * 100)
320
+
321
+ if (isNaN(result.progression)) result.progression = 0
322
+
323
+ if (result.progression === 0) {
324
+ result.state = this.status.NEW
325
+ } else if (result.progression === 100) {
326
+ result.state = this.status.COMPLETE
327
+ } else {
328
+ result.state = this.status.STARTED
329
+ }
330
+ return result
331
+ },
332
+
333
+ /**
334
+ * @description Method to get the numbers of pages in a branch
335
+ * @param {String} idBranch the id of the branch ex: "A02_E01_P01"
336
+ * @return {Array} /Return the total of pages in a specific branch
337
+ */
338
+ getBranchPagesCount(idBranch) {
339
+ if (this.$router.currentRoute.value.meta.type !== 'branching') return
340
+
341
+ const allPages = this.getAllActivities()
342
+ .list.get(this.idActivity)
343
+ .get(this.$router.currentRoute.value.meta.id)
344
+ .get(idBranch)
345
+
346
+ return allPages.size ? allPages.size : 1
347
+ },
348
+
349
+ /**
350
+ * @description Method to validate data in the prop card|customButton.
351
+ * @param {String} propName The name of the property card|customButton
352
+ * @return {String} errMsg false | string message
353
+ */
354
+ validateProp(propName) {
355
+ let isValid = true
356
+ let errMsg = false
357
+
358
+ switch (propName) {
359
+ case 'cards': {
360
+ // verify that numbers of element is equal to number
361
+ //card must have at least 2 elements
362
+ if (
363
+ this.cards.constructor !== Array ||
364
+ this.cards.length !== this.branchs.length
365
+ ) {
366
+ isValid = false
367
+ errMsg = `La propriété cards doit être un tableau contenant ${this.branchs.length} elements`
368
+ } else {
369
+ //Start validation of content of each object in cards
370
+ for (let el of this.cards) {
371
+ //Each element in card must be of type Object and can not be empty
372
+ if (el.constructor !== Object) {
373
+ isValid = false
374
+ let index = this.cards.indexOf(el)
375
+ errMsg = `Le data pour la carte ${index + 1} n'est pas de type Object`
376
+ }
377
+ if (!isValid) break
378
+ }
379
+ //Start validation of required keys
380
+ if (isValid) {
381
+ for (let el of this.cards) {
382
+ let index = this.cards.indexOf(el)
383
+
384
+ // Start validation of existing branch for this element
385
+ let searchEl = this.branchs.find((b) =>
386
+ b._ref.includes(el.brchName)
387
+ )
388
+
389
+ if (!searchEl) {
390
+ errMsg = `Aucune branch correspondante avec le nom ${el.brchName} pour la carte ${
391
+ index + 1
392
+ }. Êtes vous sûre d'avoir indiqué la bonne branch?`
393
+ isValid = false
394
+ }
395
+
396
+ if (!isValid) break
397
+ }
398
+ }
399
+ }
400
+ break
401
+ }
402
+
403
+ case 'customButtons': {
404
+ if (
405
+ this.customButtons.constructor !== Array ||
406
+ this.customButtons.length !== this.branchs.length
407
+ ) {
408
+ isValid = false
409
+ errMsg = `La propriété customButtons doit être un tableau contenant ${this.branchs.length} elements`
410
+ } else {
411
+ //Start validation of content of each object in customButtons
412
+ for (let el of this.customButtons) {
413
+ //Each element in card must be of type Object and can not be empty
414
+ if (el.constructor !== Object) {
415
+ isValid = false
416
+ let index = this.customButtons.indexOf(el)
417
+ errMsg = `Le data pour le button ${index + 1} n'est pas de type {Object}`
418
+ }
419
+ if (!isValid) break
420
+ }
421
+
422
+ //Start validation of required keys
423
+ if (isValid) {
424
+ const requiredKeys = ['name', 'brchName']
425
+
426
+ for (let el of this.customButtons) {
427
+ //Validate that require key exist for each element for customButton
428
+ let index = this.customButtons.indexOf(el)
429
+ for (let k of requiredKeys) {
430
+ if (!el[k]) {
431
+ isValid = false
432
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${index + 1} n'as pas été défini`
433
+ break
434
+ } else {
435
+ // Expecting String for properties name and brchName
436
+ if (
437
+ ['name', 'brchName'].includes(k) &&
438
+ el[k].constructor !== String
439
+ ) {
440
+ errMsg = `l'Attribut 👉 ${k} 👈 pour la button ${
441
+ index + 1
442
+ } doit être de type {String}`
443
+ isValid = false
444
+ break
445
+ }
446
+ }
447
+ if (!isValid) break
448
+ }
449
+
450
+ // Start validation of existing branch for this element
451
+ let searchEl = this.branchs.find((b) =>
452
+ b._ref.includes(el.brchName)
453
+ )
454
+
455
+ if (!searchEl) {
456
+ errMsg = `Aucune branch correspondante avec le nom ${
457
+ el.brchName
458
+ } pour le bouton ${index + 1}. Êtes vous sûre d'avoir indiqué la bonne branch?`
459
+ isValid = false
460
+ }
461
+
462
+ if (!isValid) break
463
+ }
464
+ }
465
+ }
466
+ break
467
+ }
468
+ }
469
+
470
+ return errMsg
471
+ },
472
+
473
+ /**
474
+ * @description methode to retive matching data of branch in the prop card| customButton
475
+ * @param {String} id The id of the branch
476
+ * @return {Object} Matching element from
477
+ */
478
+ getMatchingElement(id) {
479
+ if (this.isCustomButton) {
480
+ return this.customButtons.find((c) => c.brchName === id)
481
+ }
482
+ if (this.isCard) {
483
+ return this.cards.find((c) => c.brchName === id)
484
+ }
485
+ }
486
+ }
487
+ }
488
+ </script>
489
+
490
+ <style lang="scss">
491
+ .branch-btn-wrapper {
492
+ position: relative;
493
+
494
+ .branch-btn-custom {
495
+ background-color: transparent;
496
+ border: none;
497
+ }
498
+ }
499
+
500
+ .v-card {
501
+ position: relative;
502
+ .button-progress-wrapper {
503
+ position: absolute;
504
+ right: 55px;
505
+ top: 5px;
506
+ }
507
+ }
508
+ </style>