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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.editorconfig +33 -33
  2. package/.eslintignore +29 -29
  3. package/.eslintrc.cjs +81 -81
  4. package/CHANGELOG +13 -0
  5. package/README.md +71 -71
  6. package/bk.scss +117 -117
  7. package/package.json +8 -8
  8. package/src/$locales/en.json +145 -143
  9. package/src/$locales/fr.json +107 -105
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +1150 -1054
  12. package/src/components/AppBaseButton.test.js +22 -0
  13. package/src/components/AppBaseButton.vue +93 -87
  14. package/src/components/AppBaseErrorDisplay.vue +438 -438
  15. package/src/components/AppBaseFlipCard.vue +84 -84
  16. package/src/components/AppBaseModule.vue +1657 -1673
  17. package/src/components/AppBasePage.vue +742 -779
  18. package/src/components/AppBasePopover.vue +41 -41
  19. package/src/components/AppCompAudio.vue +265 -234
  20. package/src/components/AppCompBranchButtons.vue +556 -552
  21. package/src/components/AppCompButtonProgress.vue +121 -126
  22. package/src/components/AppCompCarousel.vue +328 -298
  23. package/src/components/AppCompInputCheckBoxNext.vue +200 -195
  24. package/src/components/AppCompInputDropdownNext.vue +201 -159
  25. package/src/components/AppCompInputRadioNext.vue +152 -152
  26. package/src/components/AppCompInputTextNext.vue +125 -106
  27. package/src/components/AppCompInputTextTableNext.vue +142 -141
  28. package/src/components/AppCompInputTextToFillDropdownNext.vue +238 -230
  29. package/src/components/AppCompInputTextToFillNext.vue +171 -171
  30. package/src/components/AppCompJauge.vue +74 -74
  31. package/src/components/AppCompMenu.vue +25 -10
  32. package/src/components/AppCompMenuItem.vue +228 -228
  33. package/src/components/AppCompNavigation.vue +972 -960
  34. package/src/components/AppCompNoteCall.vue +159 -133
  35. package/src/components/AppCompNoteCredit.vue +490 -292
  36. package/src/components/AppCompPlayBar.vue +1217 -1218
  37. package/src/components/AppCompPlayBarNext.vue +2060 -2052
  38. package/src/components/AppCompPlayBarProgress.vue +82 -82
  39. package/src/components/AppCompPopUpNext.vue +500 -503
  40. package/src/components/AppCompQuizNext.vue +2908 -2904
  41. package/src/components/AppCompQuizRecall.vue +298 -276
  42. package/src/components/AppCompSVGNext.vue +347 -347
  43. package/src/components/AppCompSettingsMenu.vue +172 -172
  44. package/src/components/AppCompTableOfContent.vue +386 -387
  45. package/src/components/AppCompTranscript.vue +24 -24
  46. package/src/components/AppCompVideoPlayer.vue +368 -368
  47. package/src/components/BaseModule.vue +55 -72
  48. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  49. package/src/composables/useQuiz.js +206 -206
  50. package/src/externalComps/ModuleView.vue +22 -22
  51. package/src/externalComps/SummaryView.vue +91 -91
  52. package/src/main.js +272 -272
  53. package/src/mixins/$mediaMixins.js +819 -819
  54. package/src/mixins/timerMixin.js +155 -155
  55. package/src/module/stores/appStore.js +954 -893
  56. package/src/module/xapi/ADL.js +380 -376
  57. package/src/module/xapi/Crypto/Hasher.js +241 -241
  58. package/src/module/xapi/Crypto/WordArray.js +278 -278
  59. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  60. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  61. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  62. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  63. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  64. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  65. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  66. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  67. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  68. package/src/module/xapi/Statement/agent.js +55 -55
  69. package/src/module/xapi/Statement/index.js +259 -259
  70. package/src/module/xapi/Statement/statement.js +253 -253
  71. package/src/module/xapi/launch.js +157 -157
  72. package/src/module/xapi/utils.js +167 -167
  73. package/src/module/xapi/verbs.js +294 -294
  74. package/src/module/xapi/wrapper.js +1963 -1963
  75. package/src/module/xapi/xapiStatement.js +444 -444
  76. package/src/plugins/bus.js +8 -8
  77. package/src/plugins/gsap.js +14 -14
  78. package/src/plugins/helper.js +355 -308
  79. package/src/plugins/i18n.js +44 -44
  80. package/src/plugins/idb.js +227 -219
  81. package/src/plugins/save.js +37 -37
  82. package/src/plugins/scorm.js +287 -287
  83. package/src/plugins/xapi.js +11 -11
  84. package/src/public/index.html +33 -33
  85. package/src/router/index.js +48 -43
  86. package/src/router/routes.js +312 -312
  87. package/src/shared/generalfuncs.js +210 -210
  88. package/src/shared/validators.js +926 -1069
  89. package/vitest.config.js +19 -0
  90. package/vite.config.js +0 -27
