fcad-core-dragon 2.1.0 → 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 +3 -2
  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 -2
  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 -15
  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,350 +1,355 @@
1
- <!--
2
- @ Description: This component is used to display a quiz and its answer from a previous page if it has been completed by the user. The only quiz type supported is open answer (textarea) and the quiz must be in the same lesson.
3
- @ What it does: Retrieve the quizData specific to an activity ID and pageID. Then, retrieve the answers previously saved by the user to this quiz. After, create an html element including title and conditional content. If the answer have been previously saved, a specific hypertext, the question and the answer (disabled textarea) are displayed. If the answer have not been saved, another hypertext is displayed.
4
- @How to use: in a page call <app-comp-quiz-recall :quiz-recall-data="defineDQuizRecallData">
5
- ** where "defineDQuizRecallData" refers to data object of quiz to pass to component
6
- ** data object should containt following attributes: quizId,activityId,pageId,hypertext_done',hypertext_undone,title,titletag
7
- -->
8
- <template>
9
- <section v-if="quizRecallData" class="quizRecall">
10
- <!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
11
- <!--Show skeleton while app data is not ready-->
12
- <template v-if="!isReady">
13
- <app-base-skeleton :skeleton-text="''" :skeleton-type="`quiz-recall`" />
14
- </template>
15
- <!--Show app data when ready-->
16
- <template v-else>
17
- <component
18
- :is="quizRecall.titletag"
19
- v-if="quizRecall.title"
20
- class="quizRecall-title"
21
- >
22
- {{ quizRecall.title }}
23
- </component>
24
- <!--Quiz answer conditionning-->
25
- <app-base-error-display
26
- v-if="hasError.length"
27
- :error-group="'component'"
28
- :error-title="`ERREUR: COMPOSANT QUIZ RECALL`"
29
- :errors-list="hasError"
30
- ></app-base-error-display>
31
- <template v-else>
32
- <template v-if="quizRecall.done == true">
33
- <div
34
- v-if="quizRecall.hypertext_done"
35
- class="quizRecall-text-done"
36
- v-html="quizRecall.hypertext_done"
37
- ></div>
38
- <div class="quizRecall-ennonce" v-html="quizRecall.ennonce"></div>
39
-
40
- <textarea
41
- :id="`quizRecall-answer-${quizRecallData.quizId}`"
42
- v-model="quizRecall.answer"
43
- disabled
44
- class="form-control"
45
- ></textarea>
46
- </template>
47
- <template v-if="quizRecall.done == false">
48
- <div
49
- v-if="quizRecall.hypertext_undone"
50
- class="quizRecall-text-undone"
51
- v-html="quizRecall.hypertext_undone"
52
- ></div>
53
- </template>
54
- </template>
55
- </template>
56
- </section>
57
- </template>
58
-
59
- <script>
60
- //Recall mixins has the necessary datas and functions to give back quizRecall statu
61
- import { mapState } from 'pinia'
62
- import { useAppStore } from '../module/stores/appStore'
63
-
64
- export default {
65
- name: 'AppCompQuizRecall',
66
- props: {
67
- quizRecallData: { type: Object, required: true } //{activityId,pageId,hypertext_done,hypertext_undone,title, titletag}
68
- },
69
- data() {
70
- return {
71
- quizRecall: {
72
- done: false,
73
- answer: '',
74
- ennonce: '',
75
- title: '',
76
- titletag: 'h4',
77
- hypertext_done: `<p>${this.$t('message.recall_done')}</p>` /*String traduite par défaut*/,
78
- hypertext_undone: `<p>${this.$t(
79
- 'message.recall_undone'
80
- )}</p>` /*String traduite par défaut*/
81
- },
82
- hasError: [],
83
- quizData: null,
84
- isReady: false //Used to display skeleton while app data is not ready
85
- }
86
- },
87
- computed: {
88
- ...mapState(useAppStore, [
89
- 'getAllCompleted',
90
- 'getUserInteraction',
91
- 'getPageInteraction',
92
- 'getAppStatus',
93
- 'getPageData'
94
- ])
95
- },
96
-
97
- watch: {
98
- //Watch for user interaction change to get quizRecall answer
99
- getUserInteraction: {
100
- async handler(newValue) {
101
- if (!this.getUserInteraction) return
102
-
103
- const { activityId, pageId } = this.quizRecallData
104
- const interaction = this.getPageInteraction(
105
- activityId,
106
- pageId
107
- ).userInteraction
108
-
109
- if (!interaction) return
110
- //Get quizRecall answer
111
- if (this.quizRecallData && this.quizData) {
112
- await this.getQuizRecallAnswer(interaction)
113
- } else {
114
- this.quizRecall.done == false
115
- }
116
- },
117
- immediate: true,
118
- deep: true
119
- }
120
- },
121
- created() {
122
- //Validate quizRecall data
123
- //(no validation to the quiz, only to the new datas)
124
- if (import.meta.env.DEV) {
125
- this.validateQuizRecallData(this.quizRecallData)
126
- }
127
- const { activityId, pageId } = this.quizRecallData
128
- //Get quizData and validate the quiz type
129
- if (activityId && pageId) {
130
- this.getQuizData(activityId, pageId)
131
- }
132
- },
133
- methods: {
134
- /**
135
- * @description Finds a reponse_ouverte quiz data with specified ID
136
- * @param {Object} searchObject Object that will be iterated over to find the quiz data (The page "data" object)
137
- * @param {String} quizID ID of the quiz we are looking for
138
- */
139
- findQuizdataObject(searchObject, quizID) {
140
- for (let property in searchObject) {
141
- //Should only check for not null poperties
142
- if (!searchObject[property]) continue
143
-
144
- // Should only check for Object or Array property
145
- if (
146
- searchObject[property].constructor !== Object &&
147
- searchObject[property].constructor !== Array
148
- )
149
- continue
150
-
151
- if (
152
- searchObject[property].constructor == Array &&
153
- searchObject[property][0].constructor !== Object
154
- )
155
- continue
156
-
157
- // Convert if it not an Array but an Objet
158
- const searchArray =
159
- searchObject[property].constructor === Object
160
- ? [searchObject[property]]
161
- : searchObject[property]
162
- //Only Search for quiz:
163
- //Search Array is a list of quiz if one of its element has at least the property 'type_question'
164
- return searchArray[0]['type_question']
165
- ? searchArray.find(
166
- (e) => e.id === quizID && e.type_question === 'reponse_ouverte'
167
- )
168
- : null
169
- }
170
- },
171
- //Get datas from the original open answer quiz
172
- getQuizData(activityId, pageId) {
173
- if (pageId && activityId) {
174
- let pageData = this.getPageData(activityId, pageId)
175
-
176
- if (pageData) {
177
- this.quizData = this.findQuizdataObject(
178
- pageData,
179
- this.quizRecallData.quizId
180
- )
181
-
182
- //Warn that the specified quiz could not be found
183
- if (!this.quizData) {
184
- let msgErr = `Aucun quiz avec id '${this.quizRecallData.quizId}' pour la ${pageId} de ${activityId}`
185
- console.warn(
186
- `%c WARNING!>>> Quiz Recall: Unable to find a quiz with type_question 'reponse_ouverte' and id '${this.quizRecallData.quizId}' in ${pageId} of ${activityId}`,
187
- 'background: orange; color: white; display: block; margin:5px;'
188
- )
189
- this.hasError.push(msgErr)
190
- }
191
- } else {
192
- this.quizRecall.done == false
193
- let msgErr = `Aucun quiz avec id '${this.quizRecallData.quizId}' pour la ${pageId} de ${activityId}`
194
- console.warn(
195
- `%c WARNING!>>> AppCompQuizRecall : QuizData ActivityID and PageID combinaison is invalid.`,
196
- 'background: orange; color: white; display: block; margin:5px;'
197
- )
198
- this.hasError.push(msgErr)
199
- }
200
- } else {
201
- this.quizRecall.done == false
202
- }
203
- },
204
- //Get datas from quizRecallData and userData and add them to quizRecall object
205
- async getQuizRecallAnswer(userData) {
206
- await this.$nextTick() //wait for the DOM to update
207
- const { quizId, hypertext_done, hypertext_undone, title, titletag } =
208
- this.quizRecallData
209
-
210
- //Add hypertext_done and undone to quizRecall
211
- if (hypertext_done) {
212
- this.quizRecall.hypertext_done = hypertext_done
213
- }
214
- if (hypertext_undone) {
215
- this.quizRecall.hypertext_undone = hypertext_undone
216
- }
217
- //Add the title if it exists
218
- if (title) {
219
- this.quizRecall.title = title
220
- //Modify le titletag
221
- if (titletag) {
222
- let validator = this.validateTitleTag(titletag)
223
- if (validator) {
224
- this.quizRecall.titletag = titletag
225
- }
226
- }
227
- }
228
- //Get the quiz answers from userData
229
- const answers = userData.quizAnswers || {}
230
- let quizAnswer = answers[quizId] || null
231
-
232
- //If quiz answer exists in userData, quizRecall done is true and add the quiz answer to quizRecall
233
- quizAnswer
234
- ? (this.quizRecall.done = true)
235
- : (this.quizRecall.done = false)
236
-
237
- if (quizAnswer) {
238
- this.quizRecall.answer = quizAnswer.value[0].filled || ''
239
- this.quizRecall.ennonce = this.quizData.ennonce
240
- }
241
- this.quizRecall
242
- this.isReady = true //Set isReady to true to display the component
243
- },
244
-
245
- //Validate quizRecallData
246
- /*Validate if the required properties are present in the object and that there is not invalid properties */
247
- validateQuizRecallData(value) {
248
- let requiredProperties = [
249
- 'quizId',
250
- 'activityId',
251
- 'pageId',
252
- 'hypertext_done',
253
- 'hypertext_undone'
254
- ]
255
- let optionalProperties = ['title', 'titletag']
256
- let allProperties = requiredProperties.concat(optionalProperties)
257
-
258
- //Verify if the properties are valid
259
- let recallDataProperties = Object.keys(value)
260
- let wrongProperties = []
261
- let missingRequired = []
262
-
263
- //Get all the invalids properties from recallQuizData
264
- /*Add element from target array that are NOT in arr to the wrongArray*/
265
- let checkerWrong = (arr, target, wrongArray) =>
266
- target.every((v) => {
267
- if (arr.includes(v)) {
268
- return true
269
- } else {
270
- wrongArray.push(v)
271
- return true
272
- }
273
- })
274
- //Get all the required properties that are missing from recallQuizData
275
- /*Add element from the arr that are NOT in target arr to the missingArray*/
276
-
277
- let checkerMissing = (arr, target, missingArray) =>
278
- arr.every((v) => {
279
- if (target.includes(v)) {
280
- return true
281
- } else {
282
- missingArray.push(v)
283
- return true
284
- }
285
- })
286
-
287
- checkerWrong(allProperties, recallDataProperties, wrongProperties)
288
- checkerMissing(requiredProperties, recallDataProperties, missingRequired)
289
-
290
- //Validate if all properties in quizRecallData are valid
291
- if (wrongProperties.length > 0) {
292
- let msgErr = `QuizRecallData ${wrongProperties} invalid`
293
- console.warn(
294
- `%c WARNING!>>> AppCompQuizRecall : ${msgErr}. Required properties: ${requiredProperties} . Optional properties: ${optionalProperties}`,
295
- 'background: orange; color: white; display: block; margin:5px;'
296
- )
297
- this.hasError.push(msgErr)
298
- }
299
- //Validate if all required properties are present in quizRecallData
300
- if (missingRequired.length > 0) {
301
- let msgErr = `QuizRecallData missing required ${missingRequired} property`
302
- console.warn(
303
- `%c WARNING!>>> AppCompQuizRecall : ${msgErr}. Required properties: activityId and pageId. Optional properties: title, titletag, hypertext_done and hypertext_undone.`,
304
- 'background: orange; color: white; display: block; margin:5px;'
305
- )
306
- this.hasError.push(msgErr)
307
- }
308
- },
309
- /*Get a title tag (string). Return true if the tag is valid and false is it's not*/
310
-
311
- validateTitleTag(value) {
312
- let tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span']
313
- let lowerValue = value.toLowerCase()
314
- if (tags && value && tags.find((element) => element == lowerValue))
315
- return true
316
- else {
317
- let msgErr = `Your quizRecallData titletag is not valid`
318
- console.warn(
319
- '%c WARNING!>>> AppCompQuizRecall : Your quizRecallData titletag is not valid. You can use h1,h2,h3,h4,h5,h6,p,or span',
320
- 'background: orange; color: white; display: block; margin:5px;'
321
- )
322
- this.hasError.push(msgErr)
323
- return false
324
- }
325
- }
326
- }
327
- }
328
- </script>
329
-
330
- <style>
331
- .skeleton {
332
- height: 20px;
333
- width: 200px;
334
- background-color: #ccc;
335
- border-radius: 4px;
336
- animation: pulse 1.5s infinite ease-in-out;
337
- }
338
-
339
- @keyframes pulse {
340
- 0% {
341
- opacity: 1;
342
- }
343
- 50% {
344
- opacity: 0.4;
345
- }
346
- 100% {
347
- opacity: 1;
348
- }
349
- }
350
- </style>
1
+ <!--
2
+ @ Description: This component is used to display a quiz and its answer from a previous page if it has been completed by the user. The only quiz type supported is open answer (textarea) and the quiz must be in the same lesson.
3
+ @ What it does: Retrieve the quizData specific to an activity ID and pageID. Then, retrieve the answers previously saved by the user to this quiz. After, create an html element including title and conditional content. If the answer have been previously saved, a specific hypertext, the question and the answer (disabled textarea) are displayed. If the answer have not been saved, another hypertext is displayed.
4
+ @How to use: in a page call <app-comp-quiz-recall :quiz-recall-data="defineDQuizRecallData">
5
+ ** where "defineDQuizRecallData" refers to data object of quiz to pass to component
6
+ ** data object should containt following attributes: quizId,activityId,pageId,hypertext_done',hypertext_undone,title,titletag
7
+ -->
8
+ <template>
9
+ <section v-if="quizRecallData" class="quizRecall">
10
+ <!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
11
+ <!--Show skeleton while app data is not ready-->
12
+ <template v-if="!isReady">
13
+ <app-base-skeleton :skeleton-text="''" :skeleton-type="`quiz-recall`" />
14
+ </template>
15
+ <!--Show app data when ready-->
16
+ <template v-else>
17
+ <component
18
+ :is="quizRecall.titletag"
19
+ v-if="quizRecall.title"
20
+ class="quizRecall-title"
21
+ >
22
+ {{ quizRecall.title }}
23
+ </component>
24
+ <!--Quiz answer conditionning-->
25
+ <app-base-error-display
26
+ v-if="hasError.length"
27
+ :error-group="'component'"
28
+ :error-title="`ERREUR: COMPOSANT QUIZ RECALL`"
29
+ :errors-list="hasError"
30
+ ></app-base-error-display>
31
+ <template v-else>
32
+ <template v-if="quizRecall.done == true">
33
+ <div
34
+ v-if="quizRecall.hypertext_done"
35
+ class="quizRecall-text-done"
36
+ v-html="quizRecall.hypertext_done"
37
+ ></div>
38
+ <div class="quizRecall-ennonce" v-html="quizRecall.ennonce"></div>
39
+
40
+ <textarea
41
+ :id="`quizRecall-answer-${quizRecallData.quizId}`"
42
+ v-model="quizRecall.answer"
43
+ disabled
44
+ class="form-control"
45
+ ></textarea>
46
+ </template>
47
+ <template v-if="quizRecall.done == false">
48
+ <div
49
+ v-if="quizRecall.hypertext_undone"
50
+ class="quizRecall-text-undone"
51
+ v-html="quizRecall.hypertext_undone"
52
+ ></div>
53
+ </template>
54
+ </template>
55
+ </template>
56
+ </section>
57
+ </template>
58
+
59
+ <script>
60
+ //Recall mixins has the necessary datas and functions to give back quizRecall statu
61
+ import { mapState } from 'pinia'
62
+ import { useAppStore } from '../module/stores/appStore'
63
+ import { useI18n } from 'vue-i18n'
64
+
65
+ export default {
66
+ name: 'AppCompQuizRecall',
67
+ props: {
68
+ quizRecallData: { type: Object, required: true } //{activityId,pageId,hypertext_done,hypertext_undone,title, titletag}
69
+ },
70
+ setup() {
71
+ const { t } = useI18n()
72
+ return { t }
73
+ },
74
+ data() {
75
+ return {
76
+ quizRecall: {
77
+ done: false,
78
+ answer: '',
79
+ ennonce: '',
80
+ title: '',
81
+ titletag: 'h4',
82
+ hypertext_done: `<p>${this.$t('message.recall_done')}</p>` /*String traduite par défaut*/,
83
+ hypertext_undone: `<p>${this.$t(
84
+ 'message.recall_undone'
85
+ )}</p>` /*String traduite par défaut*/
86
+ },
87
+ hasError: [],
88
+ quizData: null,
89
+ isReady: false //Used to display skeleton while app data is not ready
90
+ }
91
+ },
92
+ computed: {
93
+ ...mapState(useAppStore, [
94
+ 'getAllCompleted',
95
+ 'getUserInteraction',
96
+ 'getPageInteraction',
97
+ 'getAppStatus',
98
+ 'getPageData'
99
+ ])
100
+ },
101
+
102
+ watch: {
103
+ //Watch for user interaction change to get quizRecall answer
104
+ getUserInteraction: {
105
+ async handler(newValue) {
106
+ if (!this.getUserInteraction) return
107
+
108
+ const { activityId, pageId } = this.quizRecallData
109
+ const interaction = this.getPageInteraction(
110
+ activityId,
111
+ pageId
112
+ ).userInteraction
113
+
114
+ if (!interaction) return
115
+ //Get quizRecall answer
116
+ if (this.quizRecallData && this.quizData) {
117
+ await this.getQuizRecallAnswer(interaction)
118
+ } else {
119
+ this.quizRecall.done == false
120
+ }
121
+ },
122
+ immediate: true,
123
+ deep: true
124
+ }
125
+ },
126
+ created() {
127
+ //Validate quizRecall data
128
+ //(no validation to the quiz, only to the new datas)
129
+ if (import.meta.env.DEV) {
130
+ this.validateQuizRecallData(this.quizRecallData)
131
+ }
132
+ const { activityId, pageId } = this.quizRecallData
133
+ //Get quizData and validate the quiz type
134
+ if (activityId && pageId) {
135
+ this.getQuizData(activityId, pageId)
136
+ }
137
+ },
138
+ methods: {
139
+ /**
140
+ * @description Finds a reponse_ouverte quiz data with specified ID
141
+ * @param {Object} searchObject Object that will be iterated over to find the quiz data (The page "data" object)
142
+ * @param {String} quizID ID of the quiz we are looking for
143
+ */
144
+ findQuizdataObject(searchObject, quizID) {
145
+ for (let property in searchObject) {
146
+ //Should only check for not null poperties
147
+ if (!searchObject[property]) continue
148
+
149
+ // Should only check for Object or Array property
150
+ if (
151
+ searchObject[property].constructor !== Object &&
152
+ searchObject[property].constructor !== Array
153
+ )
154
+ continue
155
+
156
+ if (
157
+ searchObject[property].constructor == Array &&
158
+ searchObject[property][0].constructor !== Object
159
+ )
160
+ continue
161
+
162
+ // Convert if it not an Array but an Objet
163
+ const searchArray =
164
+ searchObject[property].constructor === Object
165
+ ? [searchObject[property]]
166
+ : searchObject[property]
167
+ //Only Search for quiz:
168
+ //Search Array is a list of quiz if one of its element has at least the property 'type_question'
169
+ return searchArray[0]['type_question']
170
+ ? searchArray.find(
171
+ (e) => e.id === quizID && e.type_question === 'reponse_ouverte'
172
+ )
173
+ : null
174
+ }
175
+ },
176
+ //Get datas from the original open answer quiz
177
+ getQuizData(activityId, pageId) {
178
+ if (pageId && activityId) {
179
+ let pageData = this.getPageData(activityId, pageId)
180
+
181
+ if (pageData) {
182
+ this.quizData = this.findQuizdataObject(
183
+ pageData,
184
+ this.quizRecallData.quizId
185
+ )
186
+
187
+ //Warn that the specified quiz could not be found
188
+ if (!this.quizData) {
189
+ let msgErr = `Aucun quiz avec id '${this.quizRecallData.quizId}' pour la ${pageId} de ${activityId}`
190
+ console.warn(
191
+ `%c WARNING!>>> Quiz Recall: Unable to find a quiz with type_question 'reponse_ouverte' and id '${this.quizRecallData.quizId}' in ${pageId} of ${activityId}`,
192
+ 'background: orange; color: white; display: block; margin:5px;'
193
+ )
194
+ this.hasError.push(msgErr)
195
+ }
196
+ } else {
197
+ this.quizRecall.done == false
198
+ let msgErr = `Aucun quiz avec id '${this.quizRecallData.quizId}' pour la ${pageId} de ${activityId}`
199
+ console.warn(
200
+ `%c WARNING!>>> AppCompQuizRecall : QuizData ActivityID and PageID combinaison is invalid.`,
201
+ 'background: orange; color: white; display: block; margin:5px;'
202
+ )
203
+ this.hasError.push(msgErr)
204
+ }
205
+ } else {
206
+ this.quizRecall.done == false
207
+ }
208
+ },
209
+ //Get datas from quizRecallData and userData and add them to quizRecall object
210
+ async getQuizRecallAnswer(userData) {
211
+ await this.$nextTick() //wait for the DOM to update
212
+ const { quizId, hypertext_done, hypertext_undone, title, titletag } =
213
+ this.quizRecallData
214
+
215
+ //Add hypertext_done and undone to quizRecall
216
+ if (hypertext_done) {
217
+ this.quizRecall.hypertext_done = hypertext_done
218
+ }
219
+ if (hypertext_undone) {
220
+ this.quizRecall.hypertext_undone = hypertext_undone
221
+ }
222
+ //Add the title if it exists
223
+ if (title) {
224
+ this.quizRecall.title = title
225
+ //Modify le titletag
226
+ if (titletag) {
227
+ let validator = this.validateTitleTag(titletag)
228
+ if (validator) {
229
+ this.quizRecall.titletag = titletag
230
+ }
231
+ }
232
+ }
233
+ //Get the quiz answers from userData
234
+ const answers = userData.quizAnswers || {}
235
+ let quizAnswer = answers[quizId] || null
236
+
237
+ //If quiz answer exists in userData, quizRecall done is true and add the quiz answer to quizRecall
238
+ quizAnswer
239
+ ? (this.quizRecall.done = true)
240
+ : (this.quizRecall.done = false)
241
+
242
+ if (quizAnswer) {
243
+ this.quizRecall.answer = quizAnswer.value[0].filled || ''
244
+ this.quizRecall.ennonce = this.quizData.ennonce
245
+ }
246
+ this.quizRecall
247
+ this.isReady = true //Set isReady to true to display the component
248
+ },
249
+
250
+ //Validate quizRecallData
251
+ /*Validate if the required properties are present in the object and that there is not invalid properties */
252
+ validateQuizRecallData(value) {
253
+ let requiredProperties = [
254
+ 'quizId',
255
+ 'activityId',
256
+ 'pageId',
257
+ 'hypertext_done',
258
+ 'hypertext_undone'
259
+ ]
260
+ let optionalProperties = ['title', 'titletag']
261
+ let allProperties = requiredProperties.concat(optionalProperties)
262
+
263
+ //Verify if the properties are valid
264
+ let recallDataProperties = Object.keys(value)
265
+ let wrongProperties = []
266
+ let missingRequired = []
267
+
268
+ //Get all the invalids properties from recallQuizData
269
+ /*Add element from target array that are NOT in arr to the wrongArray*/
270
+ let checkerWrong = (arr, target, wrongArray) =>
271
+ target.every((v) => {
272
+ if (arr.includes(v)) {
273
+ return true
274
+ } else {
275
+ wrongArray.push(v)
276
+ return true
277
+ }
278
+ })
279
+ //Get all the required properties that are missing from recallQuizData
280
+ /*Add element from the arr that are NOT in target arr to the missingArray*/
281
+
282
+ let checkerMissing = (arr, target, missingArray) =>
283
+ arr.every((v) => {
284
+ if (target.includes(v)) {
285
+ return true
286
+ } else {
287
+ missingArray.push(v)
288
+ return true
289
+ }
290
+ })
291
+
292
+ checkerWrong(allProperties, recallDataProperties, wrongProperties)
293
+ checkerMissing(requiredProperties, recallDataProperties, missingRequired)
294
+
295
+ //Validate if all properties in quizRecallData are valid
296
+ if (wrongProperties.length > 0) {
297
+ let msgErr = `QuizRecallData ${wrongProperties} invalid`
298
+ console.warn(
299
+ `%c WARNING!>>> AppCompQuizRecall : ${msgErr}. Required properties: ${requiredProperties} . Optional properties: ${optionalProperties}`,
300
+ 'background: orange; color: white; display: block; margin:5px;'
301
+ )
302
+ this.hasError.push(msgErr)
303
+ }
304
+ //Validate if all required properties are present in quizRecallData
305
+ if (missingRequired.length > 0) {
306
+ let msgErr = `QuizRecallData missing required ${missingRequired} property`
307
+ console.warn(
308
+ `%c WARNING!>>> AppCompQuizRecall : ${msgErr}. Required properties: activityId and pageId. Optional properties: title, titletag, hypertext_done and hypertext_undone.`,
309
+ 'background: orange; color: white; display: block; margin:5px;'
310
+ )
311
+ this.hasError.push(msgErr)
312
+ }
313
+ },
314
+ /*Get a title tag (string). Return true if the tag is valid and false is it's not*/
315
+
316
+ validateTitleTag(value) {
317
+ let tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span']
318
+ let lowerValue = value.toLowerCase()
319
+ if (tags && value && tags.find((element) => element == lowerValue))
320
+ return true
321
+ else {
322
+ let msgErr = `Your quizRecallData titletag is not valid`
323
+ console.warn(
324
+ '%c WARNING!>>> AppCompQuizRecall : Your quizRecallData titletag is not valid. You can use h1,h2,h3,h4,h5,h6,p,or span',
325
+ 'background: orange; color: white; display: block; margin:5px;'
326
+ )
327
+ this.hasError.push(msgErr)
328
+ return false
329
+ }
330
+ }
331
+ }
332
+ }
333
+ </script>
334
+
335
+ <style>
336
+ .skeleton {
337
+ height: 20px;
338
+ width: 200px;
339
+ background-color: #ccc;
340
+ border-radius: 4px;
341
+ animation: pulse 1.5s infinite ease-in-out;
342
+ }
343
+
344
+ @keyframes pulse {
345
+ 0% {
346
+ opacity: 1;
347
+ }
348
+ 50% {
349
+ opacity: 0.4;
350
+ }
351
+ 100% {
352
+ opacity: 1;
353
+ }
354
+ }
355
+ </style>