fcad-core-dragon 2.0.0-beta.0 → 2.0.0-beta.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 (84) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +13 -18
  2. package/README.md +1 -1
  3. package/bk.scss +117 -0
  4. package/package.json +22 -40
  5. package/src/$locales/en.json +57 -19
  6. package/src/$locales/fr.json +66 -28
  7. package/src/components/AppBase.vue +790 -376
  8. package/src/components/AppBaseButton.vue +33 -5
  9. package/src/components/AppBaseErrorDisplay.vue +62 -25
  10. package/src/components/AppBaseModule.vue +831 -754
  11. package/src/components/AppBasePage.vue +60 -74
  12. package/src/components/AppCompAudio.vue +266 -0
  13. package/src/components/AppCompBranchButtons.vue +79 -89
  14. package/src/components/AppCompButtonProgress.vue +35 -61
  15. package/src/components/AppCompCarousel.vue +160 -249
  16. package/src/components/AppCompInputCheckBox.vue +9 -3
  17. package/src/components/AppCompInputDropdown.vue +2 -4
  18. package/src/components/AppCompInputRadio.vue +8 -15
  19. package/src/components/AppCompInputTextTable.vue +15 -12
  20. package/src/components/AppCompInputTextToFillDropdown.vue +16 -14
  21. package/src/components/AppCompInputTextToFillText.vue +2 -2
  22. package/src/components/AppCompJauge.vue +14 -3
  23. package/src/components/AppCompMenu.vue +284 -85
  24. package/src/components/AppCompMenuItem.vue +67 -92
  25. package/src/components/AppCompNavigation.vue +945 -0
  26. package/src/components/AppCompNoteCall.vue +141 -0
  27. package/src/components/AppCompNoteCredit.vue +267 -0
  28. package/src/components/AppCompPlayBar.vue +1122 -1391
  29. package/src/components/AppCompPlayBarProgress.vue +73 -0
  30. package/src/components/AppCompPopUp.vue +195 -135
  31. package/src/components/AppCompPopover.vue +27 -0
  32. package/src/components/AppCompQuiz.vue +90 -113
  33. package/src/components/AppCompQuizRecall.vue +277 -0
  34. package/src/components/AppCompSVG.vue +335 -0
  35. package/src/components/AppCompSettingsMenu.vue +7 -8
  36. package/src/components/AppCompTableOfContent.vue +264 -88
  37. package/src/components/AppCompTranscript.vue +19 -0
  38. package/src/components/AppCompVideoPlayer.vue +380 -0
  39. package/src/components/BaseModule.vue +37 -114
  40. package/src/main.js +130 -85
  41. package/src/mixins/$mediaMixins.js +827 -0
  42. package/src/mixins/$pageMixins.js +149 -115
  43. package/src/mixins/$quizMixins.js +12 -26
  44. package/src/mixins/timerMixin.js +39 -16
  45. package/src/module/store.js +218 -78
  46. package/src/module/xapi/ADL.js +90 -53
  47. package/src/module/xapi/Crypto/Hasher.js +8 -8
  48. package/src/module/xapi/Crypto/WordArray.js +6 -6
  49. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +4 -4
  50. package/src/module/xapi/Crypto/algorithms/C_algo.js +14 -18
  51. package/src/module/xapi/Crypto/algorithms/HMAC.js +1 -1
  52. package/src/module/xapi/Crypto/algorithms/SHA1.js +1 -1
  53. package/src/module/xapi/Crypto/encoders/Base.js +7 -7
  54. package/src/module/xapi/Crypto/encoders/Base64.js +3 -3
  55. package/src/module/xapi/Crypto/encoders/Hex.js +4 -3
  56. package/src/module/xapi/Crypto/encoders/Latin1.js +3 -3
  57. package/src/module/xapi/Crypto/encoders/Utf8.js +3 -3
  58. package/src/module/xapi/Statement/index.js +1 -1
  59. package/src/module/xapi/launch.js +10 -10
  60. package/src/module/xapi/utils.js +17 -17
  61. package/src/module/xapi/wrapper.js +127 -54
  62. package/src/module/xapi/xapiStatement.js +29 -29
  63. package/src/plugins/gsap.js +4 -1
  64. package/src/plugins/helper.js +58 -24
  65. package/src/plugins/i18n.js +23 -10
  66. package/src/plugins/idb.js +1 -0
  67. package/src/plugins/scorm.js +14 -14
  68. package/src/public/index.html +1 -1
  69. package/src/router/index.js +40 -0
  70. package/src/router/routes.js +317 -0
  71. package/src/shared/generalfuncs.js +91 -9
  72. package/src/shared/validators.js +959 -0
  73. package/.prettierrc.js +0 -5
  74. package/babel.config.js +0 -3
  75. package/src/components/AppBaseDragChoice.vue +0 -91
  76. package/src/components/AppBaseDropZone.vue +0 -112
  77. package/src/components/AppCompDragAndDrop.vue +0 -339
  78. package/src/components/AppCompInputAssociation.vue +0 -332
  79. package/src/components/AppCompMediaPlayer.vue +0 -365
  80. package/src/components/AppCompNavigationFull.vue +0 -1791
  81. package/src/components/AppCompToolTip.vue +0 -94
  82. package/src/plugins/timeManager.js +0 -77
  83. package/src/routes.js +0 -734
  84. package/vue.config.js +0 -83