@@ -1,503 +1,500 @@
1
- <!-- About this Component--
2
- *@ Description: Application component to show instructions in a modal.
3
- *@ What it does: This component uses the VDialog component to create a Popup. 2 types of popups can be display:
4
- - end of activities popup (popup-endActivity). Default used by the application at the end of activities (AppCompNavigation)
5
- - custom popup (popup-custom)- this allow user to defined custom popup using the power of VDialog. Must respect properties defined by VDialog. For more info, refer to https://vuetifyjs.com/en/components/dialogs/#usage.
6
- It validates the content of the popup before rendering. Will render error view if there are some errors in content definition
7
- * What it needs: data must contain:
8
- - type(string) : 'popup-endAcivity' || 'popup-custom || popup-info'
9
- - value (Object): {$bvArgs(Object), template (String)}
10
-
11
- *⚠️@Note :There can only be Popup instance of this component in the application. You can ask the application to open the Popup with you defined popup
12
- *@ Exemple: creating a custom popup using the VDialog props
13
-
14
- const myCustomProp = {
15
- type: 'popup-custom',
16
- value: {
17
- //⚠️IMPORTANT:Only props of VDialog must be listed in $bvArgs. Check VDialog documentation for full list of props
18
- $bvArgs: {
19
- centered: true,
20
- width: 'auto',
21
- scrollable: false,
22
- 'max-width': '600'
23
- }
24
- //⚠️IMPORTANT:Must have a template attribute
25
- tempate:`<div><h4>Exemple of custom popup</h4><p>Hello World</p></div>`
26
- }
27
- }
28
- // Now we can ask the application to open our custom popup
29
- this.$bus.$emit('open-popup', myCustomProp)
30
- -->
31
-
32
- <template>
33
- <div>
34
- <template v-if="!errors.length">
35
- <!------------ End Activity --------------------------->
36
-
37
- <v-dialog
38
- id="popUp"
39
- :model-value="popupType == 'popup-endActivity' && dialogue"
40
- :class="pType"
41
- width="auto"
42
- scrollable
43
- centered
44
- @after-leave="$close(pContent.cb_$close)"
45
- >
46
- <focus-trap v-model:active="dialogue">
47
- <div class="pop-outside">
48
- <div class="pop-header">
49
- <app-base-button
50
- id="close-pop"
51
- class="btn-ghost-ico"
52
- :title="$t('button.closePopUp')"
53
- @click="$close()"
54
- >
55
- <svg>
56
- <use href="#close-square-icon" />
57
- </svg>
58
- <span class="sr-only">{{ $t('button.closePopUp') }}</span>
59
- </app-base-button>
60
- </div>
61
- <div class="pop-containt">
62
- <div class="pop-box">
63
- <div id="popHeader">
64
- <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
65
- </div>
66
- <div class="box-content-popUp"></div>
67
- <div
68
- id="end-activity"
69
- :aria-hidden="pType !== 'popup-endActivity'"
70
- class="popup-bottom-buttons"
71
- ></div>
72
- </div>
73
- </div>
74
- </div>
75
- </focus-trap>
76
- </v-dialog>
77
- <!------------ POPUP INFO ----------------------------->
78
- <v-dialog
79
- id="popUp"
80
- :model-value="popupType == 'popup-info' && dialogue"
81
- :class="pType"
82
- width="auto"
83
- scrollable
84
- centered
85
- v-bind="pArgs"
86
- @after-leave="$close(pContent.cb_$close)"
87
- >
88
- <focus-trap v-model:active="dialogue">
89
- <div class="pop-outside">
90
- <div class="pop-header">
91
- <app-base-button
92
- id="close-pop"
93
- class="btn-ghost-ico"
94
- :title="$t('button.closePopUp')"
95
- @click="$close()"
96
- >
97
- <svg>
98
- <use href="#close-square-icon" />
99
- </svg>
100
- <span class="sr-only">{{ $t('button.closePopUp') }}</span>
101
- </app-base-button>
102
- </div>
103
- <div class="pop-containt">
104
- <div class="pop-box">
105
- <div id="popHeader">
106
- <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
107
- </div>
108
- <div class="box-content-popUp">
109
- <template v-if="contentLength > 0">
110
- <template v-for="(content, _key, index) of pContent">
111
- <!-------- Create TEXT element -------------->
112
- <p
113
- v-if="_key.includes('text') && !_key.includes('hyper')"
114
- :id="`${_key}_${index}`"
115
- :key="_key"
116
- class="p-txt"
117
- >
118
- {{ content }}
119
- </p>
120
-
121
- <!-------- Create HTMLelement -------------->
122
- <div
123
- v-else-if="_key.includes('hypertext')"
124
- :id="`${_key}_${index}`"
125
- :key="`hyp_${_key}`"
126
- v-html="content"
127
- />
128
-
129
- <!-------- Create IMAGE element ------------->
130
- <img
131
- v-else-if="_key.includes('image')"
132
- :id="`${_key}_${index}`"
133
- :key="`img_${_key}`"
134
- class="p-img"
135
- :src="content.path"
136
- :alt="content.label || 'image'"
137
- />
138
-
139
- <!-------- Create LINK element -------------->
140
- <a
141
- v-else-if="_key.includes('link')"
142
- :id="`${_key}_${index}`"
143
- :key="`lnk_${_key}`"
144
- :href="content.ref"
145
- class="p-a"
146
- target="_blank"
147
- :title="`${content.label}`"
148
- >
149
- {{ content.label }}
150
- </a>
151
-
152
- <!-------- Create VIDEO element ------------->
153
- <video
154
- v-else-if="_key.includes('video')"
155
- :id="`${_key}_${index}`"
156
- :key="`vid_${_key}`"
157
- :src="content"
158
- class="p-vid"
159
- controls
160
- disablepictureinpicture
161
- controlslist="nofullscreen nodownload"
162
- />
163
-
164
- <!--------Create AUDIO element--------------->
165
- <audio
166
- v-else-if="_key.includes('audio')"
167
- :id="`${_key}_${index}`"
168
- :key="`aud_${_key}`"
169
- :src="content"
170
- class="p-aud"
171
- controls
172
- controlslist="nodownload"
173
- />
174
- </template>
175
- </template>
176
- </div>
177
- <div
178
- id="end-activity"
179
- :aria-hidden="pType !== 'popup-endActivity'"
180
- class="popup-bottom-buttons"
181
- ></div>
182
- </div>
183
- </div>
184
- </div>
185
- </focus-trap>
186
- </v-dialog>
187
- <!------------ POPUP CUSTOM --------------------------->
188
- <v-dialog
189
- id="popUp"
190
- :model-value="popupType == 'popup-custom' && dialogue"
191
- :class="pType"
192
- v-bind="pArgs"
193
- @after-leave="$close(pContent.cb_$close)"
194
- >
195
- <focus-trap v-model:active="dialogue">
196
- <component :is="{ template: pContent.template }"></component>
197
- </focus-trap>
198
- </v-dialog>
199
- <!------------------------------- END POPUPS ------------------------------>
200
- </template>
201
- <app-base-error-display
202
- v-show="errors.length"
203
- :error-group="'component'"
204
- :error-title="'ERREUR: COMPOSANT DE POPUP'"
205
- :errors-list="errors"
206
- />
207
- </div>
208
- </template>
209
-
210
- <script>
211
- import AppBaseErrorDisplay from './AppBaseErrorDisplay.vue'
212
- import { mapActions } from 'pinia'
213
- import { useAppStore } from '../module/stores/appStore'
214
-
215
- export default {
216
- name: 'AppCompPopUp',
217
- components: { AppBaseErrorDisplay },
218
- data() {
219
- return {
220
- pType: null,
221
- pName: 'noName',
222
- pContent: {},
223
- pArgs: null,
224
- pTitle: '',
225
- errors: [],
226
- docLink:
227
- 'https://fcaddocumentation.netlify.app/guide/ressources.html#creer-un-popup-custom',
228
- animationOff: false,
229
- dialogue: false
230
- }
231
- },
232
- computed: {
233
- contentLength() {
234
- let size = null
235
- if (!this.pType || this.pType !== 'popup-info') return
236
-
237
- if (this.pContent && Object.keys(this.pContent).length > 0)
238
- size = Object.keys(this.pContent).length
239
- return size
240
- },
241
-
242
- popupType() {
243
- if (!this.pType) return
244
- return this.pType
245
- }
246
- },
247
-
248
- mounted() {
249
- this.$bus.$on('popup-open', this.validateContent)
250
- this.$bus.$on('popup-close', this.$close)
251
- },
252
- beforeUnmount() {
253
- this.$bus.$off('popup-open', this.validateContent)
254
- this.$bus.$off('popup-close', this.$close)
255
- },
256
- methods: {
257
- ...mapActions(useAppStore, ['updateEndPopUp']),
258
- /**
259
- * @description - Validate the data for the component
260
- * @param {Object} data
261
- */
262
- validateContent(data) {
263
- if (!data) return
264
- const typeList = ['popup-custom', 'popup-endActivity', 'popup-info']
265
-
266
- if (this.errors.length > 0) this.errors = []
267
-
268
- if (data.type) {
269
- // validate the type value of popup
270
- const validType = typeList.includes(data.type)
271
-
272
- if (!validType) this.errors.push('Invalid value for type')
273
- } else this.errors.push('Missing argument: type')
274
-
275
- // validate value of the popup according to popup type
276
- // Content must be String|Object
277
- if (data.value) {
278
- let validObject = false
279
- if (
280
- data.value.constructor === Object &&
281
- Object.keys(data.value).length > 0
282
- )
283
- validObject = true
284
-
285
- if (validObject) {
286
- let KeywordsList
287
- //Validate the content accordint to popup type
288
- switch (true) {
289
- case data.type === 'popup-endActivity':
290
- KeywordsList = ['template', 'title'] // accepted keywords in value declaration for pop-up end-Activity
291
- break
292
-
293
- case data.type === 'popup-custom':
294
- KeywordsList = ['$bvArgs', 'template'] // accepted keywords in value declaration for pop-up custom
295
- //Must have required keywords
296
- for (const key of KeywordsList) {
297
- if (data.value[key]) continue
298
- this.errors.push(
299
- `Missing required attribute 👉${key}👈. Required attributes are ${[...KeywordsList]}`
300
- )
301
- }
302
-
303
- break
304
- default:
305
- KeywordsList = [
306
- 'title',
307
- 'text_',
308
- 'image_',
309
- 'video_',
310
- 'audio_',
311
- 'link_',
312
- 'hypertext_',
313
- '$bvArgs'
314
- ] // accepted keyword in value declaration for pop-up custom
315
- }
316
- //validate each key in value content
317
-
318
- for (let key of Object.keys(data.value)) {
319
- let expectedKey = key
320
- // search if key match lists
321
- if (key.includes('_') && !key.includes('cb_$'))
322
- expectedKey = `${key.split('_')[0]}_`
323
-
324
- //===============================================
325
- const test = (regexp) => regexp.test(expectedKey) //defining the testing function
326
-
327
- switch (true) {
328
- case ![...KeywordsList].includes(expectedKey):
329
- this.errors.push(
330
- `Invalid attribute declaration 👉${key}👈 . Expected attributes are ${[...KeywordsList]}`
331
- )
332
- break
333
-
334
- case test(/link/):
335
- // Validating content of link element
336
- if (!data.value[key].ref) {
337
- this.errors.push('Missing ref for link')
338
- }
339
- break
340
-
341
- case test(/image/):
342
- // Validating content of link element
343
- if (!data.value[key].path) {
344
- this.errors.push('Missing path for image')
345
- }
346
- break
347
-
348
- case test(/cb_\$/):
349
- // validating content of cb_$ keys to be a function
350
- if (data.value[key].constructor != Function)
351
- this.errors.push(
352
- `Invalid assignment for attribute 👉${expectedKey} popup. Must be a Function`
353
- )
354
- break
355
-
356
- case test(/\$bvArgs/):
357
- // validating content of cb_$ keys to be a function
358
-
359
- if (data.value[key].constructor != Object)
360
- this.errors.push(
361
- `Invalid assignment for attribute 👉${expectedKey} popup. Must be an Object`
362
- )
363
-
364
- break
365
-
366
- default: {
367
- if (data.value[key].constructor != String) {
368
- this.errors.push(
369
- `Invalid assignment for attribute 👉${key} in popup. Must be a String`
370
- )
371
- }
372
- }
373
- }
374
- }
375
- } else this.errors.push('Invalid object declaration for value')
376
- } else this.errors.push('Missing argument - value')
377
-
378
- if (import.meta.env.DEV) {
379
- for (const err of this.errors)
380
- console.warn(
381
- `%c WARNING!>>> POP-UP: ${err} `,
382
- 'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
383
- )
384
- }
385
-
386
- if (!this.errors.length) {
387
- if (data.type === 'popup-endActivity') {
388
- const { title, ...filtered } = data.value
389
- this.pContent = filtered
390
- this.pTitle = title
391
- } else {
392
- const { title, $bvArgs, ...filtered } = data.value
393
- this.pContent = filtered
394
- this.pTitle = title
395
- this.pArgs = $bvArgs // native VDialog props
396
- }
397
- this.pType = data.type
398
- this.pName = data.name
399
-
400
- console.log(this.pType)
401
-
402
- if (this.pType == 'popup-endActivity')
403
- this.updateEndPopUp(true) //This will handle the activation of the element in the portal
404
- else this.updateEndPopUp(false) //This will handle the activation of the element in the portal
405
- this.$open()
406
- }
407
- },
408
-
409
- /**
410
- * @description - method to open the popup
411
- * @param {Function} cb
412
- */
413
- $open(cb) {
414
- this.dialogue = true
415
- },
416
- /**
417
- * @description - method to close the popup
418
- * @param {Function} cb
419
- */
420
- $close(options, cb) {
421
- if (this.pType === 'popup-endActivity') this.updateEndPopUp(false)
422
- this.dialogue = false
423
- }
424
- }
425
- }
426
- </script>
427
- <style lang="scss">
428
- .v-overlay__scrim {
429
- background: none !important;
430
- }
431
-
432
- .popup-info {
433
- .v-overlay__content {
434
- width: 565px !important;
435
- padding: 16px 24px 24px 24px;
436
- overflow: hidden;
437
-
438
- .pop-outside {
439
- .pop-header {
440
- display: flex;
441
- flex-direction: row;
442
- justify-content: flex-end;
443
- width: 100%;
444
-
445
- #close-pop {
446
- margin-right: 2px;
447
- margin-top: 2px;
448
- }
449
- }
450
- }
451
- }
452
- }
453
-
454
- .popup-endActivity {
455
- .v-overlay__content {
456
- max-height: 277px !important;
457
- margin: 0 !important;
458
- width: 100% !important;
459
- max-width: 100% !important;
460
-
461
- .pop-outside {
462
- display: flex;
463
- flex-direction: column;
464
- padding: 24px;
465
-
466
- .pop-header {
467
- display: flex;
468
- justify-content: flex-end;
469
- }
470
- .pop-containt {
471
- display: flex;
472
- justify-content: center;
473
-
474
- #popHeader {
475
- //#hypertext_1_0 {
476
- display: flex;
477
- justify-content: center;
478
- margin-bottom: 24px;
479
- // }
480
- }
481
-
482
- .popup-bottom-buttons {
483
- display: flex;
484
- justify-content: center;
485
-
486
- .btn {
487
- &:first-child {
488
- margin-right: 24px;
489
- }
490
- }
491
- }
492
- }
493
- }
494
- }
495
- }
496
-
497
- .popup-custom {
498
- .v-overlay__content {
499
- margin: 0 !important;
500
- width: 100% !important;
501
- }
502
- }
503
- </style>
1
+ <!-- About this Component--
2
+ *@ Description: Application component to show instructions in a modal.
3
+ *@ What it does: This component uses the VDialog component to create a Popup. 2 types of popups can be display:
4
+ - end of activities popup (popup-endActivity). Default used by the application at the end of activities (AppCompNavigation)
5
+ - custom popup (popup-custom)- this allow user to defined custom popup using the power of VDialog. Must respect properties defined by VDialog. For more info, refer to https://vuetifyjs.com/en/components/dialogs/#usage.
6
+ It validates the content of the popup before rendering. Will render error view if there are some errors in content definition
7
+ * What it needs: data must contain:
8
+ - type(string) : 'popup-endAcivity' || 'popup-custom || popup-info'
9
+ - value (Object): {$bvArgs(Object), template (String)}
10
+
11
+ *⚠️@Note :There can only be Popup instance of this component in the application. You can ask the application to open the Popup with you defined popup
12
+ *@ Exemple: creating a custom popup using the VDialog props
13
+
14
+ const myCustomProp = {
15
+ type: 'popup-custom',
16
+ value: {
17
+ //⚠️IMPORTANT:Only props of VDialog must be listed in $bvArgs. Check VDialog documentation for full list of props
18
+ $bvArgs: {
19
+ centered: true,
20
+ width: 'auto',
21
+ scrollable: false,
22
+ 'max-width': '600'
23
+ }
24
+ //⚠️IMPORTANT:Must have a template attribute
25
+ tempate:`<div><h4>Exemple of custom popup</h4><p>Hello World</p></div>`
26
+ }
27
+ }
28
+ // Now we can ask the application to open our custom popup
29
+ this.$bus.$emit('open-popup', myCustomProp)
30
+ -->
31
+
32
+ <template>
33
+ <div>
34
+ <template v-if="!errors.length">
35
+ <!------------ End Activity --------------------------->
36
+
37
+ <v-dialog
38
+ id="popUp"
39
+ :model-value="popupType == 'popup-endActivity' && dialogue"
40
+ :class="pType"
41
+ width="auto"
42
+ scrollable
43
+ centered
44
+ @after-leave="$close(pContent.cb_$close)"
45
+ >
46
+ <focus-trap v-model:active="dialogue">
47
+ <div class="pop-outside">
48
+ <div class="pop-header">
49
+ <app-base-button
50
+ id="close-pop"
51
+ class="btn-ghost-ico"
52
+ :title="$t('button.closePopUp')"
53
+ @click="$close()"
54
+ >
55
+ <svg>
56
+ <use href="#close-square-icon" />
57
+ </svg>
58
+ <span class="sr-only">{{ $t('button.closePopUp') }}</span>
59
+ </app-base-button>
60
+ </div>
61
+ <div class="pop-containt">
62
+ <div class="pop-box">
63
+ <div id="popHeader">
64
+ <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
65
+ </div>
66
+ <div class="box-content-popUp"></div>
67
+ <div
68
+ id="end-activity"
69
+ :aria-hidden="pType !== 'popup-endActivity'"
70
+ class="popup-bottom-buttons"
71
+ ></div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </focus-trap>
76
+ </v-dialog>
77
+ <!------------ POPUP INFO ----------------------------->
78
+ <v-dialog
79
+ id="popUp"
80
+ :model-value="popupType == 'popup-info' && dialogue"
81
+ :class="pType"
82
+ width="auto"
83
+ scrollable
84
+ centered
85
+ v-bind="pArgs"
86
+ @after-leave="$close(pContent.cb_$close)"
87
+ >
88
+ <focus-trap v-model:active="dialogue">
89
+ <div class="pop-outside">
90
+ <div class="pop-header">
91
+ <app-base-button
92
+ id="close-pop"
93
+ class="btn-ghost-ico"
94
+ :title="$t('button.closePopUp')"
95
+ @click="$close()"
96
+ >
97
+ <svg>
98
+ <use href="#close-square-icon" />
99
+ </svg>
100
+ <span class="sr-only">{{ $t('button.closePopUp') }}</span>
101
+ </app-base-button>
102
+ </div>
103
+ <div class="pop-containt">
104
+ <div class="pop-box">
105
+ <div id="popHeader">
106
+ <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
107
+ </div>
108
+ <div class="box-content-popUp">
109
+ <template v-if="contentLength > 0">
110
+ <template v-for="(content, _key, index) of pContent">
111
+ <!-------- Create TEXT element -------------->
112
+ <p
113
+ v-if="_key.includes('text') && !_key.includes('hyper')"
114
+ :id="`${_key}_${index}`"
115
+ :key="_key"
116
+ class="p-txt"
117
+ >
118
+ {{ content }}
119
+ </p>
120
+
121
+ <!-------- Create HTMLelement -------------->
122
+ <div
123
+ v-else-if="_key.includes('hypertext')"
124
+ :id="`${_key}_${index}`"
125
+ :key="`hyp_${_key}`"
126
+ v-html="content"
127
+ />
128
+
129
+ <!-------- Create IMAGE element ------------->
130
+ <img
131
+ v-else-if="_key.includes('image')"
132
+ :id="`${_key}_${index}`"
133
+ :key="`img_${_key}`"
134
+ class="p-img"
135
+ :src="content.path"
136
+ :alt="content.label || 'image'"
137
+ />
138
+
139
+ <!-------- Create LINK element -------------->
140
+ <a
141
+ v-else-if="_key.includes('link')"
142
+ :id="`${_key}_${index}`"
143
+ :key="`lnk_${_key}`"
144
+ :href="content.ref"
145
+ class="p-a"
146
+ target="_blank"
147
+ :title="`${content.label}`"
148
+ >
149
+ {{ content.label }}
150
+ </a>
151
+
152
+ <!-------- Create VIDEO element ------------->
153
+ <video
154
+ v-else-if="_key.includes('video')"
155
+ :id="`${_key}_${index}`"
156
+ :key="`vid_${_key}`"
157
+ :src="content"
158
+ class="p-vid"
159
+ controls
160
+ disablepictureinpicture
161
+ controlslist="nofullscreen nodownload"
162
+ />
163
+
164
+ <!--------Create AUDIO element--------------->
165
+ <audio
166
+ v-else-if="_key.includes('audio')"
167
+ :id="`${_key}_${index}`"
168
+ :key="`aud_${_key}`"
169
+ :src="content"
170
+ class="p-aud"
171
+ controls
172
+ controlslist="nodownload"
173
+ />
174
+ </template>
175
+ </template>
176
+ </div>
177
+ <div
178
+ id="end-activity"
179
+ :aria-hidden="pType !== 'popup-endActivity'"
180
+ class="popup-bottom-buttons"
181
+ ></div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </focus-trap>
186
+ </v-dialog>
187
+ <!------------ POPUP CUSTOM --------------------------->
188
+ <v-dialog
189
+ id="popUp"
190
+ :model-value="popupType == 'popup-custom' && dialogue"
191
+ :class="pType"
192
+ v-bind="pArgs"
193
+ @after-leave="$close(pContent.cb_$close)"
194
+ >
195
+ <focus-trap v-model:active="dialogue">
196
+ <component :is="{ template: pContent.template }"></component>
197
+ </focus-trap>
198
+ </v-dialog>
199
+ <!------------------------------- END POPUPS ------------------------------>
200
+ </template>
201
+ <app-base-error-display
202
+ v-show="errors.length"
203
+ :error-group="'component'"
204
+ :error-title="'ERREUR: COMPOSANT DE POPUP'"
205
+ :errors-list="errors"
206
+ />
207
+ </div>
208
+ </template>
209
+
210
+ <script>
211
+ import { mapActions } from 'pinia'
212
+ import { useAppStore } from '../module/stores/appStore'
213
+
214
+ export default {
215
+ name: 'AppCompPopUp',
216
+
217
+ data() {
218
+ return {
219
+ pType: null,
220
+ pName: 'noName',
221
+ pContent: {},
222
+ pArgs: null,
223
+ pTitle: '',
224
+ errors: [],
225
+ docLink:
226
+ 'https://fcaddocumentation.netlify.app/guide/ressources.html#creer-un-popup-custom',
227
+ animationOff: false,
228
+ dialogue: false
229
+ }
230
+ },
231
+ computed: {
232
+ contentLength() {
233
+ let size = null
234
+ if (!this.pType || this.pType !== 'popup-info') return
235
+
236
+ if (this.pContent && Object.keys(this.pContent).length > 0)
237
+ size = Object.keys(this.pContent).length
238
+ return size
239
+ },
240
+
241
+ popupType() {
242
+ if (!this.pType) return
243
+ return this.pType
244
+ }
245
+ },
246
+
247
+ mounted() {
248
+ this.$bus.$on('popup-open', this.validateContent)
249
+ this.$bus.$on('popup-close', this.$close)
250
+ },
251
+ beforeUnmount() {
252
+ this.$bus.$off('popup-open', this.validateContent)
253
+ this.$bus.$off('popup-close', this.$close)
254
+ },
255
+ methods: {
256
+ ...mapActions(useAppStore, ['updateEndPopUp']),
257
+ /**
258
+ * @description - Validate the data for the component
259
+ * @param {Object} data
260
+ */
261
+ validateContent(data) {
262
+ if (!data) return
263
+ const typeList = ['popup-custom', 'popup-endActivity', 'popup-info']
264
+
265
+ if (this.errors.length > 0) this.errors = []
266
+
267
+ if (data.type) {
268
+ // validate the type value of popup
269
+ const validType = typeList.includes(data.type)
270
+
271
+ if (!validType) this.errors.push('Invalid value for type')
272
+ } else this.errors.push('Missing argument: type')
273
+
274
+ // validate value of the popup according to popup type
275
+ // Content must be String|Object
276
+ if (data.value) {
277
+ let validObject = false
278
+ if (
279
+ data.value.constructor === Object &&
280
+ Object.keys(data.value).length > 0
281
+ )
282
+ validObject = true
283
+
284
+ if (validObject) {
285
+ let KeywordsList
286
+ //Validate the content accordint to popup type
287
+ switch (true) {
288
+ case data.type === 'popup-endActivity':
289
+ KeywordsList = ['template', 'title'] // accepted keywords in value declaration for pop-up end-Activity
290
+ break
291
+
292
+ case data.type === 'popup-custom':
293
+ KeywordsList = ['$bvArgs', 'template'] // accepted keywords in value declaration for pop-up custom
294
+ //Must have required keywords
295
+ for (const key of KeywordsList) {
296
+ if (data.value[key]) continue
297
+ this.errors.push(
298
+ `Missing required attribute 👉${key}👈. Required attributes are ${[...KeywordsList]}`
299
+ )
300
+ }
301
+
302
+ break
303
+ default:
304
+ KeywordsList = [
305
+ 'title',
306
+ 'text_',
307
+ 'image_',
308
+ 'video_',
309
+ 'audio_',
310
+ 'link_',
311
+ 'hypertext_',
312
+ '$bvArgs'
313
+ ] // accepted keyword in value declaration for pop-up custom
314
+ }
315
+ //validate each key in value content
316
+
317
+ for (let key of Object.keys(data.value)) {
318
+ let expectedKey = key
319
+ // search if key match lists
320
+ if (key.includes('_') && !key.includes('cb_$'))
321
+ expectedKey = `${key.split('_')[0]}_`
322
+
323
+ //===============================================
324
+ const test = (regexp) => regexp.test(expectedKey) //defining the testing function
325
+
326
+ switch (true) {
327
+ case ![...KeywordsList].includes(expectedKey):
328
+ this.errors.push(
329
+ `Invalid attribute declaration 👉${key}👈 . Expected attributes are ${[...KeywordsList]}`
330
+ )
331
+ break
332
+
333
+ case test(/link/):
334
+ // Validating content of link element
335
+ if (!data.value[key].ref) {
336
+ this.errors.push('Missing ref for link')
337
+ }
338
+ break
339
+
340
+ case test(/image/):
341
+ // Validating content of link element
342
+ if (!data.value[key].path) {
343
+ this.errors.push('Missing path for image')
344
+ }
345
+ break
346
+
347
+ case test(/cb_\$/):
348
+ // validating content of cb_$ keys to be a function
349
+ if (data.value[key].constructor != Function)
350
+ this.errors.push(
351
+ `Invalid assignment for attribute 👉${expectedKey} popup. Must be a Function`
352
+ )
353
+ break
354
+
355
+ case test(/\$bvArgs/):
356
+ // validating content of cb_$ keys to be a function
357
+
358
+ if (data.value[key].constructor != Object)
359
+ this.errors.push(
360
+ `Invalid assignment for attribute 👉${expectedKey} popup. Must be an Object`
361
+ )
362
+
363
+ break
364
+
365
+ default: {
366
+ if (data.value[key].constructor != String) {
367
+ this.errors.push(
368
+ `Invalid assignment for attribute 👉${key} in popup. Must be a String`
369
+ )
370
+ }
371
+ }
372
+ }
373
+ }
374
+ } else this.errors.push('Invalid object declaration for value')
375
+ } else this.errors.push('Missing argument - value')
376
+
377
+ if (import.meta.env.DEV) {
378
+ for (const err of this.errors)
379
+ console.warn(
380
+ `%c WARNING!>>> POP-UP: ${err} `,
381
+ 'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
382
+ )
383
+ }
384
+
385
+ if (!this.errors.length) {
386
+ if (data.type === 'popup-endActivity') {
387
+ const { title, ...filtered } = data.value
388
+ this.pContent = filtered
389
+ this.pTitle = title
390
+ } else {
391
+ const { title, $bvArgs, ...filtered } = data.value
392
+ this.pContent = filtered
393
+ this.pTitle = title
394
+ this.pArgs = $bvArgs // native VDialog props
395
+ }
396
+ this.pType = data.type
397
+ this.pName = data.name
398
+
399
+ if (this.pType == 'popup-endActivity')
400
+ this.updateEndPopUp(true) //This will handle the activation of the element in the portal
401
+ else this.updateEndPopUp(false) //This will handle the activation of the element in the portal
402
+ this.$open()
403
+ }
404
+ },
405
+
406
+ /**
407
+ * @description - method to open the popup
408
+ * @param {Function} cb
409
+ */
410
+ $open(cb) {
411
+ this.dialogue = true
412
+ },
413
+ /**
414
+ * @description - method to close the popup
415
+ * @param {Function} cb
416
+ */
417
+ $close(options, cb) {
418
+ if (this.pType === 'popup-endActivity') this.updateEndPopUp(false)
419
+ this.dialogue = false
420
+ }
421
+ }
422
+ }
423
+ </script>
424
+ <style lang="scss">
425
+ .v-overlay__scrim {
426
+ background: none !important;
427
+ }
428
+
429
+ .popup-info {
430
+ .v-overlay__content {
431
+ width: 565px !important;
432
+ padding: 16px 24px 24px 24px;
433
+ overflow: hidden;
434
+
435
+ .pop-outside {
436
+ .pop-header {
437
+ display: flex;
438
+ flex-direction: row;
439
+ justify-content: flex-end;
440
+ width: 100%;
441
+
442
+ #close-pop {
443
+ margin-right: 2px;
444
+ margin-top: 2px;
445
+ }
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ .popup-endActivity {
452
+ .v-overlay__content {
453
+ max-height: 277px !important;
454
+ margin: 0 !important;
455
+ width: 100% !important;
456
+ max-width: 100% !important;
457
+
458
+ .pop-outside {
459
+ display: flex;
460
+ flex-direction: column;
461
+ padding: 24px;
462
+
463
+ .pop-header {
464
+ display: flex;
465
+ justify-content: flex-end;
466
+ }
467
+ .pop-containt {
468
+ display: flex;
469
+ justify-content: center;
470
+
471
+ #popHeader {
472
+ //#hypertext_1_0 {
473
+ display: flex;
474
+ justify-content: center;
475
+ margin-bottom: 24px;
476
+ // }
477
+ }
478
+
479
+ .popup-bottom-buttons {
480
+ display: flex;
481
+ justify-content: center;
482
+
483
+ .btn {
484
+ &:first-child {
485
+ margin-right: 24px;
486
+ }
487
+ }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ .popup-custom {
495
+ .v-overlay__content {
496
+ margin: 0 !important;
497
+ width: 100% !important;
498
+ }
499
+ }
500
+ </style>