fcad-core-dragon 2.1.0-beta.1 → 2.1.0-beta.3
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 +40 -0
- package/package.json +30 -31
- package/src/components/AppBase.vue +167 -39
- package/src/components/AppBaseButton.test.js +0 -1
- package/src/components/AppBaseModule.vue +103 -116
- package/src/components/AppBasePage.vue +13 -13
- package/src/components/AppCompInputCheckBoxNx.vue +1 -1
- package/src/components/AppCompInputRadioNx.vue +1 -1
- package/src/components/AppCompMenu.vue +2 -1
- package/src/components/AppCompPlayBarNext.vue +157 -16
- package/src/components/AppCompPopUpNext.vue +3 -3
- package/src/components/AppCompQuizNext.vue +1 -1
- package/src/components/AppCompQuizRecall.vue +2 -3
- package/src/components/AppCompTableOfContent.vue +1 -1
- package/src/components/tests__/useTimer.spec.js +91 -0
- package/src/composables/useIdleDetector.js +56 -0
- package/src/composables/useQuiz.js +1 -1
- package/src/composables/useTimer.js +175 -0
- package/src/main.js +2 -0
- package/src/module/stores/appStore.js +10 -34
- package/src/module/xapi/ADL.js +1 -0
- package/src/plugins/analytics.js +34 -0
- package/src/plugins/i18n.js +9 -27
- package/src/router/index.js +6 -3
- package/src/components/AppCompPlayBarProgress.vue +0 -82
- package/src/mixins/$mediaMixins.js +0 -809
- package/src/mixins/timerMixin.js +0 -195
- package/src/module/xapi/wrapper copy.js +0 -1963
package/CHANGELOG
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
|
+
2.1.0-beta.3(24 septembre 2025)
|
|
2
|
+
RUPTURE DE COMPATIBILITÉ : Ajout de Google Analytics (analytics_id dans app.vue) nouvelle dépendance : npm install vue-gtag@2.0.1
|
|
3
|
+
|
|
4
|
+
Tracking Analytics des événements play/end pour les lecteurs audio/vidéo
|
|
5
|
+
|
|
6
|
+
RUPTURE DE COMPATIBILITÉ : Nouvelle structure de données utilisées pour les lecteurs audio/vidéo
|
|
7
|
+
|
|
8
|
+
Correction du bogue de scroll au changement de page
|
|
9
|
+
|
|
10
|
+
Optimisation de la mise à jour de la valeur de currentPage dans le Store
|
|
11
|
+
|
|
12
|
+
Nouvelle fonctionnalité : Il est maintenant possible de redéfinir certains libellés spécifiques du FCAD (FIAG)
|
|
13
|
+
|
|
14
|
+
Tracking learninglocker pour les lecteurs audio/vidéo
|
|
15
|
+
|
|
16
|
+
Refactoring de la fonctionnalité "idle detector" et du calcul du temps passé sur une activité
|
|
17
|
+
|
|
18
|
+
Ajout d’informations supplémentaires affichées quand on active le debug mode
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
2.1.0-beta.1(27 août 2025)
|
|
2
22
|
RUPTURE DE COMPATIBILITÉ : (structure de données des rétroactions). Seules les propriétés "title" et "hypertext" sont acceptées (anciennement "title", "hypertext_1" et "hypertext_2")
|
|
3
23
|
|
|
24
|
+
Ajout de Style pour le responsive de la carte audio
|
|
25
|
+
|
|
4
26
|
Retrait du border sur les balises fieldset
|
|
5
27
|
|
|
6
28
|
Correction des styles texte à trous
|
|
7
29
|
|
|
30
|
+
Liste des changement de classe :
|
|
31
|
+
|
|
32
|
+
container-quiz-radio => container_quiz_choix_unique
|
|
33
|
+
container-quiz-checkbox => container_quiz_choix_mul
|
|
34
|
+
container-quiz-texte-tableau => container_quiz_texte_tableau
|
|
35
|
+
container-quiz-text-area => container_quiz_reponse_ouverte
|
|
36
|
+
container-quiz-dropdown => container_quiz_dropdown
|
|
37
|
+
container-quiz-texte-troue-select => container_quiz_texte_troue_select
|
|
38
|
+
container-quiz-texte-troue => container_quiz_texte_troue
|
|
39
|
+
|
|
40
|
+
Pour les rétros la classe dynamic est sur le container
|
|
41
|
+
retro_inline_positive => retro_inline_retro_positive
|
|
42
|
+
retro_inline_negative => retro_inline_retro_negative
|
|
43
|
+
retro_inline_neutre => retro_inline_retro_neutre
|
|
44
|
+
reponseSelectionner => answerSlct
|
|
45
|
+
wrong_answer => badAnswer
|
|
46
|
+
correct_answer => goodAnswer
|
|
47
|
+
|
|
8
48
|
2.0.3(27 août 2025)
|
|
9
49
|
HOTFIX Renommer app-comp-quiz en app-comp-quiz-next pour assurer la compatibilité avec les projets existants
|
|
10
50
|
|
package/package.json
CHANGED
|
@@ -1,59 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fcad-core-dragon",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.js",
|
|
7
|
-
"config": {
|
|
8
|
-
"projname": ""
|
|
9
|
-
},
|
|
10
7
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"lintfix": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
|
13
|
-
"lintreport": "eslint src --ext .js,.vue --ignore-path .gitignore",
|
|
14
|
-
"vue": "vue",
|
|
8
|
+
"build": "vite build",
|
|
15
9
|
"dev": "vite",
|
|
10
|
+
"docs:build": "vitepress build documentation",
|
|
11
|
+
"docs:dev": "vitepress dev documentation",
|
|
12
|
+
"docs:preview": "vitepress preview documentation",
|
|
16
13
|
"format": "prettier --write src/",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
14
|
+
"lintfix": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
|
15
|
+
"lintreport": "eslint src --ext .js,.vue --ignore-path .gitignore",
|
|
19
16
|
"preview": "vite preview",
|
|
17
|
+
"reset": "rm -rf ./node_modules package-lock.json .cache dist && npm i && npm run watch",
|
|
20
18
|
"test:unit": "vitest",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
19
|
+
"vue": "vue",
|
|
20
|
+
"watch": "nodemon -e js,vue,html,json -x yalc publish --push"
|
|
21
|
+
},
|
|
22
|
+
"config": {
|
|
23
|
+
"projname": ""
|
|
24
24
|
},
|
|
25
|
+
"browserslist": [
|
|
26
|
+
"> 1%",
|
|
27
|
+
"last 2 versions",
|
|
28
|
+
"not dead"
|
|
29
|
+
],
|
|
25
30
|
"dependencies": {
|
|
26
31
|
"axios": "^1.6.8",
|
|
27
32
|
"gsap": "^3.12.5",
|
|
28
33
|
"idb": "^8.0.0",
|
|
29
34
|
"mobile-detect": "^1.4.5",
|
|
30
|
-
"pinia": "^
|
|
35
|
+
"pinia": "^3.0.3",
|
|
31
36
|
"tiny-emitter": "^2.1.0",
|
|
32
37
|
"vue-safe-teleport": "^0.1.2"
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
|
-
"@vitejs/plugin-vue": "^
|
|
36
|
-
"@vue/eslint-config-prettier": "^
|
|
37
|
-
"@vue/test-utils": "^2.4.
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-
|
|
40
|
-
"eslint-plugin-
|
|
41
|
-
"eslint-plugin-prettier": "^5.1.3",
|
|
42
|
-
"eslint-plugin-vue": "^9.26.0",
|
|
40
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
41
|
+
"@vue/eslint-config-prettier": "^10.2.0",
|
|
42
|
+
"@vue/test-utils": "^2.4.6",
|
|
43
|
+
"eslint": "^9.31.0",
|
|
44
|
+
"eslint-plugin-cypress": "^5.1.0",
|
|
45
|
+
"eslint-plugin-vue": "~10.3.0",
|
|
43
46
|
"jsdom": "^25.0.1",
|
|
44
47
|
"nodemon": "^3.1.0",
|
|
45
|
-
"prettier": "
|
|
46
|
-
"
|
|
48
|
+
"prettier": "3.6.2",
|
|
49
|
+
"sass-embedded": "^1.91.0",
|
|
50
|
+
"vite-plugin-vue-devtools": "^8.0.2",
|
|
47
51
|
"vitepress": "^1.6.3",
|
|
48
|
-
"vitest": "^2.
|
|
49
|
-
"vue": "^3.5.
|
|
52
|
+
"vitest": "^3.2.4",
|
|
53
|
+
"vue": "^3.5.18",
|
|
50
54
|
"vue-router": "^4.4.5"
|
|
51
55
|
},
|
|
52
|
-
"browserslist": [
|
|
53
|
-
"> 1%",
|
|
54
|
-
"last 2 versions",
|
|
55
|
-
"not dead"
|
|
56
|
-
],
|
|
57
56
|
"engines": {
|
|
58
57
|
"node": ">0.11.9"
|
|
59
58
|
}
|
|
@@ -36,7 +36,12 @@
|
|
|
36
36
|
</transition>
|
|
37
37
|
|
|
38
38
|
<router-view class="box" />
|
|
39
|
-
|
|
39
|
+
<div v-if="appDebugMode" class="timer">
|
|
40
|
+
<!-- <div class="timer"> -->
|
|
41
|
+
<div>Act: {{ appTimer.ISOTimeParser(activityDuration) }}</div>
|
|
42
|
+
<span> | </span>
|
|
43
|
+
<div>Les: {{ appTimer.ISOTimeParser(lessonDuration) }}</div>
|
|
44
|
+
</div>
|
|
40
45
|
<app-icons-next :extra-icons="userExtraIcons" />
|
|
41
46
|
</template>
|
|
42
47
|
|
|
@@ -61,18 +66,19 @@
|
|
|
61
66
|
<script>
|
|
62
67
|
import { mapState, mapActions } from 'pinia'
|
|
63
68
|
import { useAppStore } from '../module/stores/appStore.js'
|
|
64
|
-
import {
|
|
69
|
+
import { Timer } from '../composables/useTimer.js'
|
|
70
|
+
import { IdleDetector } from '../composables/useIdleDetector.js'
|
|
65
71
|
import { validateAppContent } from '../shared/validators'
|
|
66
|
-
import {
|
|
72
|
+
import { version as fcadVersion } from '../../package.json'
|
|
73
|
+
import { computed, h, reactive } from 'vue'
|
|
67
74
|
import mobileDetect from 'mobile-detect'
|
|
75
|
+
|
|
68
76
|
export default {
|
|
69
|
-
mixins: [timerMixin],
|
|
70
77
|
provide() {
|
|
71
78
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
activityDuration: computed(() => this.activityDuration)
|
|
79
|
+
lessonDuration: computed(() => this.lessonDuration), //exposing the lesson Duration variable to all components
|
|
80
|
+
elapsedIdleTime: computed(() => this.idleDetector.getElapsedTime()), //exposing idle detector elapse time to all components
|
|
81
|
+
appTimer: this.appTimer //exposing the app timer instance to all components
|
|
76
82
|
}
|
|
77
83
|
},
|
|
78
84
|
props: {
|
|
@@ -86,7 +92,9 @@ export default {
|
|
|
86
92
|
},
|
|
87
93
|
setup() {
|
|
88
94
|
const store = useAppStore()
|
|
89
|
-
|
|
95
|
+
const appTimer = reactive(new Timer('ativityTimer')) // Making Timer instance reactive to be able to track changes
|
|
96
|
+
const idleDetector = reactive(new IdleDetector()) // Making detector instance reactive.
|
|
97
|
+
return { store, appTimer, idleDetector }
|
|
90
98
|
},
|
|
91
99
|
|
|
92
100
|
data() {
|
|
@@ -99,7 +107,10 @@ export default {
|
|
|
99
107
|
buildInfoClicked: false,
|
|
100
108
|
error: [],
|
|
101
109
|
initialLoading: true,
|
|
102
|
-
f5KeyPressed: false
|
|
110
|
+
f5KeyPressed: false,
|
|
111
|
+
lessonDuration: 0,
|
|
112
|
+
idleTimeout: 5 * 60000, //5 mins: this is the time of inactivity before considering the user is idle
|
|
113
|
+
starterTimeout: null
|
|
103
114
|
}
|
|
104
115
|
},
|
|
105
116
|
computed: {
|
|
@@ -117,8 +128,16 @@ export default {
|
|
|
117
128
|
'getErrorMenu',
|
|
118
129
|
'getRouteHistory',
|
|
119
130
|
'getBookmarkEnabled',
|
|
131
|
+
'getAppDebugMode',
|
|
120
132
|
'getApplicationSettings'
|
|
121
133
|
]),
|
|
134
|
+
appDebugMode() {
|
|
135
|
+
return this.getAppDebugMode
|
|
136
|
+
},
|
|
137
|
+
activityDuration() {
|
|
138
|
+
return this.appTimer.getTime() // return the time duration in seconds
|
|
139
|
+
},
|
|
140
|
+
|
|
122
141
|
getwidth() {
|
|
123
142
|
return window.innerWidth
|
|
124
143
|
},
|
|
@@ -134,12 +153,9 @@ export default {
|
|
|
134
153
|
},
|
|
135
154
|
appReady() {
|
|
136
155
|
let readyState = this.getAppStatus === 'ready' ? true : false
|
|
137
|
-
if (readyState) {
|
|
138
|
-
this.setInitialLoadingDone()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
156
|
return readyState
|
|
142
157
|
},
|
|
158
|
+
|
|
143
159
|
displayLang() {
|
|
144
160
|
let lang = false
|
|
145
161
|
const displayList = ['en-US', 'fr-FR', 'es-ES'] // list of xapi verbs language display
|
|
@@ -162,6 +178,20 @@ export default {
|
|
|
162
178
|
}
|
|
163
179
|
},
|
|
164
180
|
watch: {
|
|
181
|
+
initialLoading: {
|
|
182
|
+
deep: true,
|
|
183
|
+
handler(newVal) {
|
|
184
|
+
if (newVal !== false) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
const eventParams = {
|
|
188
|
+
fcad_version: fcadVersion,
|
|
189
|
+
course_id: this.getAppConfigs.crs_id,
|
|
190
|
+
lesson_id: this.getAppConfigs.id
|
|
191
|
+
}
|
|
192
|
+
this.$analytics.sendEvent('user_meta', eventParams)
|
|
193
|
+
}
|
|
194
|
+
},
|
|
165
195
|
'store.$state.userDataLoaded': {
|
|
166
196
|
handler() {
|
|
167
197
|
!this.store.$state.userDataLoaded
|
|
@@ -169,6 +199,13 @@ export default {
|
|
|
169
199
|
: this.updateTracker('appBase', 'ready')
|
|
170
200
|
},
|
|
171
201
|
immediate: true
|
|
202
|
+
},
|
|
203
|
+
appReady: {
|
|
204
|
+
handler(newValue) {
|
|
205
|
+
if (newValue) {
|
|
206
|
+
this.setInitialLoadingDone()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
172
209
|
}
|
|
173
210
|
},
|
|
174
211
|
|
|
@@ -203,24 +240,19 @@ export default {
|
|
|
203
240
|
this.$bus.$on('fire-exit-event', this.endLesson)
|
|
204
241
|
this.$bus.$on('reset-focus-on', this.resetFocus)
|
|
205
242
|
this.$bus.$on('move-to-target', this.moveTo)
|
|
243
|
+
this.$analytics.init(this.getAppConfigs.analytics_id, this.$router)
|
|
206
244
|
this.$bus.$on('start-timer', this.startAppTimer)
|
|
245
|
+
this.$bus.$on('stop-app-timer', this.stopAppTimer)
|
|
207
246
|
this.$bus.$on('start-idle-detector', this.setIdleDetector)
|
|
208
247
|
this.$bus.$on('stop-idle-detector', this.unsetIdleDetector)
|
|
209
248
|
},
|
|
210
249
|
beforeMount() {
|
|
211
|
-
window.addEventListener('keydown', this.handleF5KeyPressed)
|
|
212
250
|
window.addEventListener(
|
|
213
251
|
'beforeunload',
|
|
214
252
|
(e) => {
|
|
253
|
+
this.executeCloseEventTriggered()
|
|
215
254
|
// e.preventDefault()
|
|
216
255
|
// e.returnValue = true
|
|
217
|
-
|
|
218
|
-
//prevent app to send/Save data when F5 is pressed to reload app
|
|
219
|
-
if (this.f5KeyPressed) return
|
|
220
|
-
|
|
221
|
-
this.executeCloseEventTriggered()
|
|
222
|
-
|
|
223
|
-
//debugger
|
|
224
256
|
},
|
|
225
257
|
true
|
|
226
258
|
)
|
|
@@ -230,6 +262,7 @@ export default {
|
|
|
230
262
|
// set the language of the app
|
|
231
263
|
this.setLocale(this.getAppConfigs.lang)
|
|
232
264
|
window.addEventListener('keydown', this.handleF5KeyPressed)
|
|
265
|
+
window.addEventListener('beforeunload', this.handleBeforeUnload)
|
|
233
266
|
},
|
|
234
267
|
beforeUnmount() {
|
|
235
268
|
this.$bus.$off('set-comp-status', this.updateTracker)
|
|
@@ -241,7 +274,9 @@ export default {
|
|
|
241
274
|
this.$bus.$off('start-idle-detector', this.setIdleDetector)
|
|
242
275
|
this.$bus.$off('stop-idle-detector', this.unsetIdleDetector)
|
|
243
276
|
this.$bus.$off('start-timer', this.startAppTimer)
|
|
277
|
+
this.$bus.$off('stop-app-timer', this.stopAppTimer)
|
|
244
278
|
window.removeEventListener('keydown', this.handleF5KeyPressed)
|
|
279
|
+
window.removeEventListener('beforeunload', this.handleBeforeUnload)
|
|
245
280
|
if (this.getAppConfigs.remote && this.unsubscribeToSetConfig)
|
|
246
281
|
this.unsubscribeToSetConfig() //stop watching changing in store to save to local storage
|
|
247
282
|
},
|
|
@@ -258,30 +293,82 @@ export default {
|
|
|
258
293
|
'setMobileState',
|
|
259
294
|
'setCurrentBrowser'
|
|
260
295
|
]),
|
|
296
|
+
/**
|
|
297
|
+
* @description start the app timer instance
|
|
298
|
+
* Check if the timer is already started, if yes it add the activity duration
|
|
299
|
+
* to the lesson duration and restart the timer
|
|
300
|
+
*/
|
|
301
|
+
|
|
261
302
|
startAppTimer() {
|
|
262
|
-
if (this.
|
|
263
|
-
this.
|
|
303
|
+
if (this.$route.name === 'module') return // don't start timer when in menu
|
|
304
|
+
if (this.appTimer.getTimerState() == 'started') {
|
|
305
|
+
this.lessonDuration += this.activityDuration //
|
|
306
|
+
this.appTimer.stop() // Start the activity timer
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//Delay actually one second before starting timers
|
|
310
|
+
this.starterTimeout = setTimeout(() => {
|
|
311
|
+
this.appTimer.start() // Start the activity timer
|
|
312
|
+
this.setIdleDetector() //start the idle time detector
|
|
313
|
+
}, 500)
|
|
264
314
|
},
|
|
265
315
|
|
|
316
|
+
/**
|
|
317
|
+
* @description stop the app timer instance
|
|
318
|
+
* The function add the activity duration to the lesson duration and stop the timer
|
|
319
|
+
*/
|
|
320
|
+
stopAppTimer() {
|
|
321
|
+
this.lessonDuration += this.activityDuration
|
|
322
|
+
this.appTimer.stop() // stop timer
|
|
323
|
+
clearTimeout(this.starterTimeout)
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @description set the idle detector to start detecting idle time
|
|
328
|
+
* Attach DOM event listeners to reset the idle timer
|
|
329
|
+
* The idle detector will start after 1 second after the app is loaded
|
|
330
|
+
* The idle detector will pause the app timer when idle time is reached
|
|
331
|
+
*/
|
|
332
|
+
|
|
266
333
|
setIdleDetector() {
|
|
267
334
|
setTimeout(() => {
|
|
268
|
-
this.startIdleTimer(
|
|
335
|
+
this.idleDetector.startIdleTimer(
|
|
336
|
+
() => this.appTimer.pause(),
|
|
337
|
+
this.idleTimeout
|
|
338
|
+
)
|
|
269
339
|
document.addEventListener('mousedown', this.resetIdleTimer, false) //set eventlisteners
|
|
270
340
|
document.addEventListener('mousemove', this.resetIdleTimer, false) //set eventlisteners
|
|
271
341
|
document.addEventListener('keypress', this.resetIdleTimer, false) //set eventlisteners
|
|
272
342
|
document.addEventListener('touchmove', this.resetIdleTimer, false) //set eventlisteners
|
|
273
|
-
},
|
|
343
|
+
}, 800)
|
|
274
344
|
},
|
|
275
|
-
|
|
276
|
-
*
|
|
345
|
+
/**
|
|
346
|
+
* @description unset the idle detector
|
|
347
|
+
* stop the idle timer and remove DOM event to stop listening the idle timer
|
|
277
348
|
*/
|
|
278
349
|
unsetIdleDetector() {
|
|
279
|
-
this.stopIdleTimer()
|
|
350
|
+
this.idleDetector.stopIdleTimer()
|
|
280
351
|
document.removeEventListener('mousedown', this.resetIdleTimer, false)
|
|
281
352
|
document.removeEventListener('mousemove', this.resetIdleTimer, false)
|
|
282
353
|
document.removeEventListener('keypress', this.resetIdleTimer, false)
|
|
283
354
|
document.removeEventListener('touchmove', this.resetIdleTimer, false)
|
|
284
355
|
},
|
|
356
|
+
/**
|
|
357
|
+
* @description reset the idle timer
|
|
358
|
+
* The function stop the idle timer and restart it
|
|
359
|
+
* start also the app timer if it was paused
|
|
360
|
+
*
|
|
361
|
+
*/
|
|
362
|
+
resetIdleTimer() {
|
|
363
|
+
if (this.$route.name == 'menu' || this.$route.name == 'module') return // don't reset idle timer when in menu
|
|
364
|
+
this.idleDetector.stopIdleTimer()
|
|
365
|
+
|
|
366
|
+
if (this.appTimer.getTimerState() == 'stopped') this.appTimer.start()
|
|
367
|
+
this.idleDetector.startIdleTimer(
|
|
368
|
+
() => this.appTimer.pause(),
|
|
369
|
+
this.idleTimeout
|
|
370
|
+
)
|
|
371
|
+
},
|
|
285
372
|
|
|
286
373
|
setInitialLoadingDone() {
|
|
287
374
|
this.initialLoading = false
|
|
@@ -580,21 +667,31 @@ export default {
|
|
|
580
667
|
? (d = `PT${duration.split(':')[0]}H${duration.split(':')[1]}M${
|
|
581
668
|
duration.split(':')[2]
|
|
582
669
|
}S`)
|
|
583
|
-
: (d = `PT${
|
|
584
|
-
this.
|
|
585
|
-
|
|
670
|
+
: (d = `PT${
|
|
671
|
+
this.appTimer
|
|
672
|
+
.ISOTimeParser(this.activityDuration)
|
|
673
|
+
.split(':')[0]
|
|
674
|
+
}H${
|
|
675
|
+
this.appTimer
|
|
676
|
+
.ISOTimeParser(this.activityDuration)
|
|
677
|
+
.split(':')[1]
|
|
678
|
+
}M${
|
|
679
|
+
this.appTimer
|
|
680
|
+
.ISOTimeParser(this.activityDuration)
|
|
681
|
+
.split(':')[2]
|
|
682
|
+
}S`)
|
|
586
683
|
|
|
587
684
|
stmt.result['duration'] = d
|
|
588
685
|
//Set the completion status of the result
|
|
589
686
|
if (completion) stmt.result['completion'] = completion
|
|
590
687
|
}
|
|
591
688
|
|
|
592
|
-
// ===================== Extension of the Object definition =====================//
|
|
593
689
|
if (
|
|
594
690
|
extensions &&
|
|
595
691
|
extensions.constructor === Array &&
|
|
596
692
|
extensions.length > 0
|
|
597
693
|
) {
|
|
694
|
+
// ===================== Extension of the Object definition =====================//
|
|
598
695
|
//Validate each entry given in the extension Array
|
|
599
696
|
extensions.forEach((e) => {
|
|
600
697
|
//entry must be of type Object
|
|
@@ -622,7 +719,6 @@ export default {
|
|
|
622
719
|
//============================================================
|
|
623
720
|
stmtsQueue.push(stmt)
|
|
624
721
|
})
|
|
625
|
-
|
|
626
722
|
this.$xapi._sendStatements(stmtsQueue, cb, withFetch)
|
|
627
723
|
}
|
|
628
724
|
},
|
|
@@ -681,7 +777,7 @@ export default {
|
|
|
681
777
|
}
|
|
682
778
|
}
|
|
683
779
|
],
|
|
684
|
-
duration: this.lessonDuration
|
|
780
|
+
duration: this.appTimer.ISOTimeParser(this.lessonDuration)
|
|
685
781
|
}
|
|
686
782
|
|
|
687
783
|
const endStmt = { ...stmt }
|
|
@@ -732,7 +828,8 @@ export default {
|
|
|
732
828
|
option
|
|
733
829
|
) //send xapi statement
|
|
734
830
|
|
|
735
|
-
if (this.
|
|
831
|
+
if (this.appTimer.getTimerState() === 'started')
|
|
832
|
+
setTimeout(() => this.stopAppTimer(), 0) //clear the timer
|
|
736
833
|
|
|
737
834
|
if (cb) cb()
|
|
738
835
|
},
|
|
@@ -750,7 +847,7 @@ export default {
|
|
|
750
847
|
this.setRouteHistory([]) // resetting store record for all last visited pages
|
|
751
848
|
|
|
752
849
|
// this.$bus.$emit('stop-timer')
|
|
753
|
-
this.
|
|
850
|
+
this.stopAppTimer()
|
|
754
851
|
if (this.getModuleInfo.packageType !== 'xapi') return
|
|
755
852
|
|
|
756
853
|
if (!this.getConnectionInfo || this.getConnectionInfo.remote == false)
|
|
@@ -929,7 +1026,7 @@ export default {
|
|
|
929
1026
|
if (e) e.focus()
|
|
930
1027
|
},
|
|
931
1028
|
/**
|
|
932
|
-
* @description
|
|
1029
|
+
* @description Method to validate that application settings are correctly
|
|
933
1030
|
* Opens error component when configurations are not correct
|
|
934
1031
|
*
|
|
935
1032
|
*/
|
|
@@ -981,9 +1078,24 @@ export default {
|
|
|
981
1078
|
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
982
1079
|
)
|
|
983
1080
|
},
|
|
984
|
-
|
|
1081
|
+
/**
|
|
1082
|
+
* @description Handle the keydown event to detect if F5 key was pressed
|
|
1083
|
+
* The function set the f5KeyPressed to true if F5 key was pressed
|
|
1084
|
+
* @param {Event} e - keydown event
|
|
1085
|
+
*/
|
|
985
1086
|
handleF5KeyPressed(e) {
|
|
986
1087
|
this.f5KeyPressed = e.code == 'F5'
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* @description Handle the beforeunload event to save data when the user close the app or reload the app
|
|
1092
|
+
* The function check if the F5 key was pressed to avoid sending data when the user reload the app
|
|
1093
|
+
*/
|
|
1094
|
+
handleBeforeUnload() {
|
|
1095
|
+
//prevent app to send/Save data when F5 is pressed to reload app
|
|
1096
|
+
if (this.f5KeyPressed) return
|
|
1097
|
+
|
|
1098
|
+
this.executeCloseEventTriggered()
|
|
987
1099
|
}
|
|
988
1100
|
}
|
|
989
1101
|
}
|
|
@@ -1039,4 +1151,20 @@ export default {
|
|
|
1039
1151
|
transform: rotate(360deg);
|
|
1040
1152
|
}
|
|
1041
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
.timer {
|
|
1156
|
+
position: sticky; /* Sticks inside the parent container */
|
|
1157
|
+
bottom: 90%; /* Sticks to the top edge of the parent */
|
|
1158
|
+
left: 100%; /* Sticks to the left edge of the parent */
|
|
1159
|
+
display: flex;
|
|
1160
|
+
align-items: center;
|
|
1161
|
+
justify-content: flex-start;
|
|
1162
|
+
font-size: 1em;
|
|
1163
|
+
width: fit-content;
|
|
1164
|
+
color: #333;
|
|
1165
|
+
opacity: 0.9;
|
|
1166
|
+
background-color: #eaabb6b3; /* Optional: ensures readability when content scrolls */
|
|
1167
|
+
padding: 5px 12px; /* Some breathing space */
|
|
1168
|
+
z-index: 1000; /* Keeps it above other elements */
|
|
1169
|
+
}
|
|
1042
1170
|
</style>
|