fcad-core-dragon 2.0.1 → 2.0.2-beta.1
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/CHANGELOG +16 -1
- package/package.json +1 -1
- package/src/$locales/en.json +18 -4
- package/src/$locales/fr.json +17 -3
- package/src/components/AppBase.vue +36 -341
- package/src/components/AppBaseModule.vue +16 -21
- package/src/components/AppBasePage.vue +45 -14
- package/src/components/AppBaseSkeleton.vue +45 -0
- package/src/components/AppCompAudio.vue +12 -3
- package/src/components/AppCompButtonProgress.vue +13 -2
- package/src/components/AppCompCarousel.vue +12 -4
- package/src/components/AppCompInputCheckBoxNx.vue +324 -0
- package/src/components/AppCompInputDropdownNx.vue +295 -0
- package/src/components/AppCompInputRadioNx.vue +264 -0
- package/src/components/AppCompInputTextNx.vue +148 -0
- package/src/components/AppCompInputTextTableNx.vue +198 -0
- package/src/components/AppCompInputTextToFillDropdownNx.vue +291 -0
- package/src/components/AppCompInputTextToFillNx.vue +277 -0
- package/src/components/AppCompJauge.vue +11 -4
- package/src/components/AppCompMenu.vue +7 -14
- package/src/components/AppCompMenuItem.vue +7 -5
- package/src/components/AppCompNavigation.vue +21 -21
- package/src/components/AppCompNoteCall.vue +1 -0
- package/src/components/AppCompNoteCredit.vue +2 -1
- package/src/components/AppCompPlayBarNext.vue +94 -41
- package/src/components/AppCompPopUpNext.vue +6 -6
- package/src/components/AppCompQuizNx.vue +500 -0
- package/src/components/AppCompQuizRecall.vue +113 -66
- package/src/components/AppCompTableOfContent.vue +39 -10
- package/src/components/AppCompVideoPlayer.vue +1 -1
- package/src/composables/useQuiz.js +62 -179
- package/src/directives/nvdaFix.js +53 -0
- package/src/main.js +227 -30
- package/src/mixins/$mediaMixins.js +1 -11
- package/src/module/stores/appStore.js +29 -11
- package/src/plugins/idb.js +1 -1
- package/src/shared/generalfuncs.js +134 -0
- package/src/shared/validators.js +308 -234
- package/src/components/AppCompInputCheckBoxNext.vue +0 -205
- package/src/components/AppCompInputDropdownNext.vue +0 -201
- package/src/components/AppCompInputRadioNext.vue +0 -158
- package/src/components/AppCompInputTextNext.vue +0 -124
- package/src/components/AppCompInputTextTableNext.vue +0 -142
- package/src/components/AppCompInputTextToFillDropdownNext.vue +0 -238
- package/src/components/AppCompInputTextToFillNext.vue +0 -171
- package/src/components/AppCompQuizNext.vue +0 -2908
|
@@ -6,42 +6,51 @@
|
|
|
6
6
|
** data object should containt following attributes: quizId,activityId,pageId,hypertext_done',hypertext_undone,title,titletag
|
|
7
7
|
-->
|
|
8
8
|
<template>
|
|
9
|
-
<section class="quizRecall">
|
|
9
|
+
<section v-if="quizRecallData" class="quizRecall">
|
|
10
10
|
<!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{{ quizRecall.title }}
|
|
17
|
-
</component>
|
|
18
|
-
<!--Quiz answer conditionning-->
|
|
19
|
-
<app-base-error-display
|
|
20
|
-
v-if="hasError.length"
|
|
21
|
-
:error-group="'component'"
|
|
22
|
-
:error-title="`ERREUR: COMPOSANT QUIZ RECALL`"
|
|
23
|
-
:errors-list="hasError"
|
|
24
|
-
></app-base-error-display>
|
|
11
|
+
<!--Show skeleton while app data is not ready-->
|
|
12
|
+
<template v-if="!isReady">
|
|
13
|
+
<app-base-skeleton :skeleton-lines="2" />
|
|
14
|
+
</template>
|
|
15
|
+
<!--Show app data when ready-->
|
|
25
16
|
<template v-else>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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>
|
|
45
54
|
</template>
|
|
46
55
|
</template>
|
|
47
56
|
</section>
|
|
@@ -51,6 +60,7 @@
|
|
|
51
60
|
//Recall mixins has the necessary datas and functions to give back quizRecall statu
|
|
52
61
|
import { mapState } from 'pinia'
|
|
53
62
|
import { useAppStore } from '../module/stores/appStore'
|
|
63
|
+
import { nextTick } from 'vue'
|
|
54
64
|
|
|
55
65
|
export default {
|
|
56
66
|
name: 'AppCompQuizRecall',
|
|
@@ -71,7 +81,8 @@ export default {
|
|
|
71
81
|
)}</p>` /*String traduite par défaut*/
|
|
72
82
|
},
|
|
73
83
|
hasError: [],
|
|
74
|
-
quizData: null
|
|
84
|
+
quizData: null,
|
|
85
|
+
isReady: false //Used to display skeleton while app data is not ready
|
|
75
86
|
}
|
|
76
87
|
},
|
|
77
88
|
computed: {
|
|
@@ -79,25 +90,46 @@ export default {
|
|
|
79
90
|
'getAllCompleted',
|
|
80
91
|
'getUserInteraction',
|
|
81
92
|
'getPageInteraction',
|
|
93
|
+
'getAppStatus',
|
|
82
94
|
'getPageData'
|
|
83
95
|
])
|
|
84
96
|
},
|
|
97
|
+
|
|
98
|
+
watch: {
|
|
99
|
+
//Watch for user interaction change to get quizRecall answer
|
|
100
|
+
getUserInteraction: {
|
|
101
|
+
async handler(newValue) {
|
|
102
|
+
if (!this.getUserInteraction) return
|
|
103
|
+
|
|
104
|
+
const { activityId, pageId } = this.quizRecallData
|
|
105
|
+
const interaction = this.getPageInteraction(
|
|
106
|
+
activityId,
|
|
107
|
+
pageId
|
|
108
|
+
).userInteraction
|
|
109
|
+
|
|
110
|
+
if (!interaction) return
|
|
111
|
+
//Get quizRecall answer
|
|
112
|
+
if (this.quizRecallData && this.quizData) {
|
|
113
|
+
await this.getQuizRecallAnswer(interaction)
|
|
114
|
+
} else {
|
|
115
|
+
this.quizRecall.done == false
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
immediate: true,
|
|
119
|
+
deep: true
|
|
120
|
+
}
|
|
121
|
+
},
|
|
85
122
|
created() {
|
|
86
123
|
//Validate quizRecall data
|
|
87
124
|
//(no validation to the quiz, only to the new datas)
|
|
88
|
-
|
|
125
|
+
if (import.meta.env.DEV) {
|
|
126
|
+
this.validateQuizRecallData(this.quizRecallData)
|
|
127
|
+
}
|
|
89
128
|
const { activityId, pageId } = this.quizRecallData
|
|
90
129
|
//Get quizData and validate the quiz type
|
|
91
130
|
if (activityId && pageId) {
|
|
92
131
|
this.getQuizData(activityId, pageId)
|
|
93
132
|
}
|
|
94
|
-
|
|
95
|
-
//Get quizRecall answer
|
|
96
|
-
if (this.quizRecallData && this.quizData) {
|
|
97
|
-
this.getQuizRecallAnswer(this.quizRecallData, this.quizData)
|
|
98
|
-
} else {
|
|
99
|
-
this.quizRecall.done == false
|
|
100
|
-
}
|
|
101
133
|
},
|
|
102
134
|
methods: {
|
|
103
135
|
/**
|
|
@@ -165,19 +197,10 @@ export default {
|
|
|
165
197
|
}
|
|
166
198
|
},
|
|
167
199
|
//Get datas from quizRecallData and userData and add them to quizRecall object
|
|
168
|
-
getQuizRecallAnswer() {
|
|
169
|
-
//
|
|
170
|
-
const {
|
|
171
|
-
|
|
172
|
-
pageId,
|
|
173
|
-
quizId,
|
|
174
|
-
hypertext_done,
|
|
175
|
-
hypertext_undone,
|
|
176
|
-
title,
|
|
177
|
-
titletag
|
|
178
|
-
} = this.quizRecallData
|
|
179
|
-
|
|
180
|
-
let userData = this.getPageInteraction(activityId, pageId)
|
|
200
|
+
async getQuizRecallAnswer(userData) {
|
|
201
|
+
await nextTick() //wait for the DOM to update
|
|
202
|
+
const { quizId, hypertext_done, hypertext_undone, title, titletag } =
|
|
203
|
+
this.quizRecallData
|
|
181
204
|
|
|
182
205
|
//Add hypertext_done and undone to quizRecall
|
|
183
206
|
if (hypertext_done) {
|
|
@@ -197,19 +220,21 @@ export default {
|
|
|
197
220
|
}
|
|
198
221
|
}
|
|
199
222
|
}
|
|
223
|
+
//Get the quiz answers from userData
|
|
224
|
+
const answers = userData.quizAnswers || {}
|
|
225
|
+
let quizAnswer = answers[quizId] || null
|
|
200
226
|
|
|
201
227
|
//If quiz answer exists in userData, quizRecall done is true and add the quiz answer to quizRecall
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const { quizAnswers } = userData.userInteraction
|
|
228
|
+
quizAnswer
|
|
229
|
+
? (this.quizRecall.done = true)
|
|
230
|
+
: (this.quizRecall.done = false)
|
|
206
231
|
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
this.quizRecall
|
|
212
|
-
this.
|
|
232
|
+
if (quizAnswer) {
|
|
233
|
+
this.quizRecall.answer = quizAnswer.value[0].filled || ''
|
|
234
|
+
this.quizRecall.ennonce = this.quizData.ennonce
|
|
235
|
+
}
|
|
236
|
+
this.quizRecall
|
|
237
|
+
this.isReady = true //Set isReady to true to display the component
|
|
213
238
|
},
|
|
214
239
|
|
|
215
240
|
//Validate quizRecallData
|
|
@@ -296,3 +321,25 @@ export default {
|
|
|
296
321
|
}
|
|
297
322
|
}
|
|
298
323
|
</script>
|
|
324
|
+
|
|
325
|
+
<style>
|
|
326
|
+
.skeleton {
|
|
327
|
+
height: 20px;
|
|
328
|
+
width: 200px;
|
|
329
|
+
background-color: #ccc;
|
|
330
|
+
border-radius: 4px;
|
|
331
|
+
animation: pulse 1.5s infinite ease-in-out;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@keyframes pulse {
|
|
335
|
+
0% {
|
|
336
|
+
opacity: 1;
|
|
337
|
+
}
|
|
338
|
+
50% {
|
|
339
|
+
opacity: 0.4;
|
|
340
|
+
}
|
|
341
|
+
100% {
|
|
342
|
+
opacity: 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
</style>
|
|
@@ -11,19 +11,21 @@
|
|
|
11
11
|
<div v-if="error" id="sidebar-submenu" :class="{ isOpen: isOpened }">
|
|
12
12
|
<focus-trap :active="isOpened">
|
|
13
13
|
<div ref="target">
|
|
14
|
+
<!-- <div class="submenu-header"> -->
|
|
14
15
|
<app-base-button
|
|
15
16
|
id="close-toc"
|
|
16
17
|
class="btn-ghost"
|
|
17
18
|
:title="$t('button.closePopUp')"
|
|
18
19
|
@click="onCloseWidget('toc')"
|
|
19
20
|
>
|
|
20
|
-
<svg>
|
|
21
|
+
<svg aria-hidden="true" focusable="false">
|
|
21
22
|
<use href="#close-square-icon" />
|
|
22
23
|
</svg>
|
|
23
24
|
</app-base-button>
|
|
25
|
+
<!-- </div> -->
|
|
24
26
|
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
+
<!-- <div class="submenu-content"> -->
|
|
28
|
+
<p class="t-act" v-html="`${title}`"></p>
|
|
27
29
|
<div class="box-prog-act">
|
|
28
30
|
<p>
|
|
29
31
|
{{ $t('text.activity_progress') }}
|
|
@@ -46,19 +48,20 @@
|
|
|
46
48
|
<p class="anchor-t" v-html="anchor.title"></p>
|
|
47
49
|
<p class="state">
|
|
48
50
|
<span class="box-text">
|
|
49
|
-
<svg>
|
|
51
|
+
<svg aria-hidden="true" focusable="false">
|
|
50
52
|
<use href="#check-toc" />
|
|
51
53
|
</svg>
|
|
52
54
|
{{ $t('text.complete') }}
|
|
53
55
|
</span>
|
|
54
56
|
</p>
|
|
55
57
|
</div>
|
|
56
|
-
<svg>
|
|
58
|
+
<svg aria-hidden="true" focusable="false">
|
|
57
59
|
<use href="#chevronD-icon" />
|
|
58
60
|
</svg>
|
|
59
61
|
</div>
|
|
60
62
|
</RouterLink>
|
|
61
63
|
</div>
|
|
64
|
+
<!-- </div> -->
|
|
62
65
|
</div>
|
|
63
66
|
</focus-trap>
|
|
64
67
|
</div>
|
|
@@ -147,8 +150,25 @@ export default {
|
|
|
147
150
|
if (activity.charAt(1) == '0' || activity.charAt(1) == 0)
|
|
148
151
|
nb = activity.substr(2)
|
|
149
152
|
else nb = activity.substr(1)
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
//Format title in the Progress info
|
|
154
|
+
switch (nb) {
|
|
155
|
+
case '0':
|
|
156
|
+
this.title = menuInfo[activity].subTitle
|
|
157
|
+
? `Introduction : ${menuInfo[activity].subTitle}`
|
|
158
|
+
: `Introduction`
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
case '99':
|
|
162
|
+
this.title = menuInfo[activity].subTitle
|
|
163
|
+
? `Conclusion : ${menuInfo[activity].subTitle}`
|
|
164
|
+
: `Conclusion`
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
default:
|
|
168
|
+
this.title = menuInfo[activity].subTitle
|
|
169
|
+
? `${this.$t('text.activity')} ${nb} : ${menuInfo[activity].subTitle}`
|
|
170
|
+
: `${this.$t('text.activity')} ${nb}`
|
|
171
|
+
}
|
|
152
172
|
|
|
153
173
|
//create anchors title and path
|
|
154
174
|
if (menuInfo[activity] && menuInfo[activity].anchors) {
|
|
@@ -308,14 +328,19 @@ export default {
|
|
|
308
328
|
transition: left 0.5s ease-in-out;
|
|
309
329
|
|
|
310
330
|
&.isOpen {
|
|
311
|
-
display:
|
|
331
|
+
display: flex;
|
|
332
|
+
flex-direction: column;
|
|
312
333
|
}
|
|
313
334
|
|
|
335
|
+
// .sidebar-submenu-content {
|
|
336
|
+
// display: flex;
|
|
337
|
+
// flex-direction: column;
|
|
338
|
+
// }
|
|
314
339
|
#close-toc {
|
|
315
340
|
position: absolute;
|
|
316
341
|
right: 24px;
|
|
317
342
|
margin-bottom: 0;
|
|
318
|
-
padding:
|
|
343
|
+
padding: 12px;
|
|
319
344
|
width: 48px;
|
|
320
345
|
height: 48px;
|
|
321
346
|
display: flex;
|
|
@@ -374,7 +399,11 @@ export default {
|
|
|
374
399
|
}
|
|
375
400
|
&[done='true'] {
|
|
376
401
|
.state {
|
|
377
|
-
display:
|
|
402
|
+
display: flex;
|
|
403
|
+
align-items: center;
|
|
404
|
+
svg {
|
|
405
|
+
margin-right: 10px;
|
|
406
|
+
}
|
|
378
407
|
}
|
|
379
408
|
}
|
|
380
409
|
|
|
@@ -111,7 +111,7 @@ export default {
|
|
|
111
111
|
...mapState(useAppStore, ['getCurrentBrowser', 'getCurrentPage']),
|
|
112
112
|
//Return true if the video is loading (to show the loading display)
|
|
113
113
|
isLoading() {
|
|
114
|
-
if (!this.isSet
|
|
114
|
+
if (!this.isSet) return true
|
|
115
115
|
else return false
|
|
116
116
|
},
|
|
117
117
|
|
|
@@ -2,185 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import i18n from '@/i18n' //Must import directly the local from project app because vue-18in does not work in legacy mode with composable
|
|
4
4
|
|
|
5
|
-
export function useQuiz(
|
|
6
|
-
const { quizType, solution, showSolution, quizInputType } = quiz
|
|
5
|
+
export function useQuiz() {
|
|
7
6
|
const { t } = i18n.global
|
|
8
|
-
|
|
9
|
-
// /**
|
|
10
|
-
// * @param {String} inputId
|
|
11
|
-
// * @returns {Array} the class
|
|
12
|
-
// */
|
|
13
|
-
function classInput(inputId, optSelected = null) {
|
|
14
|
-
let theClass = []
|
|
15
|
-
|
|
16
|
-
switch (true) {
|
|
17
|
-
case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
|
|
18
|
-
if (inputId == quizInputType.value) {
|
|
19
|
-
theClass.push('reponseSelectionner')
|
|
20
|
-
}
|
|
21
|
-
break
|
|
22
|
-
|
|
23
|
-
case quizType.value == 'choix_mult':
|
|
24
|
-
if (containsValue(quizInputType.value, inputId)) {
|
|
25
|
-
theClass.push('reponseSelectionner')
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (showSolution.value) {
|
|
30
|
-
if (solution.value !== null) {
|
|
31
|
-
switch (true) {
|
|
32
|
-
case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
|
|
33
|
-
if (inputId == solution.value) {
|
|
34
|
-
theClass.push('correct_answer')
|
|
35
|
-
} else {
|
|
36
|
-
theClass.push('wrong_answer')
|
|
37
|
-
}
|
|
38
|
-
break
|
|
39
|
-
|
|
40
|
-
case quizType.value == 'choix_mult':
|
|
41
|
-
if (containsValue(solution.value, inputId)) {
|
|
42
|
-
theClass.push('correct_answer')
|
|
43
|
-
} else {
|
|
44
|
-
theClass.push('wrong_answer')
|
|
45
|
-
}
|
|
46
|
-
break
|
|
47
|
-
case quizType.value == 'dropdown':
|
|
48
|
-
if (!optSelected || !optSelected[inputId]) return
|
|
49
|
-
if (solution.value[inputId] == optSelected[inputId]) {
|
|
50
|
-
theClass.push('correct_answer')
|
|
51
|
-
} else {
|
|
52
|
-
theClass.push('wrong_answer')
|
|
53
|
-
}
|
|
54
|
-
break
|
|
55
|
-
|
|
56
|
-
case quizType.value == 'texte_troue_select':
|
|
57
|
-
if (typeof optSelected[inputId] !== 'undefined') {
|
|
58
|
-
if (
|
|
59
|
-
Object.values(solution.value[inputId])[0] ==
|
|
60
|
-
optSelected[inputId]
|
|
61
|
-
) {
|
|
62
|
-
theClass.push('correct_answer')
|
|
63
|
-
} else {
|
|
64
|
-
theClass.push('wrong_answer')
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
break
|
|
68
|
-
|
|
69
|
-
case quizType.value == 'texte_tableau':
|
|
70
|
-
if (
|
|
71
|
-
containsValue(
|
|
72
|
-
solution.value[inputId].reponse_value,
|
|
73
|
-
optSelected[inputId]
|
|
74
|
-
)
|
|
75
|
-
) {
|
|
76
|
-
theClass.push('correct_answer')
|
|
77
|
-
} else {
|
|
78
|
-
theClass.push('wrong_answer')
|
|
79
|
-
}
|
|
80
|
-
break
|
|
81
|
-
case quizType.value == 'texte_troue':
|
|
82
|
-
if (typeof optSelected[inputId] !== 'undefined') {
|
|
83
|
-
if (
|
|
84
|
-
containsValue(
|
|
85
|
-
Object.values(solution.value[inputId])[0],
|
|
86
|
-
optSelected[inputId]
|
|
87
|
-
)
|
|
88
|
-
) {
|
|
89
|
-
theClass.push('correct_answer')
|
|
90
|
-
} else {
|
|
91
|
-
theClass.push('wrong_answer')
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
break
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return theClass
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function messageAccessibility(inputId, optSelected = null) {
|
|
102
|
-
let mess = ''
|
|
103
|
-
if (showSolution.value) {
|
|
104
|
-
if (solution.value !== null) {
|
|
105
|
-
switch (true) {
|
|
106
|
-
case ['choix_unique', 'reponse_ouverte'].includes(quizType.value):
|
|
107
|
-
if (inputId == solution.value) {
|
|
108
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
109
|
-
} else {
|
|
110
|
-
mess = `${t('quizState.badAnswer')}`
|
|
111
|
-
}
|
|
112
|
-
break
|
|
113
|
-
|
|
114
|
-
case quizType.value == 'choix_mult':
|
|
115
|
-
if (containsValue(solution.value, inputId)) {
|
|
116
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
117
|
-
} else {
|
|
118
|
-
mess = `${t('quizState.badAnswer')}`
|
|
119
|
-
}
|
|
120
|
-
break
|
|
121
|
-
case quizType.value == 'dropdown':
|
|
122
|
-
if (solution[inputId.value] == optSelected[inputId.value]) {
|
|
123
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
124
|
-
} else {
|
|
125
|
-
mess = `${t('quizState.badAnswer')}`
|
|
126
|
-
}
|
|
127
|
-
break
|
|
128
|
-
|
|
129
|
-
case quizType.value == 'texte_troue_select':
|
|
130
|
-
if (typeof optSelected[inputId] !== 'undefined') {
|
|
131
|
-
if (
|
|
132
|
-
Object.values(solution.value[inputId])[0] ==
|
|
133
|
-
optSelected[inputId]
|
|
134
|
-
) {
|
|
135
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
136
|
-
} else {
|
|
137
|
-
mess = `${t('quizState.badAnswer')}`
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
break
|
|
141
|
-
|
|
142
|
-
case quizType.value == 'texte_tableau':
|
|
143
|
-
if (
|
|
144
|
-
containsValue(
|
|
145
|
-
solution.value[inputId].reponse_value,
|
|
146
|
-
optSelected[inputId]
|
|
147
|
-
)
|
|
148
|
-
) {
|
|
149
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
150
|
-
} else {
|
|
151
|
-
mess = `${t('quizState.badAnswer')}`
|
|
152
|
-
}
|
|
153
|
-
break
|
|
154
|
-
case quizType.value == 'texte_troue':
|
|
155
|
-
if (typeof optSelected[inputId] !== 'undefined') {
|
|
156
|
-
if (
|
|
157
|
-
containsValue(
|
|
158
|
-
Object.values(solution.value[inputId])[0],
|
|
159
|
-
optSelected[inputId]
|
|
160
|
-
)
|
|
161
|
-
) {
|
|
162
|
-
mess = `${t('quizState.goodAnswer')}`
|
|
163
|
-
} else {
|
|
164
|
-
mess = `${t('quizState.badAnswer')}`
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
break
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return mess
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* @description check if a values exists in a array
|
|
176
|
-
* @param {Array} array
|
|
177
|
-
* @param value
|
|
178
|
-
* @returns {Boolean}
|
|
179
|
-
*/
|
|
180
|
-
function containsValue(array, value) {
|
|
181
|
-
return array.includes(value)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
7
|
/**
|
|
185
8
|
* @description shuffles an array used to randomized the option order if shuffleAnswers is true
|
|
186
9
|
* @param {Array} array
|
|
@@ -201,6 +24,66 @@ export function useQuiz(quiz) {
|
|
|
201
24
|
}
|
|
202
25
|
return newArray2
|
|
203
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* @description add retro style to the quiz
|
|
29
|
+
* @param {Object} solution - the solution of the quiz
|
|
30
|
+
* @param {Array} reponse - the user response
|
|
31
|
+
* @param {Number} length - the length of the input value
|
|
32
|
+
* @returns {Object|Boolean}
|
|
33
|
+
*/
|
|
34
|
+
function addRetroStyle(solution, reponse, length) {
|
|
35
|
+
let classRetro = []
|
|
36
|
+
let mesA11y = []
|
|
37
|
+
if (solution != null) {
|
|
38
|
+
reponse.forEach((el) => {
|
|
39
|
+
if (el.correct == true) {
|
|
40
|
+
classRetro.push('goodAnswer')
|
|
41
|
+
mesA11y.push(`${t('quizState.goodAnswer')}`)
|
|
42
|
+
} else {
|
|
43
|
+
classRetro.push('badAnswer')
|
|
44
|
+
mesA11y.push(`${t('quizState.badAnswer')}`)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
classRetro.push('NeutralAnswer')
|
|
49
|
+
mesA11y.push(`${t('quizState.neutralAnswer')}`)
|
|
50
|
+
}
|
|
204
51
|
|
|
205
|
-
|
|
52
|
+
return { classRetro, mesA11y }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @description reset the list of validation style of a quiz
|
|
57
|
+
* @param {Array} aList - a list of array to reset
|
|
58
|
+
* @emits hide-retro - event message to hide the retro Message
|
|
59
|
+
* $el- the target quiz,
|
|
60
|
+
* false|true of th event
|
|
61
|
+
* */
|
|
62
|
+
function resetRetroStyle(aList) {
|
|
63
|
+
aList.forEach((a) => {
|
|
64
|
+
if (a && a.length > 0) a.splice(0)
|
|
65
|
+
})
|
|
66
|
+
this.$bus.$emit('hide-retro', this.$el, false)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function retroType(solution, reponse) {
|
|
70
|
+
if (solution != null) {
|
|
71
|
+
if (reponse.length == solution.length) {
|
|
72
|
+
let Ag = (i) => i.correct
|
|
73
|
+
if (reponse.every(Ag)) return 'retro_positive'
|
|
74
|
+
else return 'retro_negative'
|
|
75
|
+
} else {
|
|
76
|
+
return 'retro_negative'
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
return `retro_neutre`
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
addRetroStyle,
|
|
85
|
+
resetRetroStyle,
|
|
86
|
+
retroType,
|
|
87
|
+
shuffleArray
|
|
88
|
+
}
|
|
206
89
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file nvdaFix.js
|
|
3
|
+
* @description Directive to fix NVDA screen reader issues with Vuetify dropdowns.
|
|
4
|
+
* This directive hides the input field from screen readers and provides an aria-live region
|
|
5
|
+
* to announce the selected option.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <v-select v-nvda-fix="selectedOptionText" ...>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
mounted(el, binding) {
|
|
13
|
+
// 1. Hide Vuetify input by adding aria-hidden="true"
|
|
14
|
+
const input = el.querySelector('input[type="text"]')
|
|
15
|
+
if (input) {
|
|
16
|
+
input.setAttribute('aria-hidden', 'true')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Create/update aria-live for screen reader
|
|
20
|
+
const valueText =
|
|
21
|
+
binding && binding.value ? binding.value : 'choisir une option'
|
|
22
|
+
let liveRegion = el.querySelector('.nvda-live-region')
|
|
23
|
+
|
|
24
|
+
if (!liveRegion) {
|
|
25
|
+
liveRegion = document.createElement('div')
|
|
26
|
+
liveRegion.className = 'nvda-live-region sr-only'
|
|
27
|
+
liveRegion.setAttribute('aria-live', 'polite')
|
|
28
|
+
el.appendChild(liveRegion)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
liveRegion.textContent = `🍍${valueText}`
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* @description Directive updated hook to handle changes in the selected option.
|
|
35
|
+
* This updates the aria-live region with the new selected option text.
|
|
36
|
+
* @param {Object} el - The element the directive is bound to.
|
|
37
|
+
* @param {Object} binding - The binding object containing the new value.
|
|
38
|
+
*/
|
|
39
|
+
updated(el, binding) {
|
|
40
|
+
// update aria-live when selected option changes
|
|
41
|
+
const valueText = binding.value?.text || ''
|
|
42
|
+
const input = el.querySelector('input[type="text"]')
|
|
43
|
+
const liveRegion = el.querySelector('.nvda-live-region')
|
|
44
|
+
|
|
45
|
+
if (input) {
|
|
46
|
+
input.setAttribute('aria-hidden', 'true')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (liveRegion) {
|
|
50
|
+
liveRegion.textContent = `🍍${valueText}`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|