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.
- package/{.eslintrc.js → .eslintrc.cjs} +13 -18
- package/README.md +1 -1
- package/bk.scss +117 -0
- package/package.json +22 -40
- package/src/$locales/en.json +57 -19
- package/src/$locales/fr.json +66 -28
- package/src/components/AppBase.vue +790 -376
- package/src/components/AppBaseButton.vue +33 -5
- package/src/components/AppBaseErrorDisplay.vue +62 -25
- package/src/components/AppBaseModule.vue +831 -754
- package/src/components/AppBasePage.vue +60 -74
- package/src/components/AppCompAudio.vue +266 -0
- package/src/components/AppCompBranchButtons.vue +79 -89
- package/src/components/AppCompButtonProgress.vue +35 -61
- package/src/components/AppCompCarousel.vue +160 -249
- package/src/components/AppCompInputCheckBox.vue +9 -3
- package/src/components/AppCompInputDropdown.vue +2 -4
- package/src/components/AppCompInputRadio.vue +8 -15
- package/src/components/AppCompInputTextTable.vue +15 -12
- package/src/components/AppCompInputTextToFillDropdown.vue +16 -14
- package/src/components/AppCompInputTextToFillText.vue +2 -2
- package/src/components/AppCompJauge.vue +14 -3
- package/src/components/AppCompMenu.vue +284 -85
- package/src/components/AppCompMenuItem.vue +67 -92
- package/src/components/AppCompNavigation.vue +945 -0
- package/src/components/AppCompNoteCall.vue +141 -0
- package/src/components/AppCompNoteCredit.vue +267 -0
- package/src/components/AppCompPlayBar.vue +1122 -1391
- package/src/components/AppCompPlayBarProgress.vue +73 -0
- package/src/components/AppCompPopUp.vue +195 -135
- package/src/components/AppCompPopover.vue +27 -0
- package/src/components/AppCompQuiz.vue +90 -113
- package/src/components/AppCompQuizRecall.vue +277 -0
- package/src/components/AppCompSVG.vue +335 -0
- package/src/components/AppCompSettingsMenu.vue +7 -8
- package/src/components/AppCompTableOfContent.vue +264 -88
- package/src/components/AppCompTranscript.vue +19 -0
- package/src/components/AppCompVideoPlayer.vue +380 -0
- package/src/components/BaseModule.vue +37 -114
- package/src/main.js +130 -85
- package/src/mixins/$mediaMixins.js +827 -0
- package/src/mixins/$pageMixins.js +149 -115
- package/src/mixins/$quizMixins.js +12 -26
- package/src/mixins/timerMixin.js +39 -16
- package/src/module/store.js +218 -78
- package/src/module/xapi/ADL.js +90 -53
- package/src/module/xapi/Crypto/Hasher.js +8 -8
- package/src/module/xapi/Crypto/WordArray.js +6 -6
- package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +4 -4
- package/src/module/xapi/Crypto/algorithms/C_algo.js +14 -18
- package/src/module/xapi/Crypto/algorithms/HMAC.js +1 -1
- package/src/module/xapi/Crypto/algorithms/SHA1.js +1 -1
- package/src/module/xapi/Crypto/encoders/Base.js +7 -7
- package/src/module/xapi/Crypto/encoders/Base64.js +3 -3
- package/src/module/xapi/Crypto/encoders/Hex.js +4 -3
- package/src/module/xapi/Crypto/encoders/Latin1.js +3 -3
- package/src/module/xapi/Crypto/encoders/Utf8.js +3 -3
- package/src/module/xapi/Statement/index.js +1 -1
- package/src/module/xapi/launch.js +10 -10
- package/src/module/xapi/utils.js +17 -17
- package/src/module/xapi/wrapper.js +127 -54
- package/src/module/xapi/xapiStatement.js +29 -29
- package/src/plugins/gsap.js +4 -1
- package/src/plugins/helper.js +58 -24
- package/src/plugins/i18n.js +23 -10
- package/src/plugins/idb.js +1 -0
- package/src/plugins/scorm.js +14 -14
- package/src/public/index.html +1 -1
- package/src/router/index.js +40 -0
- package/src/router/routes.js +317 -0
- package/src/shared/generalfuncs.js +91 -9
- package/src/shared/validators.js +959 -0
- package/.prettierrc.js +0 -5
- package/babel.config.js +0 -3
- package/src/components/AppBaseDragChoice.vue +0 -91
- package/src/components/AppBaseDropZone.vue +0 -112
- package/src/components/AppCompDragAndDrop.vue +0 -339
- package/src/components/AppCompInputAssociation.vue +0 -332
- package/src/components/AppCompMediaPlayer.vue +0 -365
- package/src/components/AppCompNavigationFull.vue +0 -1791
- package/src/components/AppCompToolTip.vue +0 -94
- package/src/plugins/timeManager.js +0 -77
- package/src/routes.js +0 -734
- 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
|
-
|
|
164
|
-
|
|
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(
|
|
1235
|
-
a,
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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(
|
|
1272
|
-
a,
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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 {
|
|
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 (
|
|
2337
|
-
|
|
2338
|
-
|
|
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 =
|
|
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
|
-
|
|
2447
|
+
getUserInteraction: {
|
|
2459
2448
|
//in development environment (localhost), don't wait for the axios call
|
|
2460
|
-
immediate:
|
|
2449
|
+
// immediate: import.meta.env.DEV,
|
|
2450
|
+
immediate: true,
|
|
2461
2451
|
handler() {
|
|
2462
|
-
if (!this.
|
|
2452
|
+
if (!this.getUserInteraction) return
|
|
2463
2453
|
this.getPreviousAnswers()
|
|
2464
2454
|
}
|
|
2465
2455
|
}
|
|
2466
2456
|
},
|
|
2467
|
-
created() {
|
|
2468
|
-
|
|
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.
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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.
|
|
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.
|
|
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>
|