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,504 +1,508 @@
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" :prevent-scroll="true">
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
- :aria-label="$t('button.closePopUp')"
54
- @click="$close()"
55
- >
56
- <svg aria-hidden="true" focusable="false">
57
- <use href="#close-square-icon" />
58
- </svg>
59
- </app-base-button>
60
- </div>
61
- <div class="pop-containt">
62
- <div class="pop-box">
63
- <div id="popHeader">
64
- <div id="dialogTitle" class="p-title" v-html="pTitle"></div>
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" :prevent-scroll="true">
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
- :aria-label="$t('button.closePopUp')"
96
- @click="$close()"
97
- >
98
- <svg aria-hidden="true" focusable="false">
99
- <use href="#close-square-icon" />
100
- </svg>
101
- </app-base-button>
102
- </div>
103
- <div class="pop-containt">
104
- <div class="pop-box">
105
- <div id="popHeader">
106
- <p id="dialogTitle" class="p-title" v-html="pTitle"></p>
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" :prevent-scroll="true">
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 { mapState, 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
- ...mapState(useAppStore, ['getNotes', 'getWidgetOpen']),
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
- if (this.pType == 'popup-endActivity')
401
- this.updateEndPopUp(true) //This will handle the activation of the element in the portal
402
- else this.updateEndPopUp(false) //This will handle the activation of the element in the portal
403
- if (this.getWidgetOpen) {
404
- this.$bus.$emit('close-widget')
405
- }
406
- this.$open()
407
- }
408
- },
409
-
410
- /**
411
- * @description - method to open the popup
412
- * @param {Function} cb
413
- */
414
- $open(cb) {
415
- this.dialogue = true
416
- },
417
- /**
418
- * @description - method to close the popup
419
- * @param {Function} cb
420
- */
421
- $close(options, cb) {
422
- if (this.pType === 'popup-endActivity') this.updateEndPopUp(false)
423
- this.dialogue = false
424
- }
425
- }
426
- }
427
- </script>
428
- <style lang="scss">
429
- .v-overlay__scrim {
430
- background: none !important;
431
- }
432
-
433
- .popup-info {
434
- .v-overlay__content {
435
- width: 565px !important;
436
- padding: 16px 24px 24px 24px;
437
- overflow: hidden;
438
-
439
- .pop-outside {
440
- .pop-header {
441
- display: flex;
442
- flex-direction: row;
443
- justify-content: flex-end;
444
- width: 100%;
445
-
446
- #close-pop {
447
- margin-right: 2px;
448
- margin-top: 2px;
449
- }
450
- }
451
- }
452
- }
453
- }
454
-
455
- .popup-endActivity {
456
- .v-overlay__content {
457
- max-height: 277px !important;
458
- margin: 0 !important;
459
- width: 100% !important;
460
- max-width: 100% !important;
461
-
462
- .pop-outside {
463
- display: flex;
464
- flex-direction: column;
465
- padding: 24px;
466
-
467
- .pop-header {
468
- display: flex;
469
- justify-content: flex-end;
470
- }
471
- .pop-containt {
472
- display: flex;
473
- justify-content: center;
474
- position: relative;
475
- bottom: 8px;
476
-
477
- #popHeader {
478
- display: flex;
479
- justify-content: center;
480
- margin-bottom: 24px;
481
- }
482
-
483
- .popup-bottom-buttons {
484
- display: flex;
485
- justify-content: center;
486
-
487
- .btn {
488
- &:first-child {
489
- margin-right: 24px;
490
- }
491
- }
492
- }
493
- }
494
- }
495
- }
496
- }
497
-
498
- .popup-custom {
499
- .v-overlay__content {
500
- margin: 0 !important;
501
- width: 100% !important;
502
- }
503
- }
504
- </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" :prevent-scroll="true">
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
+ :aria-label="$t('button.closePopUp')"
54
+ @click="$close()"
55
+ >
56
+ <svg aria-hidden="true" focusable="false">
57
+ <use href="#close-square-icon" />
58
+ </svg>
59
+ </app-base-button>
60
+ </div>
61
+ <div class="pop-containt">
62
+ <div class="pop-box">
63
+ <div id="popHeader">
64
+ <div id="dialogTitle" class="p-title" v-html="pTitle"></div>
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" :prevent-scroll="true">
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
+ :aria-label="$t('button.closePopUp')"
96
+ @click="$close()"
97
+ >
98
+ <svg aria-hidden="true" focusable="false">
99
+ <use href="#close-square-icon" />
100
+ </svg>
101
+ </app-base-button>
102
+ </div>
103
+ <div class="pop-containt">
104
+ <div class="pop-box">
105
+ <div id="popHeader">
106
+ <p id="dialogTitle" class="p-title" v-html="pTitle"></p>
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" :prevent-scroll="true">
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 { mapState, mapActions } from 'pinia'
212
+ import { useAppStore } from '../module/stores/appStore'
213
+ import { useI18n } from 'vue-i18n'
214
+
215
+ export default {
216
+ name: 'AppCompPopUp',
217
+ setup() {
218
+ const { t } = useI18n()
219
+ return { t }
220
+ },
221
+ data() {
222
+ return {
223
+ pType: null,
224
+ pName: 'noName',
225
+ pContent: {},
226
+ pArgs: null,
227
+ pTitle: '',
228
+ errors: [],
229
+ docLink:
230
+ 'https://fcaddocumentation.netlify.app/guide/ressources.html#creer-un-popup-custom',
231
+ animationOff: false,
232
+ dialogue: false
233
+ }
234
+ },
235
+ computed: {
236
+ ...mapState(useAppStore, ['getNotes', 'getWidgetOpen']),
237
+ contentLength() {
238
+ let size = null
239
+ if (!this.pType || this.pType !== 'popup-info') return
240
+
241
+ if (this.pContent && Object.keys(this.pContent).length > 0)
242
+ size = Object.keys(this.pContent).length
243
+ return size
244
+ },
245
+
246
+ popupType() {
247
+ if (!this.pType) return
248
+ return this.pType
249
+ }
250
+ },
251
+
252
+ mounted() {
253
+ this.$bus.$on('popup-open', this.validateContent)
254
+ this.$bus.$on('popup-close', this.$close)
255
+ },
256
+ beforeUnmount() {
257
+ this.$bus.$off('popup-open', this.validateContent)
258
+ this.$bus.$off('popup-close', this.$close)
259
+ },
260
+ methods: {
261
+ ...mapActions(useAppStore, ['updateEndPopUp']),
262
+ /**
263
+ * @description - Validate the data for the component
264
+ * @param {Object} data
265
+ */
266
+ validateContent(data) {
267
+ if (!data) return
268
+ const typeList = ['popup-custom', 'popup-endActivity', 'popup-info']
269
+
270
+ if (this.errors.length > 0) this.errors = []
271
+
272
+ if (data.type) {
273
+ // validate the type value of popup
274
+ const validType = typeList.includes(data.type)
275
+
276
+ if (!validType) this.errors.push('Invalid value for type')
277
+ } else this.errors.push('Missing argument: type')
278
+
279
+ // validate value of the popup according to popup type
280
+ // Content must be String|Object
281
+ if (data.value) {
282
+ let validObject = false
283
+ if (
284
+ data.value.constructor === Object &&
285
+ Object.keys(data.value).length > 0
286
+ )
287
+ validObject = true
288
+
289
+ if (validObject) {
290
+ let KeywordsList
291
+ //Validate the content accordint to popup type
292
+ switch (true) {
293
+ case data.type === 'popup-endActivity':
294
+ KeywordsList = ['template', 'title'] // accepted keywords in value declaration for pop-up end-Activity
295
+ break
296
+
297
+ case data.type === 'popup-custom':
298
+ KeywordsList = ['$bvArgs', 'template'] // accepted keywords in value declaration for pop-up custom
299
+ //Must have required keywords
300
+ for (const key of KeywordsList) {
301
+ if (data.value[key]) continue
302
+ this.errors.push(
303
+ `Missing required attribute 👉${key}👈. Required attributes are ${[...KeywordsList]}`
304
+ )
305
+ }
306
+
307
+ break
308
+ default:
309
+ KeywordsList = [
310
+ 'title',
311
+ 'text_',
312
+ 'image_',
313
+ 'video_',
314
+ 'audio_',
315
+ 'link_',
316
+ 'hypertext_',
317
+ '$bvArgs'
318
+ ] // accepted keyword in value declaration for pop-up custom
319
+ }
320
+ //validate each key in value content
321
+
322
+ for (let key of Object.keys(data.value)) {
323
+ let expectedKey = key
324
+ // search if key match lists
325
+ if (key.includes('_') && !key.includes('cb_$'))
326
+ expectedKey = `${key.split('_')[0]}_`
327
+
328
+ //===============================================
329
+ const test = (regexp) => regexp.test(expectedKey) //defining the testing function
330
+
331
+ switch (true) {
332
+ case ![...KeywordsList].includes(expectedKey):
333
+ this.errors.push(
334
+ `Invalid attribute declaration 👉${key}👈 . Expected attributes are ${[...KeywordsList]}`
335
+ )
336
+ break
337
+
338
+ case test(/link/):
339
+ // Validating content of link element
340
+ if (!data.value[key].ref) {
341
+ this.errors.push('Missing ref for link')
342
+ }
343
+ break
344
+
345
+ case test(/image/):
346
+ // Validating content of link element
347
+ if (!data.value[key].path) {
348
+ this.errors.push('Missing path for image')
349
+ }
350
+ break
351
+
352
+ case test(/cb_\$/):
353
+ // validating content of cb_$ keys to be a function
354
+ if (data.value[key].constructor != Function)
355
+ this.errors.push(
356
+ `Invalid assignment for attribute 👉${expectedKey} popup. Must be a Function`
357
+ )
358
+ break
359
+
360
+ case test(/\$bvArgs/):
361
+ // validating content of cb_$ keys to be a function
362
+
363
+ if (data.value[key].constructor != Object)
364
+ this.errors.push(
365
+ `Invalid assignment for attribute 👉${expectedKey} popup. Must be an Object`
366
+ )
367
+
368
+ break
369
+
370
+ default: {
371
+ if (data.value[key].constructor != String) {
372
+ this.errors.push(
373
+ `Invalid assignment for attribute 👉${key} in popup. Must be a String`
374
+ )
375
+ }
376
+ }
377
+ }
378
+ }
379
+ } else this.errors.push('Invalid object declaration for value')
380
+ } else this.errors.push('Missing argument - value')
381
+
382
+ if (import.meta.env.DEV) {
383
+ for (const err of this.errors)
384
+ console.warn(
385
+ `%c WARNING!>>> POP-UP: ${err} `,
386
+ 'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
387
+ )
388
+ }
389
+
390
+ if (!this.errors.length) {
391
+ if (data.type === 'popup-endActivity') {
392
+ const { title, ...filtered } = data.value
393
+ this.pContent = filtered
394
+ this.pTitle = title
395
+ } else {
396
+ const { title, $bvArgs, ...filtered } = data.value
397
+ this.pContent = filtered
398
+ this.pTitle = title
399
+ this.pArgs = $bvArgs // native VDialog props
400
+ }
401
+ this.pType = data.type
402
+ this.pName = data.name
403
+
404
+ if (this.pType == 'popup-endActivity')
405
+ this.updateEndPopUp(true) //This will handle the activation of the element in the portal
406
+ else this.updateEndPopUp(false) //This will handle the activation of the element in the portal
407
+ if (this.getWidgetOpen) {
408
+ this.$bus.$emit('close-widget')
409
+ }
410
+ this.$open()
411
+ }
412
+ },
413
+
414
+ /**
415
+ * @description - method to open the popup
416
+ * @param {Function} cb
417
+ */
418
+ $open(cb) {
419
+ this.dialogue = true
420
+ },
421
+ /**
422
+ * @description - method to close the popup
423
+ * @param {Function} cb
424
+ */
425
+ $close(options, cb) {
426
+ if (this.pType === 'popup-endActivity') this.updateEndPopUp(false)
427
+ this.dialogue = false
428
+ }
429
+ }
430
+ }
431
+ </script>
432
+ <style lang="scss">
433
+ .v-overlay__scrim {
434
+ background: none !important;
435
+ }
436
+
437
+ .popup-info {
438
+ .v-overlay__content {
439
+ width: 565px !important;
440
+ padding: 16px 24px 24px 24px;
441
+ overflow: hidden;
442
+
443
+ .pop-outside {
444
+ .pop-header {
445
+ display: flex;
446
+ flex-direction: row;
447
+ justify-content: flex-end;
448
+ width: 100%;
449
+
450
+ #close-pop {
451
+ margin-right: 2px;
452
+ margin-top: 2px;
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ .popup-endActivity {
460
+ .v-overlay__content {
461
+ max-height: 277px !important;
462
+ margin: 0 !important;
463
+ width: 100% !important;
464
+ max-width: 100% !important;
465
+
466
+ .pop-outside {
467
+ display: flex;
468
+ flex-direction: column;
469
+ padding: 24px;
470
+
471
+ .pop-header {
472
+ display: flex;
473
+ justify-content: flex-end;
474
+ }
475
+ .pop-containt {
476
+ display: flex;
477
+ justify-content: center;
478
+ position: relative;
479
+ bottom: 8px;
480
+
481
+ #popHeader {
482
+ display: flex;
483
+ justify-content: center;
484
+ margin-bottom: 24px;
485
+ }
486
+
487
+ .popup-bottom-buttons {
488
+ display: flex;
489
+ justify-content: center;
490
+
491
+ .btn {
492
+ &:first-child {
493
+ margin-right: 24px;
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ .popup-custom {
503
+ .v-overlay__content {
504
+ margin: 0 !important;
505
+ width: 100% !important;
506
+ }
507
+ }
508
+ </style>