@@ -21,11 +21,7 @@
21
21
  },
22
22
  {
23
23
  'container-quiz-texte-troue': quizData.type_question == 'texte_troue'
24
- },
25
- {
26
- 'container-quiz-association': quizData.type_question == 'association'
27
- },
28
- { 'container-quiz-drag-drop': quizData.type_question == 'dragdrop' }
24
+ }
29
25
  ]"
30
26
  >
31
27
  <div
@@ -39,16 +35,14 @@
39
35
  quizData.type_question == 'texte_troue_select'
40
36
  },
41
37
  { 'quiz-texte-tableau': quizData.type_question == 'texte_tableau' },
42
- { 'quiz-texte-troue': quizData.type_question == 'texte_troue' },
43
- { 'quiz-association': quizData.type_question == 'association' },
44
- { 'quiz-drag-drop': quizData.type_question == 'dragdrop' }
38
+ { 'quiz-texte-troue': quizData.type_question == 'texte_troue' }
45
39
  ]"
46
40
  >
47
41
  <div
48
42
  :id="`${quizData.type_question}_${quizData.id}`"
49
43
  class="quiz-question"
50
44
  >
51
- <span class="sr-only">question:</span>
45
+ <span class="sr-only">question :</span>
52
46
  <div v-html="quizEnnonce" />
53
47
  </div>
54
48
 
@@ -75,7 +69,6 @@
75
69
 
76
70
  <div class="btn-ctrl-quiz">
77
71
  <app-base-button
78
- v-show="$store.state.$appStore.isDr"
79
72
  :id="`btn_quiz_${quizData.id}`"
80
73
  ref="quiz"
81
74
  class="btn-quiz"
@@ -85,21 +78,23 @@
85
78
  >
86
79
  {{ txtBtnSubmit }}
87
80
  </app-base-button>
88
-
89
- <!-- REMOVE FROM PHASE 3 -->
90
- <!-- <app-base-button
91
- v-if="$store.state.$appStore.isDr"
92
- id="btn-reset"
93
- ref="quiz"
94
- :is-active="quizIsNotStarted"
95
- @click="resetQuiz"
96
- >
97
- {{ $t('button.reset') }}
98
- </app-base-button> -->
99
- <!--TO DOULBE CHCEK...-->
100
81
  </div>
101
82
  </div>
102
83
  </b-col>
84
+ <b-col cols="12" aria-live="polite">
85
+ <transition name="fade" mode="in-out">
86
+ <div
87
+ v-if="showSolution && showRetro"
88
+ :class="`retro_inline_wrapper retro_inline_${retroType}`"
89
+ :aria-label="retroAriaLabel"
90
+ >
91
+ <div class="retro-title-container">
92
+ <span class="retro-title">{{ retroTitle }}</span>
93
+ </div>
94
+ <div class="retro-text-container" v-html="retroHtml"></div>
95
+ </div>
96
+ </transition>
97
+ </b-col>
103
98
  </b-row>
104
99
  <b-row v-else-if="errorQuizItems" class="warning-error">
105
100
  <b-col class="box-error">
@@ -118,7 +113,7 @@
118
113
  </li>
119
114
  </ul>
120
115
  </div>
121
- <div class="box ">
116
+ <div class="box">
122
117
  <p class="doc">
123
118
  Visitez
124
119
  <a href="https://fcaddocumentation.netlify.app/" target="blank">
@@ -151,7 +146,6 @@ import $extendsQuiz from '../mixins/$quizMixins'
151
146
  export default {
152
147
  name: 'AppCompQuiz',
153
148
  components: {
154
- AppBaseInstruction: () => import('./AppBaseInstruction.vue'),
155
149
  AppCompInputTextBox: () => import('./AppCompInputTextBox.vue'),
156
150
  AppCompInputDropdown: () => import('./AppCompInputDropdown.vue'),
157
151
  AppCompInputCheckBox: () => import('./AppCompInputCheckBox.vue'),
@@ -159,10 +153,9 @@ export default {
159
153
  AppCompInputTextToFillDropdown: () =>
160
154
  import('./AppCompInputTextToFillDropdown.vue'),
161
155
  AppCompInputTextTable: () => import('./AppCompInputTextTable.vue'),
162
- AppCompInputTextToFillText: () =>
163
- import('./AppCompInputTextToFillText.vue'),
164
- AppCompInputAssociation: () => import('./AppCompInputAssociation.vue'),
165
- AppCompInputDragDrop: () => import('./AppCompDragAndDrop.vue')
156
+ AppCompInputTextToFillText: () => import('./AppCompInputTextToFillText.vue')
157
+ //AppCompInputAssociation: () => import('./AppCompInputAssociation.vue'),
158
+ //AppCompInputDragDrop: () => import('./AppCompDragAndDrop.vue')
166
159
  },
167
160
  mixins: [$extendsQuiz],
168
161
  props: {
@@ -177,7 +170,7 @@ export default {
177
170
 
178
171
  quizData: {
179
172
  type: Object,
180
- validator: function(value) {
173
+ validator: function (value) {
181
174
  //verify that element has all required properties
182
175
  if (
183
176
  value.id &&
@@ -239,15 +232,35 @@ export default {
239
232
  quizDragDrop: {},
240
233
  quizSubmit: false, //added to explod comp^
241
234
  initQuizSelected: [], //value of the saved answers of the dropdown quiz
242
- initQuizTable: [] //value of the saved answers of the table quiz
235
+ initQuizTable: [], //value of the saved answers of the table quiz
236
+ retroType: '',
237
+ retroTitle: '',
238
+ retroHtml: '',
239
+ showRetro: false
243
240
  }
244
241
  },
245
242
  computed: {
246
243
  ...mapGetters([
247
244
  'getUserInteraction',
248
245
  'getCurrentPage',
249
- 'getConnectionInfo'
246
+ 'getConnectionInfo',
247
+ 'getDataFromServer'
250
248
  ]),
249
+ retroAriaLabel() {
250
+ let label = ''
251
+ switch (this.retroType) {
252
+ case 'neutral':
253
+ label = this.$t('quizState.neutralAnswer')
254
+ break
255
+ case 'positive':
256
+ label = this.$t('quizState.goodAnswer')
257
+ break
258
+ case 'negative':
259
+ label = this.$t('quizState.badAnswer')
260
+ break
261
+ }
262
+ return label
263
+ },
251
264
  /**
252
265
  * @description To control whether or not the submit button is disabled
253
266
  * @returns {Boolean} true if the quiz is not yet answered, false the button is enable
@@ -1231,13 +1244,12 @@ export default {
1231
1244
  }
1232
1245
  if (
1233
1246
  !this.isEqual(
1234
- Object.keys(theQuiz.solution).sort(function(
1235
- a,
1236
- b
1237
- ) {
1238
- return a - b
1239
- }),
1240
- listIdZone.sort(function(a, b) {
1247
+ Object.keys(theQuiz.solution).sort(
1248
+ function (a, b) {
1249
+ return a - b
1250
+ }
1251
+ ),
1252
+ listIdZone.sort(function (a, b) {
1241
1253
  return a - b
1242
1254
  })
1243
1255
  ) &&
@@ -1268,13 +1280,12 @@ export default {
1268
1280
  }
1269
1281
  if (
1270
1282
  !this.isEqual(
1271
- Object.values(theQuiz.solution).sort(function(
1272
- a,
1273
- b
1274
- ) {
1275
- return a - b
1276
- }),
1277
- listIdChoice.sort(function(a, b) {
1283
+ Object.values(theQuiz.solution).sort(
1284
+ function (a, b) {
1285
+ return a - b
1286
+ }
1287
+ ),
1288
+ listIdChoice.sort(function (a, b) {
1278
1289
  return a - b
1279
1290
  })
1280
1291
  ) &&
@@ -1789,10 +1800,10 @@ export default {
1789
1800
  }
1790
1801
  if (
1791
1802
  !this.isEqual(
1792
- Object.keys(theQuiz.solution).sort(function(a, b) {
1803
+ Object.keys(theQuiz.solution).sort(function (a, b) {
1793
1804
  return a - b
1794
1805
  }),
1795
- listIdZone.sort(function(a, b) {
1806
+ listIdZone.sort(function (a, b) {
1796
1807
  return a - b
1797
1808
  })
1798
1809
  ) &&
@@ -2322,37 +2333,11 @@ export default {
2322
2333
  * */
2323
2334
  txtBtnSubmit() {
2324
2335
  let str = ''
2325
- const { type_question, solution } = this.quizData
2326
- //Is the quiz one of these
2327
- let isMulti = [
2328
- 'dropdown',
2329
- 'texte_tableau',
2330
- 'texte_troue',
2331
- 'texte_troue_select'
2332
- ].includes(type_question)
2333
- ? true
2334
- : false
2336
+ const { solution } = this.quizData
2335
2337
 
2336
- if (isMulti) {
2337
- if (solution === null)
2338
- str = `${this.$t('button.save')} ${this.$t('button.quiz_multiple')}`
2339
- //There is more than on element in the solution
2340
- else if (solution.length > 1)
2341
- str = `${this.$t('button.quiz_verify')} ${this.$t(
2342
- 'button.quiz_multiple'
2343
- )}`
2344
- //only one element in the solution
2345
- else
2346
- str = `${this.$t('button.quiz_verify')} ${this.$t(
2347
- 'button.quiz_single'
2348
- )}`
2349
- } else {
2350
- //Quiz text area
2351
- if (solution === null)
2352
- str = `${this.$t('button.save')} ${this.$t('button.quiz_single')}`
2353
- //None of the above case
2354
- else str = this.$t('button.submit')
2355
- }
2338
+ if (solution === null) str = this.$t('button.save')
2339
+ //There is more than on element in the solution
2340
+ else str = this.$t('button.quiz_verify')
2356
2341
 
2357
2342
  return str
2358
2343
  },
@@ -2364,7 +2349,9 @@ export default {
2364
2349
  quizEnnonce() {
2365
2350
  const { ennonce, id } = this.quizData
2366
2351
  let _ennonce = ennonce
2367
-
2352
+ /**
2353
+ *Image in quiz enonce is disabled for now
2354
+ *
2368
2355
  const regex = /\$img__[\S|\s]*\$/i // regex pattern to match exp: $img__myimage$
2369
2356
  let matchAll = _ennonce.match(regex)
2370
2357
 
@@ -2378,15 +2365,17 @@ export default {
2378
2365
 
2379
2366
  const imgName = imgString.substring(0, splitPosition) //get the name of the image
2380
2367
  const imgAlt = imgString.substring(splitPosition + 1) // get the alt define for the image
2368
+
2381
2369
 
2382
- const imgFile = require(`@/assets/img/${imgName.trim()}`) // require the image file
2370
+ const imgFile = import(`@/assets/img/${imgName.trim()}`)
2383
2371
 
2384
2372
  _ennonce = _ennonce.replace(
2385
2373
  regex,
2386
2374
  `<img src=${imgFile} alt='${imgAlt}' id='img_for_en_${id}' class='img-ennonce'>`
2387
2375
  ) //replace with correct img tag
2376
+
2388
2377
  })
2389
-
2378
+ */
2390
2379
  return _ennonce
2391
2380
  }
2392
2381
  },
@@ -2455,22 +2444,18 @@ export default {
2455
2444
  this.showSolution = false
2456
2445
  }
2457
2446
  },
2458
- getConnectionInfo: {
2447
+ getUserInteraction: {
2459
2448
  //in development environment (localhost), don't wait for the axios call
2460
- immediate: process.env.NODE_ENV === 'development',
2449
+ // immediate: import.meta.env.DEV,
2450
+ immediate: true,
2461
2451
  handler() {
2462
- if (!this.getConnectionInfo) return
2452
+ if (!this.getUserInteraction) return
2463
2453
  this.getPreviousAnswers()
2464
2454
  }
2465
2455
  }
2466
2456
  },
2467
- created() {
2468
- this.getPreviousAnswers(),
2469
- this.$bus.$on('validateQuiz', () => {
2470
- this.quizSubmit = true
2471
- })
2472
- },
2473
-
2457
+ created() {},
2458
+ beforeUnmount() {},
2474
2459
  mounted() {},
2475
2460
  methods: {
2476
2461
  showNoAnswerPopup() {
@@ -2494,13 +2479,11 @@ export default {
2494
2479
  let theSolution = theQuiz.solution
2495
2480
  if (theSolution == null) {
2496
2481
  //Create the popup with the content of the retrotaction neutre
2497
- this.createPopUp(
2498
- {
2499
- type: 'popup-retro',
2500
- value: theQuiz.retroaction.retro_neutre
2501
- },
2502
- 'retro_neutre'
2503
- )
2482
+ this.retroType = 'neutral'
2483
+ this.retroTitle = theQuiz.retroaction.retro_neutre.title
2484
+ this.retroHtml = theQuiz.retroaction.retro_neutre.hypertext_1
2485
+ this.showSolution = true
2486
+ this.showRetro = true
2504
2487
  } else {
2505
2488
  let correctAnswer = false
2506
2489
  switch (theQuiz.type_question) {
@@ -2509,10 +2492,10 @@ export default {
2509
2492
  break
2510
2493
  case 'choix_mult':
2511
2494
  correctAnswer = this.isEqual(
2512
- theAnswer[theQuiz.id].sort(function(a, b) {
2495
+ theAnswer[theQuiz.id].sort(function (a, b) {
2513
2496
  return a - b
2514
2497
  }),
2515
- theSolution.sort(function(a, b) {
2498
+ theSolution.sort(function (a, b) {
2516
2499
  return a - b
2517
2500
  })
2518
2501
  )
@@ -2569,23 +2552,17 @@ export default {
2569
2552
  }
2570
2553
  }
2571
2554
  if (correctAnswer) {
2555
+ this.retroTitle = theQuiz.retroaction.retro_positive.title
2556
+ this.retroHtml = theQuiz.retroaction.retro_positive.hypertext_1
2557
+ this.retroType = 'positive'
2572
2558
  this.showSolution = true
2573
- this.createPopUp(
2574
- {
2575
- type: 'popup-retro',
2576
- value: theQuiz.retroaction.retro_positive
2577
- },
2578
- 'retro_positive'
2579
- )
2559
+ this.showRetro = true
2580
2560
  } else {
2561
+ this.retroTitle = theQuiz.retroaction.retro_negative.title
2562
+ this.retroHtml = theQuiz.retroaction.retro_negative.hypertext_1
2563
+ this.retroType = 'negative'
2581
2564
  this.showSolution = true
2582
- this.createPopUp(
2583
- {
2584
- type: 'popup-retro',
2585
- value: theQuiz.retroaction.retro_negative
2586
- },
2587
- 'retro_negative'
2588
- )
2565
+ this.showRetro = true
2589
2566
  this.totalAttempts++
2590
2567
  if (
2591
2568
  this.totalAttempts >= theQuiz.max_essai &&
@@ -0,0 +1,277 @@
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
+ -->
5
+
6
+ <template>
7
+ <section class="quizRecall">
8
+ <!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
9
+ <component
10
+ :is="quizRecall.titletag"
11
+ v-if="quizRecall.title"
12
+ class="quizRecall-title"
13
+ >
14
+ {{ quizRecall.title }}
15
+ </component>
16
+ <!--Quiz answer conditionning-->
17
+ <template v-if="quizRecall.done == true">
18
+ <div
19
+ v-if="quizRecall.hypertext_done"
20
+ class="quizRecall-text-done"
21
+ v-html="quizRecall.hypertext_done"
22
+ ></div>
23
+ <div class="quizRecall-ennonce" v-html="quizRecall.ennonce"></div>
24
+ <textarea
25
+ v-model="quizRecall.answer"
26
+ disabled
27
+ class="form-control"
28
+ ></textarea>
29
+ </template>
30
+ <template v-if="quizRecall.done == false">
31
+ <div
32
+ v-if="quizRecall.hypertext_undone"
33
+ class="quizRecall-text-undone"
34
+ v-html="quizRecall.hypertext_undone"
35
+ ></div>
36
+ </template>
37
+ </section>
38
+ </template>
39
+
40
+ <script>
41
+ //Recall mixins has the necessary datas and functions to give back quizRecall statu
42
+ import { mapGetters } from 'vuex'
43
+ export default {
44
+ name: 'AppCompQuizRecall',
45
+ props: {
46
+ quizRecallData: { type: Object, required: true } //{activityId,pageId,hypertext_done,hypertext_undone,title, titletag}
47
+ },
48
+ data() {
49
+ return {
50
+ quizRecall: {
51
+ done: false,
52
+ answer: '',
53
+ ennonce: '',
54
+ title: '',
55
+ titletag: 'h4',
56
+ hypertext_done: `<p>${this.$t(
57
+ 'message.recall_done'
58
+ )}</p>` /*String traduite par défaut*/,
59
+ hypertext_undone: `<p>${this.$t(
60
+ 'message.recall_undone'
61
+ )}</p>` /*String traduite par défaut*/
62
+ },
63
+ quizData: null
64
+ }
65
+ },
66
+ computed: {
67
+ ...mapGetters(['getAllCompleted', 'getUserInteraction', 'getPageData'])
68
+ },
69
+ watch: {
70
+ //Change quizRecall when userData is changed (it is where answer are saved)
71
+ getUserInteraction(newUserData, oldUserData) {
72
+ if (this.quizRecallData && this.quizData) {
73
+ this.getQuizRecallAnswer(this.quizRecallData, this.quizData)
74
+ } else {
75
+ this.quizRecall.done == false
76
+ }
77
+ }
78
+ },
79
+ mounted() {},
80
+ created() {
81
+ //Validate quizRecall data
82
+ //(no validation to the quiz, only to the new datas)
83
+ this.validateQuizRecallData(this.quizRecallData)
84
+
85
+ //Get quizData and validate the quiz type
86
+ if (this.quizRecallData.activityId && this.quizRecallData.pageId) {
87
+ this.getQuizData(
88
+ this.quizRecallData.activityId,
89
+ this.quizRecallData.pageId
90
+ )
91
+ }
92
+
93
+ //Get quizRecall answer
94
+ if (this.quizRecallData && this.quizData) {
95
+ this.getQuizRecallAnswer(this.quizRecallData, this.quizData)
96
+ } else {
97
+ this.quizRecall.done == false
98
+ }
99
+ },
100
+ methods: {
101
+ /**
102
+ * @description Finds a reponse_ouverte quiz data with specified ID
103
+ * @param {Object} searchObject Object that will be iterated over to find the quiz data (The page "data" object)
104
+ * @param {String} quizID ID of the quiz we are looking for
105
+ */
106
+ findQuizdataObject(searchObject, quizID) {
107
+ for (let property in searchObject) {
108
+ // Check if the current property is an object and has the required properties
109
+ if (
110
+ typeof searchObject[property] === 'object' &&
111
+ searchObject[property] !== null
112
+ ) {
113
+ if (
114
+ searchObject[property].id === quizID &&
115
+ searchObject[property].type_question === 'reponse_ouverte'
116
+ ) {
117
+ return searchObject[property] // Valid Quiz data found
118
+ }
119
+ }
120
+ }
121
+
122
+ // No reponse_ouverte quiz data with specified ID was found
123
+ return null
124
+ },
125
+ //Get datas from the original open answer quiz
126
+ getQuizData(activityId, pageId) {
127
+ if (pageId && activityId) {
128
+ let pageData = this.getPageData(activityId, pageId)
129
+ if (pageData && pageData.content) {
130
+ this.quizData = this.findQuizdataObject(
131
+ pageData.content,
132
+ this.quizRecallData.quizId
133
+ )
134
+ //Warn that the specified quiz could not be found
135
+ if (this.quizData === null) {
136
+ console.warn(
137
+ `%c WARNING!>>> Quiz Recall: Unable to find a quiz with type_question 'reponse_ouverte' and id '${this.quizRecallData.quizId}' in ${pageId} of ${activityId}`,
138
+ 'background: orange; color: white; display: block; margin:5px;'
139
+ )
140
+ }
141
+ } else {
142
+ this.quizRecall.done == false
143
+ console.warn(
144
+ `%c WARNING!>>> AppCompQuizRecall : QuizData ActivityID and PageID combinaison is invalid.`,
145
+ 'background: orange; color: white; display: block; margin:5px;'
146
+ )
147
+ }
148
+ } else {
149
+ this.quizRecall.done == false
150
+ }
151
+ },
152
+ //Get datas from quizRecallData and userData and add them to quizRecall object
153
+ getQuizRecallAnswer(quizRecallData, quizData) {
154
+ //Get userData
155
+ let userData = this.getUserInteraction
156
+ //Get quizId
157
+ let quizId = quizData.id
158
+ //Get activityId
159
+ let activityId = quizRecallData.activityId
160
+ //Get pageId
161
+ let pageId = quizRecallData.pageId
162
+ //Add hypertext_done and undone to quizRecall
163
+ if (quizRecallData.hypertext_done) {
164
+ this.quizRecall.hypertext_done = quizRecallData.hypertext_done
165
+ }
166
+ if (quizRecallData.hypertext_undone) {
167
+ this.quizRecall.hypertext_undone = quizRecallData.hypertext_undone
168
+ }
169
+ //Add the title if it exists
170
+ if (quizRecallData.title) {
171
+ this.quizRecall.title = quizRecallData.title
172
+ //Modify le titletag
173
+ if (quizRecallData.titletag) {
174
+ let validator = this.validateTitleTag(this.quizRecallData.titletag)
175
+ if (validator) {
176
+ this.quizRecall.titletag = this.quizRecallData.titletag
177
+ }
178
+ }
179
+ }
180
+
181
+ //If quiz answer exists in userData, quizRecall done is true and add the quiz answer to quizRecall
182
+ if (
183
+ userData &&
184
+ userData[activityId] &&
185
+ userData[activityId][pageId] &&
186
+ userData[activityId][pageId].userInteraction &&
187
+ userData[activityId][pageId].userInteraction.quizAnswers &&
188
+ userData[activityId][pageId].userInteraction.quizAnswers[quizId]
189
+ ) {
190
+ this.quizRecall.done = true
191
+ this.quizRecall.answer =
192
+ userData[activityId][pageId].userInteraction.quizAnswers[quizId]
193
+ this.quizRecall.ennonce = quizData.ennonce
194
+ } else {
195
+ //If the quiz answer doesn't exist
196
+ this.quizRecall.done = false
197
+ }
198
+ },
199
+
200
+ //Validate quizRecallData
201
+ /*Validate if the required properties are present in the object and that there is not invalid properties */
202
+ validateQuizRecallData(value) {
203
+ let requiredProperties = [
204
+ 'quizId',
205
+ 'activityId',
206
+ 'pageId',
207
+ 'hypertext_done',
208
+ 'hypertext_undone'
209
+ ]
210
+ let optionalProperties = ['title', 'titletag']
211
+ let allProperties = requiredProperties.concat(optionalProperties)
212
+
213
+ //Verify if the properties are valid
214
+ let recallDataProperties = Object.keys(value)
215
+ let wrongProperties = []
216
+ let missingRequired = []
217
+
218
+ //Get all the invalids properties from recallQuizData
219
+ /*Add element from target array that are NOT in arr to the wrongArray*/
220
+ let checkerWrong = (arr, target, wrongArray) =>
221
+ target.every((v) => {
222
+ if (arr.includes(v)) {
223
+ return true
224
+ } else {
225
+ wrongArray.push(v)
226
+ return true
227
+ }
228
+ })
229
+ //Get all the required properties that are missing from recallQuizData
230
+ /*Add element from the arr that are NOT in target arr to the missingArray*/
231
+
232
+ let checkerMissing = (arr, target, missingArray) =>
233
+ arr.every((v) => {
234
+ if (target.includes(v)) {
235
+ return true
236
+ } else {
237
+ missingArray.push(v)
238
+ return true
239
+ }
240
+ })
241
+
242
+ checkerWrong(allProperties, recallDataProperties, wrongProperties)
243
+ checkerMissing(requiredProperties, recallDataProperties, missingRequired)
244
+
245
+ //Validate if all properties in quizRecallData are valid
246
+ if (wrongProperties.length > 0) {
247
+ console.warn(
248
+ `%c WARNING!>>> AppCompQuizRecall : QuizRecallData ${wrongProperties} invalid. Required properties: ${requiredProperties} . Optional properties: ${optionalProperties}`,
249
+ 'background: orange; color: white; display: block; margin:5px;'
250
+ )
251
+ }
252
+ //Validate if all required properties are present in quizRecallData
253
+ if (missingRequired.length > 0) {
254
+ console.warn(
255
+ `%c WARNING!>>> AppCompQuizRecall : QuizRecallData missing required ${missingRequired} property. Required properties: activityId and pageId. Optional properties: title, titletag, hypertext_done and hypertext_undone.`,
256
+ 'background: orange; color: white; display: block; margin:5px;'
257
+ )
258
+ }
259
+ },
260
+ /*Get a title tag (string). Return true if the tag is valid and false is it's not*/
261
+
262
+ validateTitleTag(value) {
263
+ let tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span']
264
+ let lowerValue = value.toLowerCase()
265
+ if (tags && value && tags.find((element) => element == lowerValue))
266
+ return true
267
+ else {
268
+ console.warn(
269
+ '%c WARNING!>>> recallMixins: Your quizRecallData titletag is not valid. You can use h1,h2,h3,h4,h5,h6,p,or span',
270
+ 'background: orange; color: white; display: block; margin:5px;'
271
+ )
272
+ return false
273
+ }
274
+ }
275
+ }
276
+ }
277
+ </script>