fcad-core-dragon 2.1.0 → 2.1.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 +2 -2
- package/package.json +1 -1
- package/src/components/AppBasePage.vue +866 -866
- package/src/components/AppCompMenu.vue +0 -1
- package/src/main.js +5 -5
- package/src/plugins/analytics.js +4 -4
- package/src/plugins/gsap.js +4 -2
- package/src/plugins/helper.js +4 -4
|
@@ -1,866 +1,866 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@ Description: a prototype to test refactoring of pageMixin paradigm and fonctionality
|
|
3
|
-
@ What it does: The component received a data and validate the type of page that will be created
|
|
4
|
-
The component update the store information for the type of page created and also the timeline
|
|
5
|
-
@Note: Page not created with this component will not have the data automatically traked by the application
|
|
6
|
-
-->
|
|
7
|
-
<template>
|
|
8
|
-
<div :id="pageData.id" :key="pageData.id" class="app-page" role="main">
|
|
9
|
-
<div v-if="appDebugMode" class="debug-pageInfo" :class="{ open: push }">
|
|
10
|
-
<button class="w-btn" @click="push = !push"><</button>
|
|
11
|
-
<div class="ctn">
|
|
12
|
-
Activity info: Route : {{ $route.path }}
|
|
13
|
-
<br />
|
|
14
|
-
|
|
15
|
-
Activity id : {{ getDebugModeInfo.id }}
|
|
16
|
-
<br />
|
|
17
|
-
Nombre de page : {{ getDebugModeInfo.size }}
|
|
18
|
-
<br />
|
|
19
|
-
Page complete :
|
|
20
|
-
<ul>
|
|
21
|
-
<li v-for="(value, key) in getDebugModeInfo.progression" :key="key">
|
|
22
|
-
{{ value }}
|
|
23
|
-
</li>
|
|
24
|
-
</ul>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
<slot v-if="!errorPage.length" name="content">Page</slot>
|
|
28
|
-
<app-base-error-display
|
|
29
|
-
v-else
|
|
30
|
-
:error-group="'component'"
|
|
31
|
-
:error-title="'ERREUR: CRÉATION DE LA PAGE'"
|
|
32
|
-
:errors-list="errorPage"
|
|
33
|
-
:error-text="`Vous avez une/des erreur(s) dans la création de votre PAGE. Veuillez
|
|
34
|
-
corriger les erreurs ci-dessous:`"
|
|
35
|
-
/>
|
|
36
|
-
<span
|
|
37
|
-
id="page_info"
|
|
38
|
-
ref="page_info"
|
|
39
|
-
class="sr-only"
|
|
40
|
-
aria-hidden="true"
|
|
41
|
-
></span>
|
|
42
|
-
<div id="hiddenAlertContainer" role="alert" class="sr-only"></div>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
|
|
46
|
-
<script>
|
|
47
|
-
import { computed } from 'vue'
|
|
48
|
-
import { useAppStore } from '../module/stores/appStore'
|
|
49
|
-
import { mapState, mapActions } from 'pinia'
|
|
50
|
-
|
|
51
|
-
export default {
|
|
52
|
-
provide() {
|
|
53
|
-
return { userInteraction: computed(() => this.userInteraction) }
|
|
54
|
-
},
|
|
55
|
-
props: {
|
|
56
|
-
pageData: {
|
|
57
|
-
type: Object,
|
|
58
|
-
required: true,
|
|
59
|
-
|
|
60
|
-
validator(value) {
|
|
61
|
-
let isValid = true
|
|
62
|
-
if (import.meta.env.DEV) {
|
|
63
|
-
const requiredPageKeys = ['id', 'activityRef', 'type']
|
|
64
|
-
let requiredTypeValues = [
|
|
65
|
-
'pg_normal',
|
|
66
|
-
'pg_menu',
|
|
67
|
-
'pg_animation',
|
|
68
|
-
'pg_media',
|
|
69
|
-
'pg_branch'
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
requiredPageKeys.forEach((key) => {
|
|
73
|
-
if (!Object.keys(value).includes(key)) {
|
|
74
|
-
console.warn(
|
|
75
|
-
`%c WARNING!>>> PAGE: PAGE: Missing ${key} for the page in $data `,
|
|
76
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
77
|
-
)
|
|
78
|
-
isValid = false
|
|
79
|
-
} else if (
|
|
80
|
-
key === 'type' &&
|
|
81
|
-
!requiredTypeValues.includes(value[key])
|
|
82
|
-
) {
|
|
83
|
-
let errString = `Invalid value assigment for type of the page in $data.`
|
|
84
|
-
|
|
85
|
-
console.warn(
|
|
86
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
87
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
88
|
-
)
|
|
89
|
-
isValid = false
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
return isValid
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
setup(props) {
|
|
98
|
-
const store = useAppStore()
|
|
99
|
-
const { activityRef, id: pageID, type: pageType } = props.pageData
|
|
100
|
-
|
|
101
|
-
//Getting initial existing userIntaction from store
|
|
102
|
-
const { userInteraction: previousInteraction = {} } =
|
|
103
|
-
store.getPageInteraction(activityRef, pageID)
|
|
104
|
-
|
|
105
|
-
return { store, previousInteraction, pageID, pageType }
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
data() {
|
|
109
|
-
return {
|
|
110
|
-
getHash: null,
|
|
111
|
-
//========PageMisins values ===================
|
|
112
|
-
userInteraction: this.previousInteraction,
|
|
113
|
-
state: null,
|
|
114
|
-
anchorEnable: true,
|
|
115
|
-
anchorInfo: null,
|
|
116
|
-
drModeActive: null,
|
|
117
|
-
error: null,
|
|
118
|
-
id: this.pageID,
|
|
119
|
-
type: this.pageType,
|
|
120
|
-
push: false
|
|
121
|
-
|
|
122
|
-
//======== End PageMisins values ===================
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
computed: {
|
|
126
|
-
...mapState(useAppStore, [
|
|
127
|
-
'getDataNoteCredit',
|
|
128
|
-
'getAllActivities',
|
|
129
|
-
'getApplicationSettings',
|
|
130
|
-
'getAppDebugMode'
|
|
131
|
-
]),
|
|
132
|
-
|
|
133
|
-
appDebugMode() {
|
|
134
|
-
return this.getAppDebugMode
|
|
135
|
-
},
|
|
136
|
-
getRouteHistory() {
|
|
137
|
-
return this.store.getRouteHistory
|
|
138
|
-
},
|
|
139
|
-
getAllActivitiesState() {
|
|
140
|
-
return this.store.getAllActivitiesState
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
getAllCompleted() {
|
|
144
|
-
return this.store.getAllCompleted
|
|
145
|
-
},
|
|
146
|
-
getErrorChoiceDetect() {
|
|
147
|
-
return this.store.getErrorChoiceDetect
|
|
148
|
-
},
|
|
149
|
-
getUserInteraction() {
|
|
150
|
-
return this.store.getUserInteraction
|
|
151
|
-
},
|
|
152
|
-
getPageInteraction() {
|
|
153
|
-
return this.store.getPageInteraction
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
getCurrentPage() {
|
|
157
|
-
return this.store.getCurrentPage
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
getCurrentBranching() {
|
|
161
|
-
return this.store.getCurrentBranching
|
|
162
|
-
},
|
|
163
|
-
updateCurrentBranching() {
|
|
164
|
-
return this.store.updateCurrentBranching
|
|
165
|
-
},
|
|
166
|
-
getModuleInfo() {
|
|
167
|
-
return this.store.getModuleInfo
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
getAllActivities() {
|
|
171
|
-
return this.store.getAllActivities()
|
|
172
|
-
},
|
|
173
|
-
getConnectionInfo() {
|
|
174
|
-
return this.store.getConnectionInfo
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
getAnchorsForActivity() {
|
|
178
|
-
return this.store.getAnchorsForActivity()
|
|
179
|
-
},
|
|
180
|
-
getBifChoice() {
|
|
181
|
-
return this.store.getBifChoice
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
isBranchingPage() {
|
|
185
|
-
return this.$route.meta.type === 'branching' && this.type !== 'pg_branch'
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
//================================================
|
|
189
|
-
settingsOptions() {
|
|
190
|
-
return this.settingsOptionsELPlus
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
settingsSelected() {
|
|
194
|
-
const setting = this.getApplicationSettings
|
|
195
|
-
return setting
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
errorPage() {
|
|
199
|
-
let err = false
|
|
200
|
-
if (import.meta.env.DEV) {
|
|
201
|
-
const requiredPageKeys = ['id', 'activityRef', 'type']
|
|
202
|
-
const errorList = []
|
|
203
|
-
let count = 0
|
|
204
|
-
let requiredTypeValues = [
|
|
205
|
-
'pg_normal',
|
|
206
|
-
'pg_menu',
|
|
207
|
-
'pg_animation',
|
|
208
|
-
'pg_media',
|
|
209
|
-
'pg_branch'
|
|
210
|
-
]
|
|
211
|
-
|
|
212
|
-
requiredPageKeys.forEach((key) => {
|
|
213
|
-
// required key is missing in $data that was passed for the page
|
|
214
|
-
if (!Object.keys(this.pageData).includes(key)) {
|
|
215
|
-
errorList.push(`Missing page ${key} in $data`)
|
|
216
|
-
}
|
|
217
|
-
// Validator value for type
|
|
218
|
-
else if (
|
|
219
|
-
key === 'type' &&
|
|
220
|
-
!requiredTypeValues.includes(this.pageData[key])
|
|
221
|
-
) {
|
|
222
|
-
errorList.push(`Invalid value assigment for page type in $data`)
|
|
223
|
-
} else if (count < 1) {
|
|
224
|
-
let errString = null
|
|
225
|
-
const requiredValues = ['video', 'audio']
|
|
226
|
-
switch (this.type) {
|
|
227
|
-
// validation for animation page type content
|
|
228
|
-
|
|
229
|
-
case 'pg_animation':
|
|
230
|
-
if (!this.pageData.animation)
|
|
231
|
-
errString = `Missing >>information in $data<< for animation type page`
|
|
232
|
-
if (this.pageData.animation && !this.pageData.animation.refName)
|
|
233
|
-
errString = `Missing >>refName<< for your animation `
|
|
234
|
-
if (errString) {
|
|
235
|
-
errorList.push(errString)
|
|
236
|
-
console.warn(
|
|
237
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
238
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
break
|
|
242
|
-
// valdation for media page type content
|
|
243
|
-
case 'pg_media':
|
|
244
|
-
if (
|
|
245
|
-
!this.pageData.mediaData ||
|
|
246
|
-
Object.keys(this.pageData.mediaData).length < 1
|
|
247
|
-
) {
|
|
248
|
-
errString = `Missing >>media information<< for media type page`
|
|
249
|
-
errorList.push(errString)
|
|
250
|
-
console.warn(
|
|
251
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
252
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
253
|
-
)
|
|
254
|
-
} else {
|
|
255
|
-
// validation for mediaData content. Must have mType && mSource
|
|
256
|
-
const { mType, mSources, mSubtitle, mTranscript, mPoster } =
|
|
257
|
-
this.pageData.mediaData
|
|
258
|
-
if (!mType || !mSources) {
|
|
259
|
-
errString = `Missing key(s) in mediaData declaration for media`
|
|
260
|
-
errorList.push(errString)
|
|
261
|
-
console.warn(
|
|
262
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
263
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
264
|
-
)
|
|
265
|
-
}
|
|
266
|
-
// Validation for mType content. Must be video/audio
|
|
267
|
-
else if (!requiredValues.includes(mType)) {
|
|
268
|
-
errString = `Invalid declariation for media type must be audio or video`
|
|
269
|
-
errorList.push(errString)
|
|
270
|
-
console.warn(
|
|
271
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
272
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
// Validation for mSource content. Must have at least 1 media source definition
|
|
276
|
-
else if (mSources && mSources.length < 1) {
|
|
277
|
-
errString = `Missing>>information for media source(s)<< for media type page`
|
|
278
|
-
errorList.push(errString)
|
|
279
|
-
console.warn(
|
|
280
|
-
`%c WARNING!>>> PAGE: ${errString}`,
|
|
281
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
282
|
-
)
|
|
283
|
-
}
|
|
284
|
-
//Start validating that the files exsist
|
|
285
|
-
if (!errString) {
|
|
286
|
-
// start validating that the file for subtitle exist in medias folder
|
|
287
|
-
if (mSubtitle && mSubtitle.src) return
|
|
288
|
-
|
|
289
|
-
//validate that mPoster file is present in media file when set
|
|
290
|
-
if (mPoster) {
|
|
291
|
-
if (mPoster.constructor === String) return
|
|
292
|
-
else {
|
|
293
|
-
let errStringInconsole =
|
|
294
|
-
'\n 💥 Invalid type declaration for mPoster.\n 🚩 Must be of type {String}'
|
|
295
|
-
|
|
296
|
-
errString = `l'Attribut 👉 mPoster 👈 pour le media doit être de type {String}`
|
|
297
|
-
|
|
298
|
-
errorList.push(errString)
|
|
299
|
-
console.warn(
|
|
300
|
-
`%c WARNING!>>> PAGE: ${errStringInconsole}`,
|
|
301
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
if (errString) break
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
//validate that transcript file is present in media file when set
|
|
308
|
-
if (mTranscript) {
|
|
309
|
-
if (mTranscript.constructor === String) return
|
|
310
|
-
else {
|
|
311
|
-
let errStringInconsole =
|
|
312
|
-
'\n 💥 Invalid type declaration for mTtranscript.\n 🚩 Must be of type {String}'
|
|
313
|
-
|
|
314
|
-
errString = `l'Attribut 👉 mTranscript 👈 pour le media doit être de type {String}`
|
|
315
|
-
|
|
316
|
-
errorList.push(errString)
|
|
317
|
-
console.warn(
|
|
318
|
-
`%c WARNING!>>> PAGE: ${errStringInconsole}`,
|
|
319
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
320
|
-
)
|
|
321
|
-
}
|
|
322
|
-
if (errString) break
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
break
|
|
327
|
-
}
|
|
328
|
-
count++
|
|
329
|
-
}
|
|
330
|
-
})
|
|
331
|
-
if (errorList.length > 0) err = errorList
|
|
332
|
-
}
|
|
333
|
-
return err
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
pgNumber() {
|
|
337
|
-
if (!this.pageData.id) return
|
|
338
|
-
let n = parseInt(this.pageData.id.replace('P', ''))
|
|
339
|
-
return n
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
A11yPageInfo() {
|
|
343
|
-
if (!this.$route || !this.$route.path) return ''
|
|
344
|
-
|
|
345
|
-
let A11YTxt = ''
|
|
346
|
-
const path = this.$route.path
|
|
347
|
-
let reg = /[/|-]/g
|
|
348
|
-
|
|
349
|
-
A11YTxt = path.replaceAll(reg, ' ') // replace all '/' and '-' by space
|
|
350
|
-
|
|
351
|
-
if (A11YTxt.includes('activite'))
|
|
352
|
-
A11YTxt = A11YTxt.replace('activite', this.$t('text.activity'))
|
|
353
|
-
|
|
354
|
-
return A11YTxt.toLowerCase()
|
|
355
|
-
},
|
|
356
|
-
|
|
357
|
-
getDebugModeInfo() {
|
|
358
|
-
const allActivitiesState = JSON.parse(
|
|
359
|
-
JSON.stringify(this.getAllActivitiesState)
|
|
360
|
-
)
|
|
361
|
-
let size = allActivitiesState[this.pageData.activityRef]
|
|
362
|
-
? allActivitiesState[this.pageData.activityRef].size
|
|
363
|
-
: 0
|
|
364
|
-
|
|
365
|
-
let Pprogress = allActivitiesState[this.pageData.activityRef].progressions
|
|
366
|
-
|
|
367
|
-
let info = {
|
|
368
|
-
id: this.pageData.activityRef,
|
|
369
|
-
size: size,
|
|
370
|
-
progression: Pprogress
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return info
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
watch: {
|
|
377
|
-
userInteraction: {
|
|
378
|
-
async handler(newValue) {
|
|
379
|
-
/**
|
|
380
|
-
* Observe changes in the number of poperties to dispatch updates in the userdata in the Store
|
|
381
|
-
*/
|
|
382
|
-
|
|
383
|
-
if (newValue && Object.entries(this.userInteraction).length) {
|
|
384
|
-
await this.store.updateUserMetaData({
|
|
385
|
-
activityRef: this.pageData.activityRef,
|
|
386
|
-
id: this.pageData.id,
|
|
387
|
-
userInteraction: { ...this.userInteraction }
|
|
388
|
-
})
|
|
389
|
-
}
|
|
390
|
-
},
|
|
391
|
-
immediate: true,
|
|
392
|
-
deep: true
|
|
393
|
-
},
|
|
394
|
-
'store.userDataLoaded': {
|
|
395
|
-
async handler() {
|
|
396
|
-
if (!this.store.userDataLoaded) return
|
|
397
|
-
this.userInteraction = await this.setInitialInteraction()
|
|
398
|
-
},
|
|
399
|
-
immediate: true,
|
|
400
|
-
deep: true
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
created() {
|
|
404
|
-
/*
|
|
405
|
-
* Create a custome object for this page that will be added in the collection
|
|
406
|
-
* will greate an id for the page
|
|
407
|
-
* will create a data for the page. Data key can hold media info such as timeline of animation, url, media type
|
|
408
|
-
* will update the store information for the currentPage
|
|
409
|
-
* wil update the store information for the currentTimeline (GSap animation)
|
|
410
|
-
*/
|
|
411
|
-
|
|
412
|
-
if (this.pageData && this.errorPage === false) {
|
|
413
|
-
// Handeling presence of animation in the page
|
|
414
|
-
if (this.pageData.animation) {
|
|
415
|
-
// update the store with the information of currentTimeline
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (this.type == 'pg_branch') {
|
|
419
|
-
// Update the store with the current page information when branching page is created
|
|
420
|
-
// Note: Branching doesn't trigger router navigation so is done directly here
|
|
421
|
-
this.store.updateCurrentPage({
|
|
422
|
-
activity_Id: this.pageData.activityRef,
|
|
423
|
-
page_Id: this.pageData.id
|
|
424
|
-
})
|
|
425
|
-
this.$bus.$on('branch-page-viewed', this.completePageBranching)
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (this.isBranchingPage) this.updateCurrentBranching(this.$data)
|
|
430
|
-
this.$bus.$on('media-viewed', this.setMediaViewed)
|
|
431
|
-
this.$bus.$on('manage-media-players', this.managePlayingMedia)
|
|
432
|
-
this.$bus.$on('video-transcript-toggle', this.onVideoTranscriptToggle)
|
|
433
|
-
this.$bus.$on('save-quiz-answers', this.saveQuizAnswers)
|
|
434
|
-
},
|
|
435
|
-
mounted() {
|
|
436
|
-
//Fix for firefox not updating aria-labelledby (was stuck saying Activite 1, page 1 on every pages)
|
|
437
|
-
this.$refs['page_info'].innerHTML = this.A11yPageInfo
|
|
438
|
-
|
|
439
|
-
// scrolling to top of only when is normal page
|
|
440
|
-
if (this.pageData && this.type === 'pg_normal')
|
|
441
|
-
this.$bus.$emit('move-to-target', 'page_info_section')
|
|
442
|
-
|
|
443
|
-
// set the state of the page when page is mounted : started or completed
|
|
444
|
-
if (this.userInteraction && this.userInteraction.state)
|
|
445
|
-
this.state = this.userInteraction.state
|
|
446
|
-
else if (
|
|
447
|
-
this.type === 'pg_menu' &&
|
|
448
|
-
this.userInteraction.state !== 'completed'
|
|
449
|
-
) {
|
|
450
|
-
this.state = 'completed' // set menu page state to completed
|
|
451
|
-
} else this.state = 'started' // set the default state to started
|
|
452
|
-
|
|
453
|
-
this.userInteraction['state'] = this.state // add the state to the userInteraction
|
|
454
|
-
// handle completion status of the page.
|
|
455
|
-
if (
|
|
456
|
-
document.documentElement.scrollHeight <=
|
|
457
|
-
document.documentElement.clientHeight + 20 &&
|
|
458
|
-
this.type !== 'pg_branch'
|
|
459
|
-
) {
|
|
460
|
-
this.completePage()
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (this.type === 'pg_branch') {
|
|
464
|
-
this.updateCurrentBranchPage(this.$data)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (this.type == 'pg_branch') return //do not proceed to add listener when branch page
|
|
468
|
-
|
|
469
|
-
window.addEventListener('scroll', this.onFirstScroll, { once: true }) //listener is removed when event fired
|
|
470
|
-
},
|
|
471
|
-
unmounted() {
|
|
472
|
-
//cleaning up all the event listener
|
|
473
|
-
this.$bus.$off('branch-page-viewed', this.completePageBranching)
|
|
474
|
-
this.$bus.$off('media-viewed', this.setMediaViewed)
|
|
475
|
-
this.$bus.$off('manage-media-players', this.managePlayingMedia)
|
|
476
|
-
this.$bus.$off('video-transcript-toggle', this.onVideoTranscriptToggle)
|
|
477
|
-
if (!this.isBranchingPage && this.type !== 'pg_branch')
|
|
478
|
-
this.updateCurrentBranching(null) //unset current branching when leaving a branching page
|
|
479
|
-
if (this.type == 'pg_branch') this.updateCurrentBranchPage(null) //unset current branch page when leaving a branch page
|
|
480
|
-
this.$bus.$off('save-quiz-answers', this.saveQuizAnswers)
|
|
481
|
-
window.removeEventListener('scroll', this.onFirstScroll, { once: true }) //in case user did not scroll
|
|
482
|
-
window.removeEventListener('scroll', this.handleScroll)
|
|
483
|
-
},
|
|
484
|
-
methods: {
|
|
485
|
-
...mapActions(useAppStore, ['updateCurrentBranchPage']),
|
|
486
|
-
//==============================================================
|
|
487
|
-
onFirstScroll() {
|
|
488
|
-
window.addEventListener('scroll', this.handleScroll)
|
|
489
|
-
},
|
|
490
|
-
/**
|
|
491
|
-
* @description to handle the complete state for the gauge
|
|
492
|
-
*/
|
|
493
|
-
|
|
494
|
-
handleScroll(event) {
|
|
495
|
-
event
|
|
496
|
-
/*
|
|
497
|
-
* DocumentElement properties does not alway work properly on all Browser.
|
|
498
|
-
* To Ensure reliable value of its properties on all.
|
|
499
|
-
* Browser we will calculate the Document Height by taking the maximum of
|
|
500
|
-
* body and documentElement height poperties.
|
|
501
|
-
* ref:https://javascript.info/size-and-scroll-window
|
|
502
|
-
*/
|
|
503
|
-
|
|
504
|
-
let scrollHeight = null
|
|
505
|
-
let clientHeight = null
|
|
506
|
-
let scrollTop = null
|
|
507
|
-
scrollHeight = Math.max(
|
|
508
|
-
document.body.scrollHeight,
|
|
509
|
-
document.documentElement.scrollHeight,
|
|
510
|
-
document.body.offsetHeight,
|
|
511
|
-
document.documentElement.offsetHeight,
|
|
512
|
-
document.body.clientHeight,
|
|
513
|
-
document.documentElement.clientHeight
|
|
514
|
-
)
|
|
515
|
-
clientHeight = document.documentElement.clientHeight
|
|
516
|
-
scrollTop = window.scrollY
|
|
517
|
-
|
|
518
|
-
// //Set scroll limit reached at 150px above the document height.
|
|
519
|
-
let scrollLimit = scrollHeight - 150
|
|
520
|
-
let fullyScrolled = Math.round(clientHeight + scrollTop)
|
|
521
|
-
|
|
522
|
-
//consider page completed when scolled value has reached or passed set limit
|
|
523
|
-
if (fullyScrolled >= scrollLimit && this.state !== 'completed') {
|
|
524
|
-
this.completePage()
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
/**
|
|
528
|
-
* @description set the state of the page to complete
|
|
529
|
-
* @fires send-xapi-statement to AppBaseModule.vue
|
|
530
|
-
*/
|
|
531
|
-
completePage() {
|
|
532
|
-
if (
|
|
533
|
-
['pg_menu', 'pg_branch'].includes(this.type) ||
|
|
534
|
-
this.state == 'completed' ||
|
|
535
|
-
this.isBranchingPage
|
|
536
|
-
)
|
|
537
|
-
return
|
|
538
|
-
|
|
539
|
-
this.state = 'completed'
|
|
540
|
-
this.userInteraction.state = this.state
|
|
541
|
-
},
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* @description set the state of the branching page to complete
|
|
545
|
-
* @fires send-xapi-statement to AppBaseModule.vue
|
|
546
|
-
*/
|
|
547
|
-
completePageBranching() {
|
|
548
|
-
//Get the current branching from the store
|
|
549
|
-
const currentBranching = this.getCurrentBranching
|
|
550
|
-
if (!currentBranching || currentBranching.id !== this.$route.meta.id)
|
|
551
|
-
return
|
|
552
|
-
if (currentBranching.state === 'completed') return
|
|
553
|
-
|
|
554
|
-
const children = this.$route.meta.children
|
|
555
|
-
let count = 0
|
|
556
|
-
children.forEach((c) => {
|
|
557
|
-
let progress = this.getProgress(c._ref)
|
|
558
|
-
|
|
559
|
-
if (progress.state == 'completed') count += 1
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
if (count !== children.length) return
|
|
563
|
-
|
|
564
|
-
currentBranching.state = 'completed' //set the state of the page to completed
|
|
565
|
-
currentBranching.userInteraction.state = currentBranching.state // update the useInteraction state
|
|
566
|
-
},
|
|
567
|
-
/**
|
|
568
|
-
* @description Get the user progress for the current page
|
|
569
|
-
* @param {string} id (Otpional) - the id of the targeted page
|
|
570
|
-
* @return {Oject} - the existing user data for the current page
|
|
571
|
-
*/
|
|
572
|
-
getProgress(id) {
|
|
573
|
-
id = id || this.pageData.id
|
|
574
|
-
|
|
575
|
-
const record = this.getPageInteraction(this.pageData.activityRef, id)
|
|
576
|
-
|
|
577
|
-
if (Object.entries(record).length) {
|
|
578
|
-
const { userInteraction } = record
|
|
579
|
-
return userInteraction
|
|
580
|
-
}
|
|
581
|
-
return {}
|
|
582
|
-
},
|
|
583
|
-
|
|
584
|
-
anchorProgress() {
|
|
585
|
-
const anchors = document.querySelectorAll('.anchor') // look for anchor
|
|
586
|
-
const options = {
|
|
587
|
-
root: null,
|
|
588
|
-
threshold: 0
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
let anchorsComplete
|
|
592
|
-
let indexStrt
|
|
593
|
-
let anchorString
|
|
594
|
-
let indexEnd
|
|
595
|
-
let anchorComplete
|
|
596
|
-
|
|
597
|
-
let target = document.querySelector('#App-base')
|
|
598
|
-
// get anchor already seen
|
|
599
|
-
anchorsComplete = this.getAnchorComplete()
|
|
600
|
-
|
|
601
|
-
const observer = new IntersectionObserver((entries) => {
|
|
602
|
-
// everytime the page passes a anchor
|
|
603
|
-
observer.observe(target)
|
|
604
|
-
|
|
605
|
-
entries.forEach((entry) => {
|
|
606
|
-
// when it's visable in the page
|
|
607
|
-
|
|
608
|
-
if (entry.isIntersecting) {
|
|
609
|
-
// get the target
|
|
610
|
-
|
|
611
|
-
this.anchorInfo = entry.target.classList
|
|
612
|
-
indexStrt = this.anchorInfo.value.indexOf('anchor-')
|
|
613
|
-
// work the string to get juste the anchor tag
|
|
614
|
-
// must be the same as the class
|
|
615
|
-
if (indexStrt == -1) {
|
|
616
|
-
return
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
anchorString = this.anchorInfo.value.substring(indexStrt)
|
|
620
|
-
|
|
621
|
-
indexEnd = anchorString.indexOf(' ')
|
|
622
|
-
if (indexEnd != -1) {
|
|
623
|
-
anchorComplete = anchorString.slice(0, indexEnd)
|
|
624
|
-
} else {
|
|
625
|
-
anchorComplete = anchorString
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
//get all the anchors of the current activity
|
|
629
|
-
const anchors_list = this.getAnchorsForActivity(
|
|
630
|
-
this.pageData.activityRef
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
//search for the current ancor
|
|
634
|
-
const anc = anchors_list.find((a) => a.anchorTag === anchorComplete)
|
|
635
|
-
//dispatch the current anchor to the store
|
|
636
|
-
if (anc) {
|
|
637
|
-
// update the store value for current section
|
|
638
|
-
// this.updateCurrentSection(anc)
|
|
639
|
-
// Ask bread scrumb to update its information
|
|
640
|
-
|
|
641
|
-
this.$bus.$emit('anchor-seen', anchorComplete)
|
|
642
|
-
} else {
|
|
643
|
-
if (import.meta.env.DEV)
|
|
644
|
-
console.warn(
|
|
645
|
-
`%c WARNING!>>> Anchor handeling: 👉${anchorComplete}👈 doesn't exist. Please provide a valid anchor.`,
|
|
646
|
-
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
return
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// if you didn't aldreay seen anchors
|
|
653
|
-
if (anchorsComplete != undefined) {
|
|
654
|
-
// look if you already saw this anchor
|
|
655
|
-
if (anc && !anchorsComplete.includes(anchorComplete)) {
|
|
656
|
-
// push it in the array them in the store
|
|
657
|
-
anchorsComplete.push(anchorComplete)
|
|
658
|
-
this.$set(this.userInteraction, 'anchors', anchorsComplete)
|
|
659
|
-
}
|
|
660
|
-
} else {
|
|
661
|
-
// if you never saw any anchor
|
|
662
|
-
// push it in the array them in the store
|
|
663
|
-
anchorsComplete = []
|
|
664
|
-
if (anc) anchorsComplete.push(anchorComplete)
|
|
665
|
-
this.$set(this.userInteraction, 'anchors', anchorsComplete)
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
})
|
|
669
|
-
}, options)
|
|
670
|
-
|
|
671
|
-
// observer call llok for each anchor in page
|
|
672
|
-
anchors.forEach((anchor) => {
|
|
673
|
-
observer.observe(anchor)
|
|
674
|
-
})
|
|
675
|
-
},
|
|
676
|
-
getAnchorComplete() {
|
|
677
|
-
const records = this.getPageInteraction(
|
|
678
|
-
this.pageData.activityRef,
|
|
679
|
-
this.pageData.id
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
// Verify if the element existe
|
|
683
|
-
if (
|
|
684
|
-
records &&
|
|
685
|
-
records.userInteraction &&
|
|
686
|
-
records.userInteraction.anchors
|
|
687
|
-
) {
|
|
688
|
-
// if you already saw anchors return them
|
|
689
|
-
|
|
690
|
-
return records.userInteraction.anchors
|
|
691
|
-
}
|
|
692
|
-
},
|
|
693
|
-
showChoiceBif(data) {
|
|
694
|
-
// check if you made a choice
|
|
695
|
-
if (
|
|
696
|
-
typeof this.getBifChoice === 'undefined' ||
|
|
697
|
-
Object.keys(this.getBifChoice).length === 0
|
|
698
|
-
) {
|
|
699
|
-
return data['A']
|
|
700
|
-
} else {
|
|
701
|
-
if (this.getBifChoice.choix) {
|
|
702
|
-
// get the choise from store
|
|
703
|
-
if (data.hasOwnProperty(this.getBifChoice.choix)) {
|
|
704
|
-
let choice = data[this.getBifChoice.choix]
|
|
705
|
-
//return choice
|
|
706
|
-
return choice
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
},
|
|
711
|
-
openPopup(data) {
|
|
712
|
-
this.$bus.$emit('open-popup', data)
|
|
713
|
-
},
|
|
714
|
-
/**
|
|
715
|
-
* @description Send to the bd that the media have been view by the user- Add the id of the media to the
|
|
716
|
-
* userInteraction of the page
|
|
717
|
-
*/
|
|
718
|
-
setMediaViewed(mediaID) {
|
|
719
|
-
// Should get the userData to check if current mediaElement has entry
|
|
720
|
-
let { mediasViewed } = this.userInteraction
|
|
721
|
-
|
|
722
|
-
// Should create entry for medias viewed in userInteraction if none
|
|
723
|
-
if (!mediasViewed) {
|
|
724
|
-
mediasViewed = []
|
|
725
|
-
|
|
726
|
-
this.userInteraction.mediasViewed = mediasViewed
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
//Should add ID in media viewed list if viewed for the 1st time
|
|
730
|
-
if (mediasViewed.includes(mediaID)) return
|
|
731
|
-
|
|
732
|
-
mediasViewed.push(mediaID)
|
|
733
|
-
|
|
734
|
-
this.userInteraction.mediasViewed = mediasViewed //Update the userInteraction data of the page
|
|
735
|
-
}, //
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* @description - Method to manage the playing state of media element in the page.
|
|
739
|
-
* Only one media should play at a time. When receives signal of new media playing
|
|
740
|
-
* get the reference of the previous media in play from the store and
|
|
741
|
-
* put it in stop state. Also closes the transcript sidebar if it is open.
|
|
742
|
-
* @param {HTMLElement} media - the actual media that is playing
|
|
743
|
-
*/
|
|
744
|
-
managePlayingMedia(media) {
|
|
745
|
-
if (!media) return
|
|
746
|
-
//Should get all the media of the page from store
|
|
747
|
-
const { mElements } = this.getCurrentPage
|
|
748
|
-
if (!mElements || !mElements.length) return
|
|
749
|
-
|
|
750
|
-
// // Should stop any media playing
|
|
751
|
-
mElements.forEach((m) => {
|
|
752
|
-
if (m.id == media.id) return
|
|
753
|
-
|
|
754
|
-
const attrKeys = Object.keys(m) //
|
|
755
|
-
const playbarInstance = m[attrKeys[3]]
|
|
756
|
-
const HTMLmediaElement = m[attrKeys[1]]
|
|
757
|
-
|
|
758
|
-
//Close the transcript sidebar if it is open
|
|
759
|
-
if (playbarInstance.transcriptEnabled) {
|
|
760
|
-
playbarInstance.toggleViewTranscript()
|
|
761
|
-
}
|
|
762
|
-
//Check if the media is playing to stop it. Playing state is given by the instance of the target play-bar
|
|
763
|
-
if (playbarInstance.isPlaying) {
|
|
764
|
-
HTMLmediaElement.pause() // target the HTMLmediaElement to control it state
|
|
765
|
-
playbarInstance.isPlaying = false //change this isPlaying value of the instance
|
|
766
|
-
|
|
767
|
-
playbarInstance.timer.pause() // pause the timer of the instance
|
|
768
|
-
}
|
|
769
|
-
})
|
|
770
|
-
},
|
|
771
|
-
/**
|
|
772
|
-
* @description - Method to manage the state of the transcript and fullscreen buttons.
|
|
773
|
-
* Disables those buttons on other media playbar when a transcript sidebar is open.
|
|
774
|
-
*
|
|
775
|
-
* @param {HTMLElement} media - the actual media that is playing
|
|
776
|
-
* @param {Boolean} transcriptShown - current status of the transcript sidebar
|
|
777
|
-
*/
|
|
778
|
-
onVideoTranscriptToggle(media, transcriptShown) {
|
|
779
|
-
if (!media) return
|
|
780
|
-
//Should get all the media of the page from store
|
|
781
|
-
const { mElements } = this.getCurrentPage
|
|
782
|
-
if (!mElements || !mElements.length) return
|
|
783
|
-
mElements.forEach((m) => {
|
|
784
|
-
if (m.id == media.id) return
|
|
785
|
-
const attrKeys = Object.keys(m)
|
|
786
|
-
const playbarInstance = m[attrKeys[3]]
|
|
787
|
-
playbarInstance.otherVideoTranscriptShown = transcriptShown
|
|
788
|
-
})
|
|
789
|
-
},
|
|
790
|
-
setInitialInteraction() {
|
|
791
|
-
const { activityRef, id: pageID } = this.pageData
|
|
792
|
-
const { userInteraction: previousInteraction = {} } =
|
|
793
|
-
this.getPageInteraction(activityRef, pageID)
|
|
794
|
-
|
|
795
|
-
return previousInteraction
|
|
796
|
-
},
|
|
797
|
-
saveQuizAnswers(el, quiz) {
|
|
798
|
-
if (!this.userInteraction.quizAnswers)
|
|
799
|
-
return (this.userInteraction.quizAnswers = { ...quiz })
|
|
800
|
-
const quizID = Object.keys(quiz)[0]
|
|
801
|
-
|
|
802
|
-
if (!this.userInteraction.quizAnswers[quizID])
|
|
803
|
-
return (this.userInteraction.quizAnswers = {
|
|
804
|
-
...this.userInteraction.quizAnswers,
|
|
805
|
-
...quiz
|
|
806
|
-
})
|
|
807
|
-
const quizValue = Object.values(quiz)[0]
|
|
808
|
-
this.userInteraction.quizAnswers[quizID] = quizValue
|
|
809
|
-
// const {}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
</script>
|
|
814
|
-
<style lang="scss">
|
|
815
|
-
.img-popUp {
|
|
816
|
-
position: relative;
|
|
817
|
-
|
|
818
|
-
.box-trigger {
|
|
819
|
-
position: absolute;
|
|
820
|
-
width: 100%;
|
|
821
|
-
height: 100%;
|
|
822
|
-
top: 0;
|
|
823
|
-
left: 0;
|
|
824
|
-
|
|
825
|
-
.btn {
|
|
826
|
-
position: absolute;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
.debug-pageInfo {
|
|
832
|
-
position: fixed;
|
|
833
|
-
right: -250px;
|
|
834
|
-
bottom: 20px;
|
|
835
|
-
|
|
836
|
-
color: #333;
|
|
837
|
-
background-color: rgba(#eaabb6b3, 0.9);
|
|
838
|
-
|
|
839
|
-
display: flex;
|
|
840
|
-
flex-direction: row;
|
|
841
|
-
|
|
842
|
-
&.open {
|
|
843
|
-
right: 0;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
.w-btn {
|
|
847
|
-
padding: 10px;
|
|
848
|
-
height: 100%;
|
|
849
|
-
cursor: pointer;
|
|
850
|
-
background: rgba(#fff, 0.05);
|
|
851
|
-
|
|
852
|
-
&:hover {
|
|
853
|
-
background: rgba(#fff, 0.1);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
.ctn {
|
|
858
|
-
padding: 24px;
|
|
859
|
-
width: 250px;
|
|
860
|
-
|
|
861
|
-
ul {
|
|
862
|
-
margin-left: 15px;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
@ Description: a prototype to test refactoring of pageMixin paradigm and fonctionality
|
|
3
|
+
@ What it does: The component received a data and validate the type of page that will be created
|
|
4
|
+
The component update the store information for the type of page created and also the timeline
|
|
5
|
+
@Note: Page not created with this component will not have the data automatically traked by the application
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<div :id="pageData.id" :key="pageData.id" class="app-page" role="main">
|
|
9
|
+
<div v-if="appDebugMode" class="debug-pageInfo" :class="{ open: push }">
|
|
10
|
+
<button class="w-btn" @click="push = !push"><</button>
|
|
11
|
+
<div class="ctn">
|
|
12
|
+
Activity info: Route : {{ $route.path }}
|
|
13
|
+
<br />
|
|
14
|
+
|
|
15
|
+
Activity id : {{ getDebugModeInfo.id }}
|
|
16
|
+
<br />
|
|
17
|
+
Nombre de page : {{ getDebugModeInfo.size }}
|
|
18
|
+
<br />
|
|
19
|
+
Page complete :
|
|
20
|
+
<ul>
|
|
21
|
+
<li v-for="(value, key) in getDebugModeInfo.progression" :key="key">
|
|
22
|
+
{{ value }}
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<slot v-if="!errorPage.length" name="content">Page</slot>
|
|
28
|
+
<app-base-error-display
|
|
29
|
+
v-else
|
|
30
|
+
:error-group="'component'"
|
|
31
|
+
:error-title="'ERREUR: CRÉATION DE LA PAGE'"
|
|
32
|
+
:errors-list="errorPage"
|
|
33
|
+
:error-text="`Vous avez une/des erreur(s) dans la création de votre PAGE. Veuillez
|
|
34
|
+
corriger les erreurs ci-dessous:`"
|
|
35
|
+
/>
|
|
36
|
+
<span
|
|
37
|
+
id="page_info"
|
|
38
|
+
ref="page_info"
|
|
39
|
+
class="sr-only"
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
></span>
|
|
42
|
+
<div id="hiddenAlertContainer" role="alert" class="sr-only"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
import { computed } from 'vue'
|
|
48
|
+
import { useAppStore } from '../module/stores/appStore'
|
|
49
|
+
import { mapState, mapActions } from 'pinia'
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
provide() {
|
|
53
|
+
return { userInteraction: computed(() => this.userInteraction) }
|
|
54
|
+
},
|
|
55
|
+
props: {
|
|
56
|
+
pageData: {
|
|
57
|
+
type: Object,
|
|
58
|
+
required: true,
|
|
59
|
+
|
|
60
|
+
validator(value) {
|
|
61
|
+
let isValid = true
|
|
62
|
+
if (import.meta.env.DEV) {
|
|
63
|
+
const requiredPageKeys = ['id', 'activityRef', 'type']
|
|
64
|
+
let requiredTypeValues = [
|
|
65
|
+
'pg_normal',
|
|
66
|
+
'pg_menu',
|
|
67
|
+
'pg_animation',
|
|
68
|
+
'pg_media',
|
|
69
|
+
'pg_branch'
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
requiredPageKeys.forEach((key) => {
|
|
73
|
+
if (!Object.keys(value).includes(key)) {
|
|
74
|
+
console.warn(
|
|
75
|
+
`%c WARNING!>>> PAGE: PAGE: Missing ${key} for the page in $data `,
|
|
76
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
77
|
+
)
|
|
78
|
+
isValid = false
|
|
79
|
+
} else if (
|
|
80
|
+
key === 'type' &&
|
|
81
|
+
!requiredTypeValues.includes(value[key])
|
|
82
|
+
) {
|
|
83
|
+
let errString = `Invalid value assigment for type of the page in $data.`
|
|
84
|
+
|
|
85
|
+
console.warn(
|
|
86
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
87
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
88
|
+
)
|
|
89
|
+
isValid = false
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
return isValid
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
setup(props) {
|
|
98
|
+
const store = useAppStore()
|
|
99
|
+
const { activityRef, id: pageID, type: pageType } = props.pageData
|
|
100
|
+
|
|
101
|
+
//Getting initial existing userIntaction from store
|
|
102
|
+
const { userInteraction: previousInteraction = {} } =
|
|
103
|
+
store.getPageInteraction(activityRef, pageID)
|
|
104
|
+
|
|
105
|
+
return { store, previousInteraction, pageID, pageType }
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
data() {
|
|
109
|
+
return {
|
|
110
|
+
getHash: null,
|
|
111
|
+
//========PageMisins values ===================
|
|
112
|
+
userInteraction: this.previousInteraction,
|
|
113
|
+
state: null,
|
|
114
|
+
anchorEnable: true,
|
|
115
|
+
anchorInfo: null,
|
|
116
|
+
drModeActive: null,
|
|
117
|
+
error: null,
|
|
118
|
+
id: this.pageID,
|
|
119
|
+
type: this.pageType,
|
|
120
|
+
push: false
|
|
121
|
+
|
|
122
|
+
//======== End PageMisins values ===================
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
computed: {
|
|
126
|
+
...mapState(useAppStore, [
|
|
127
|
+
'getDataNoteCredit',
|
|
128
|
+
'getAllActivities',
|
|
129
|
+
'getApplicationSettings',
|
|
130
|
+
'getAppDebugMode'
|
|
131
|
+
]),
|
|
132
|
+
|
|
133
|
+
appDebugMode() {
|
|
134
|
+
return this.getAppDebugMode
|
|
135
|
+
},
|
|
136
|
+
getRouteHistory() {
|
|
137
|
+
return this.store.getRouteHistory
|
|
138
|
+
},
|
|
139
|
+
getAllActivitiesState() {
|
|
140
|
+
return this.store.getAllActivitiesState
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getAllCompleted() {
|
|
144
|
+
return this.store.getAllCompleted
|
|
145
|
+
},
|
|
146
|
+
getErrorChoiceDetect() {
|
|
147
|
+
return this.store.getErrorChoiceDetect
|
|
148
|
+
},
|
|
149
|
+
getUserInteraction() {
|
|
150
|
+
return this.store.getUserInteraction
|
|
151
|
+
},
|
|
152
|
+
getPageInteraction() {
|
|
153
|
+
return this.store.getPageInteraction
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
getCurrentPage() {
|
|
157
|
+
return this.store.getCurrentPage
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
getCurrentBranching() {
|
|
161
|
+
return this.store.getCurrentBranching
|
|
162
|
+
},
|
|
163
|
+
updateCurrentBranching() {
|
|
164
|
+
return this.store.updateCurrentBranching
|
|
165
|
+
},
|
|
166
|
+
getModuleInfo() {
|
|
167
|
+
return this.store.getModuleInfo
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
getAllActivities() {
|
|
171
|
+
return this.store.getAllActivities()
|
|
172
|
+
},
|
|
173
|
+
getConnectionInfo() {
|
|
174
|
+
return this.store.getConnectionInfo
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
getAnchorsForActivity() {
|
|
178
|
+
return this.store.getAnchorsForActivity()
|
|
179
|
+
},
|
|
180
|
+
getBifChoice() {
|
|
181
|
+
return this.store.getBifChoice
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
isBranchingPage() {
|
|
185
|
+
return this.$route.meta.type === 'branching' && this.type !== 'pg_branch'
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
//================================================
|
|
189
|
+
settingsOptions() {
|
|
190
|
+
return this.settingsOptionsELPlus
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
settingsSelected() {
|
|
194
|
+
const setting = this.getApplicationSettings
|
|
195
|
+
return setting
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
errorPage() {
|
|
199
|
+
let err = false
|
|
200
|
+
if (import.meta.env.DEV) {
|
|
201
|
+
const requiredPageKeys = ['id', 'activityRef', 'type']
|
|
202
|
+
const errorList = []
|
|
203
|
+
let count = 0
|
|
204
|
+
let requiredTypeValues = [
|
|
205
|
+
'pg_normal',
|
|
206
|
+
'pg_menu',
|
|
207
|
+
'pg_animation',
|
|
208
|
+
'pg_media',
|
|
209
|
+
'pg_branch'
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
requiredPageKeys.forEach((key) => {
|
|
213
|
+
// required key is missing in $data that was passed for the page
|
|
214
|
+
if (!Object.keys(this.pageData).includes(key)) {
|
|
215
|
+
errorList.push(`Missing page ${key} in $data`)
|
|
216
|
+
}
|
|
217
|
+
// Validator value for type
|
|
218
|
+
else if (
|
|
219
|
+
key === 'type' &&
|
|
220
|
+
!requiredTypeValues.includes(this.pageData[key])
|
|
221
|
+
) {
|
|
222
|
+
errorList.push(`Invalid value assigment for page type in $data`)
|
|
223
|
+
} else if (count < 1) {
|
|
224
|
+
let errString = null
|
|
225
|
+
const requiredValues = ['video', 'audio']
|
|
226
|
+
switch (this.type) {
|
|
227
|
+
// validation for animation page type content
|
|
228
|
+
|
|
229
|
+
case 'pg_animation':
|
|
230
|
+
if (!this.pageData.animation)
|
|
231
|
+
errString = `Missing >>information in $data<< for animation type page`
|
|
232
|
+
if (this.pageData.animation && !this.pageData.animation.refName)
|
|
233
|
+
errString = `Missing >>refName<< for your animation `
|
|
234
|
+
if (errString) {
|
|
235
|
+
errorList.push(errString)
|
|
236
|
+
console.warn(
|
|
237
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
238
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
break
|
|
242
|
+
// valdation for media page type content
|
|
243
|
+
case 'pg_media':
|
|
244
|
+
if (
|
|
245
|
+
!this.pageData.mediaData ||
|
|
246
|
+
Object.keys(this.pageData.mediaData).length < 1
|
|
247
|
+
) {
|
|
248
|
+
errString = `Missing >>media information<< for media type page`
|
|
249
|
+
errorList.push(errString)
|
|
250
|
+
console.warn(
|
|
251
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
252
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
253
|
+
)
|
|
254
|
+
} else {
|
|
255
|
+
// validation for mediaData content. Must have mType && mSource
|
|
256
|
+
const { mType, mSources, mSubtitle, mTranscript, mPoster } =
|
|
257
|
+
this.pageData.mediaData
|
|
258
|
+
if (!mType || !mSources) {
|
|
259
|
+
errString = `Missing key(s) in mediaData declaration for media`
|
|
260
|
+
errorList.push(errString)
|
|
261
|
+
console.warn(
|
|
262
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
263
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
// Validation for mType content. Must be video/audio
|
|
267
|
+
else if (!requiredValues.includes(mType)) {
|
|
268
|
+
errString = `Invalid declariation for media type must be audio or video`
|
|
269
|
+
errorList.push(errString)
|
|
270
|
+
console.warn(
|
|
271
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
272
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
// Validation for mSource content. Must have at least 1 media source definition
|
|
276
|
+
else if (mSources && mSources.length < 1) {
|
|
277
|
+
errString = `Missing>>information for media source(s)<< for media type page`
|
|
278
|
+
errorList.push(errString)
|
|
279
|
+
console.warn(
|
|
280
|
+
`%c WARNING!>>> PAGE: ${errString}`,
|
|
281
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
//Start validating that the files exsist
|
|
285
|
+
if (!errString) {
|
|
286
|
+
// start validating that the file for subtitle exist in medias folder
|
|
287
|
+
if (mSubtitle && mSubtitle.src) return
|
|
288
|
+
|
|
289
|
+
//validate that mPoster file is present in media file when set
|
|
290
|
+
if (mPoster) {
|
|
291
|
+
if (mPoster.constructor === String) return
|
|
292
|
+
else {
|
|
293
|
+
let errStringInconsole =
|
|
294
|
+
'\n 💥 Invalid type declaration for mPoster.\n 🚩 Must be of type {String}'
|
|
295
|
+
|
|
296
|
+
errString = `l'Attribut 👉 mPoster 👈 pour le media doit être de type {String}`
|
|
297
|
+
|
|
298
|
+
errorList.push(errString)
|
|
299
|
+
console.warn(
|
|
300
|
+
`%c WARNING!>>> PAGE: ${errStringInconsole}`,
|
|
301
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
if (errString) break
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//validate that transcript file is present in media file when set
|
|
308
|
+
if (mTranscript) {
|
|
309
|
+
if (mTranscript.constructor === String) return
|
|
310
|
+
else {
|
|
311
|
+
let errStringInconsole =
|
|
312
|
+
'\n 💥 Invalid type declaration for mTtranscript.\n 🚩 Must be of type {String}'
|
|
313
|
+
|
|
314
|
+
errString = `l'Attribut 👉 mTranscript 👈 pour le media doit être de type {String}`
|
|
315
|
+
|
|
316
|
+
errorList.push(errString)
|
|
317
|
+
console.warn(
|
|
318
|
+
`%c WARNING!>>> PAGE: ${errStringInconsole}`,
|
|
319
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
if (errString) break
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
count++
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
if (errorList.length > 0) err = errorList
|
|
332
|
+
}
|
|
333
|
+
return err
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
pgNumber() {
|
|
337
|
+
if (!this.pageData.id) return
|
|
338
|
+
let n = parseInt(this.pageData.id.replace('P', ''))
|
|
339
|
+
return n
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
A11yPageInfo() {
|
|
343
|
+
if (!this.$route || !this.$route.path) return ''
|
|
344
|
+
|
|
345
|
+
let A11YTxt = ''
|
|
346
|
+
const path = this.$route.path
|
|
347
|
+
let reg = /[/|-]/g
|
|
348
|
+
|
|
349
|
+
A11YTxt = path.replaceAll(reg, ' ') // replace all '/' and '-' by space
|
|
350
|
+
|
|
351
|
+
if (A11YTxt.includes('activite'))
|
|
352
|
+
A11YTxt = A11YTxt.replace('activite', this.$t('text.activity'))
|
|
353
|
+
|
|
354
|
+
return A11YTxt.toLowerCase()
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
getDebugModeInfo() {
|
|
358
|
+
const allActivitiesState = JSON.parse(
|
|
359
|
+
JSON.stringify(this.getAllActivitiesState)
|
|
360
|
+
)
|
|
361
|
+
let size = allActivitiesState[this.pageData.activityRef]
|
|
362
|
+
? allActivitiesState[this.pageData.activityRef].size
|
|
363
|
+
: 0
|
|
364
|
+
|
|
365
|
+
let Pprogress = allActivitiesState[this.pageData.activityRef].progressions
|
|
366
|
+
|
|
367
|
+
let info = {
|
|
368
|
+
id: this.pageData.activityRef,
|
|
369
|
+
size: size,
|
|
370
|
+
progression: Pprogress
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return info
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
watch: {
|
|
377
|
+
userInteraction: {
|
|
378
|
+
async handler(newValue) {
|
|
379
|
+
/**
|
|
380
|
+
* Observe changes in the number of poperties to dispatch updates in the userdata in the Store
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
if (newValue && Object.entries(this.userInteraction).length) {
|
|
384
|
+
await this.store.updateUserMetaData({
|
|
385
|
+
activityRef: this.pageData.activityRef,
|
|
386
|
+
id: this.pageData.id,
|
|
387
|
+
userInteraction: { ...this.userInteraction }
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
immediate: true,
|
|
392
|
+
deep: true
|
|
393
|
+
},
|
|
394
|
+
'store.userDataLoaded': {
|
|
395
|
+
async handler() {
|
|
396
|
+
if (!this.store.userDataLoaded) return
|
|
397
|
+
this.userInteraction = await this.setInitialInteraction()
|
|
398
|
+
},
|
|
399
|
+
immediate: true,
|
|
400
|
+
deep: true
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
created() {
|
|
404
|
+
/*
|
|
405
|
+
* Create a custome object for this page that will be added in the collection
|
|
406
|
+
* will greate an id for the page
|
|
407
|
+
* will create a data for the page. Data key can hold media info such as timeline of animation, url, media type
|
|
408
|
+
* will update the store information for the currentPage
|
|
409
|
+
* wil update the store information for the currentTimeline (GSap animation)
|
|
410
|
+
*/
|
|
411
|
+
|
|
412
|
+
if (this.pageData && this.errorPage === false) {
|
|
413
|
+
// Handeling presence of animation in the page
|
|
414
|
+
if (this.pageData.animation) {
|
|
415
|
+
// update the store with the information of currentTimeline
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (this.type == 'pg_branch') {
|
|
419
|
+
// Update the store with the current page information when branching page is created
|
|
420
|
+
// Note: Branching doesn't trigger router navigation so is done directly here
|
|
421
|
+
this.store.updateCurrentPage({
|
|
422
|
+
activity_Id: this.pageData.activityRef,
|
|
423
|
+
page_Id: this.pageData.id
|
|
424
|
+
})
|
|
425
|
+
this.$bus.$on('branch-page-viewed', this.completePageBranching)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this.isBranchingPage) this.updateCurrentBranching(this.$data)
|
|
430
|
+
this.$bus.$on('media-viewed', this.setMediaViewed)
|
|
431
|
+
this.$bus.$on('manage-media-players', this.managePlayingMedia)
|
|
432
|
+
this.$bus.$on('video-transcript-toggle', this.onVideoTranscriptToggle)
|
|
433
|
+
this.$bus.$on('save-quiz-answers', this.saveQuizAnswers)
|
|
434
|
+
},
|
|
435
|
+
mounted() {
|
|
436
|
+
//Fix for firefox not updating aria-labelledby (was stuck saying Activite 1, page 1 on every pages)
|
|
437
|
+
this.$refs['page_info'].innerHTML = this.A11yPageInfo
|
|
438
|
+
|
|
439
|
+
// scrolling to top of only when is normal page
|
|
440
|
+
if (this.pageData && this.type === 'pg_normal')
|
|
441
|
+
this.$bus.$emit('move-to-target', 'page_info_section')
|
|
442
|
+
|
|
443
|
+
// set the state of the page when page is mounted : started or completed
|
|
444
|
+
if (this.userInteraction && this.userInteraction.state)
|
|
445
|
+
this.state = this.userInteraction.state
|
|
446
|
+
else if (
|
|
447
|
+
this.type === 'pg_menu' &&
|
|
448
|
+
this.userInteraction.state !== 'completed'
|
|
449
|
+
) {
|
|
450
|
+
this.state = 'completed' // set menu page state to completed
|
|
451
|
+
} else this.state = 'started' // set the default state to started
|
|
452
|
+
|
|
453
|
+
this.userInteraction['state'] = this.state // add the state to the userInteraction
|
|
454
|
+
// handle completion status of the page.
|
|
455
|
+
if (
|
|
456
|
+
document.documentElement.scrollHeight <=
|
|
457
|
+
document.documentElement.clientHeight + 20 &&
|
|
458
|
+
this.type !== 'pg_branch'
|
|
459
|
+
) {
|
|
460
|
+
this.completePage()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (this.type === 'pg_branch') {
|
|
464
|
+
this.updateCurrentBranchPage(this.$data)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (this.type == 'pg_branch') return //do not proceed to add listener when branch page
|
|
468
|
+
|
|
469
|
+
window.addEventListener('scroll', this.onFirstScroll, { once: true }) //listener is removed when event fired
|
|
470
|
+
},
|
|
471
|
+
unmounted() {
|
|
472
|
+
//cleaning up all the event listener
|
|
473
|
+
this.$bus.$off('branch-page-viewed', this.completePageBranching)
|
|
474
|
+
this.$bus.$off('media-viewed', this.setMediaViewed)
|
|
475
|
+
this.$bus.$off('manage-media-players', this.managePlayingMedia)
|
|
476
|
+
this.$bus.$off('video-transcript-toggle', this.onVideoTranscriptToggle)
|
|
477
|
+
if (!this.isBranchingPage && this.type !== 'pg_branch')
|
|
478
|
+
this.updateCurrentBranching(null) //unset current branching when leaving a branching page
|
|
479
|
+
if (this.type == 'pg_branch') this.updateCurrentBranchPage(null) //unset current branch page when leaving a branch page
|
|
480
|
+
this.$bus.$off('save-quiz-answers', this.saveQuizAnswers)
|
|
481
|
+
window.removeEventListener('scroll', this.onFirstScroll, { once: true }) //in case user did not scroll
|
|
482
|
+
window.removeEventListener('scroll', this.handleScroll)
|
|
483
|
+
},
|
|
484
|
+
methods: {
|
|
485
|
+
...mapActions(useAppStore, ['updateCurrentBranchPage']),
|
|
486
|
+
//==============================================================
|
|
487
|
+
onFirstScroll() {
|
|
488
|
+
window.addEventListener('scroll', this.handleScroll)
|
|
489
|
+
},
|
|
490
|
+
/**
|
|
491
|
+
* @description to handle the complete state for the gauge
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
handleScroll(event) {
|
|
495
|
+
event
|
|
496
|
+
/*
|
|
497
|
+
* DocumentElement properties does not alway work properly on all Browser.
|
|
498
|
+
* To Ensure reliable value of its properties on all.
|
|
499
|
+
* Browser we will calculate the Document Height by taking the maximum of
|
|
500
|
+
* body and documentElement height poperties.
|
|
501
|
+
* ref:https://javascript.info/size-and-scroll-window
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
let scrollHeight = null
|
|
505
|
+
let clientHeight = null
|
|
506
|
+
let scrollTop = null
|
|
507
|
+
scrollHeight = Math.max(
|
|
508
|
+
document.body.scrollHeight,
|
|
509
|
+
document.documentElement.scrollHeight,
|
|
510
|
+
document.body.offsetHeight,
|
|
511
|
+
document.documentElement.offsetHeight,
|
|
512
|
+
document.body.clientHeight,
|
|
513
|
+
document.documentElement.clientHeight
|
|
514
|
+
)
|
|
515
|
+
clientHeight = document.documentElement.clientHeight
|
|
516
|
+
scrollTop = window.scrollY
|
|
517
|
+
|
|
518
|
+
// //Set scroll limit reached at 150px above the document height.
|
|
519
|
+
let scrollLimit = scrollHeight - 150
|
|
520
|
+
let fullyScrolled = Math.round(clientHeight + scrollTop)
|
|
521
|
+
|
|
522
|
+
//consider page completed when scolled value has reached or passed set limit
|
|
523
|
+
if (fullyScrolled >= scrollLimit && this.state !== 'completed') {
|
|
524
|
+
this.completePage()
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
/**
|
|
528
|
+
* @description set the state of the page to complete
|
|
529
|
+
* @fires send-xapi-statement to AppBaseModule.vue
|
|
530
|
+
*/
|
|
531
|
+
completePage() {
|
|
532
|
+
if (
|
|
533
|
+
['pg_menu', 'pg_branch'].includes(this.type) ||
|
|
534
|
+
this.state == 'completed' ||
|
|
535
|
+
this.isBranchingPage
|
|
536
|
+
)
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
this.state = 'completed'
|
|
540
|
+
this.userInteraction.state = this.state
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* @description set the state of the branching page to complete
|
|
545
|
+
* @fires send-xapi-statement to AppBaseModule.vue
|
|
546
|
+
*/
|
|
547
|
+
completePageBranching() {
|
|
548
|
+
//Get the current branching from the store
|
|
549
|
+
const currentBranching = this.getCurrentBranching
|
|
550
|
+
if (!currentBranching || currentBranching.id !== this.$route.meta.id)
|
|
551
|
+
return
|
|
552
|
+
if (currentBranching.state === 'completed') return
|
|
553
|
+
|
|
554
|
+
const children = this.$route.meta.children
|
|
555
|
+
let count = 0
|
|
556
|
+
children.forEach((c) => {
|
|
557
|
+
let progress = this.getProgress(c._ref)
|
|
558
|
+
|
|
559
|
+
if (progress.state == 'completed') count += 1
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
if (count !== children.length) return
|
|
563
|
+
|
|
564
|
+
currentBranching.state = 'completed' //set the state of the page to completed
|
|
565
|
+
currentBranching.userInteraction.state = currentBranching.state // update the useInteraction state
|
|
566
|
+
},
|
|
567
|
+
/**
|
|
568
|
+
* @description Get the user progress for the current page
|
|
569
|
+
* @param {string} id (Otpional) - the id of the targeted page
|
|
570
|
+
* @return {Oject} - the existing user data for the current page
|
|
571
|
+
*/
|
|
572
|
+
getProgress(id) {
|
|
573
|
+
id = id || this.pageData.id
|
|
574
|
+
|
|
575
|
+
const record = this.getPageInteraction(this.pageData.activityRef, id)
|
|
576
|
+
|
|
577
|
+
if (Object.entries(record).length) {
|
|
578
|
+
const { userInteraction } = record
|
|
579
|
+
return userInteraction
|
|
580
|
+
}
|
|
581
|
+
return {}
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
anchorProgress() {
|
|
585
|
+
const anchors = document.querySelectorAll('.anchor') // look for anchor
|
|
586
|
+
const options = {
|
|
587
|
+
root: null,
|
|
588
|
+
threshold: 0
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let anchorsComplete
|
|
592
|
+
let indexStrt
|
|
593
|
+
let anchorString
|
|
594
|
+
let indexEnd
|
|
595
|
+
let anchorComplete
|
|
596
|
+
|
|
597
|
+
let target = document.querySelector('#App-base')
|
|
598
|
+
// get anchor already seen
|
|
599
|
+
anchorsComplete = this.getAnchorComplete()
|
|
600
|
+
|
|
601
|
+
const observer = new IntersectionObserver((entries) => {
|
|
602
|
+
// everytime the page passes a anchor
|
|
603
|
+
observer.observe(target)
|
|
604
|
+
|
|
605
|
+
entries.forEach((entry) => {
|
|
606
|
+
// when it's visable in the page
|
|
607
|
+
|
|
608
|
+
if (entry.isIntersecting) {
|
|
609
|
+
// get the target
|
|
610
|
+
|
|
611
|
+
this.anchorInfo = entry.target.classList
|
|
612
|
+
indexStrt = this.anchorInfo.value.indexOf('anchor-')
|
|
613
|
+
// work the string to get juste the anchor tag
|
|
614
|
+
// must be the same as the class
|
|
615
|
+
if (indexStrt == -1) {
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
anchorString = this.anchorInfo.value.substring(indexStrt)
|
|
620
|
+
|
|
621
|
+
indexEnd = anchorString.indexOf(' ')
|
|
622
|
+
if (indexEnd != -1) {
|
|
623
|
+
anchorComplete = anchorString.slice(0, indexEnd)
|
|
624
|
+
} else {
|
|
625
|
+
anchorComplete = anchorString
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
//get all the anchors of the current activity
|
|
629
|
+
const anchors_list = this.getAnchorsForActivity(
|
|
630
|
+
this.pageData.activityRef
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
//search for the current ancor
|
|
634
|
+
const anc = anchors_list.find((a) => a.anchorTag === anchorComplete)
|
|
635
|
+
//dispatch the current anchor to the store
|
|
636
|
+
if (anc) {
|
|
637
|
+
// update the store value for current section
|
|
638
|
+
// this.updateCurrentSection(anc)
|
|
639
|
+
// Ask bread scrumb to update its information
|
|
640
|
+
|
|
641
|
+
this.$bus.$emit('anchor-seen', anchorComplete)
|
|
642
|
+
} else {
|
|
643
|
+
if (import.meta.env.DEV)
|
|
644
|
+
console.warn(
|
|
645
|
+
`%c WARNING!>>> Anchor handeling: 👉${anchorComplete}👈 doesn't exist. Please provide a valid anchor.`,
|
|
646
|
+
'background: orange; color: white; display: block; border-radius:5px; margin:5px;'
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// if you didn't aldreay seen anchors
|
|
653
|
+
if (anchorsComplete != undefined) {
|
|
654
|
+
// look if you already saw this anchor
|
|
655
|
+
if (anc && !anchorsComplete.includes(anchorComplete)) {
|
|
656
|
+
// push it in the array them in the store
|
|
657
|
+
anchorsComplete.push(anchorComplete)
|
|
658
|
+
this.$set(this.userInteraction, 'anchors', anchorsComplete)
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
// if you never saw any anchor
|
|
662
|
+
// push it in the array them in the store
|
|
663
|
+
anchorsComplete = []
|
|
664
|
+
if (anc) anchorsComplete.push(anchorComplete)
|
|
665
|
+
this.$set(this.userInteraction, 'anchors', anchorsComplete)
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
}, options)
|
|
670
|
+
|
|
671
|
+
// observer call llok for each anchor in page
|
|
672
|
+
anchors.forEach((anchor) => {
|
|
673
|
+
observer.observe(anchor)
|
|
674
|
+
})
|
|
675
|
+
},
|
|
676
|
+
getAnchorComplete() {
|
|
677
|
+
const records = this.getPageInteraction(
|
|
678
|
+
this.pageData.activityRef,
|
|
679
|
+
this.pageData.id
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
// Verify if the element existe
|
|
683
|
+
if (
|
|
684
|
+
records &&
|
|
685
|
+
records.userInteraction &&
|
|
686
|
+
records.userInteraction.anchors
|
|
687
|
+
) {
|
|
688
|
+
// if you already saw anchors return them
|
|
689
|
+
|
|
690
|
+
return records.userInteraction.anchors
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
showChoiceBif(data) {
|
|
694
|
+
// check if you made a choice
|
|
695
|
+
if (
|
|
696
|
+
typeof this.getBifChoice === 'undefined' ||
|
|
697
|
+
Object.keys(this.getBifChoice).length === 0
|
|
698
|
+
) {
|
|
699
|
+
return data['A']
|
|
700
|
+
} else {
|
|
701
|
+
if (this.getBifChoice.choix) {
|
|
702
|
+
// get the choise from store
|
|
703
|
+
if (data.hasOwnProperty(this.getBifChoice.choix)) {
|
|
704
|
+
let choice = data[this.getBifChoice.choix]
|
|
705
|
+
//return choice
|
|
706
|
+
return choice
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
openPopup(data) {
|
|
712
|
+
this.$bus.$emit('open-popup', data)
|
|
713
|
+
},
|
|
714
|
+
/**
|
|
715
|
+
* @description Send to the bd that the media have been view by the user- Add the id of the media to the
|
|
716
|
+
* userInteraction of the page
|
|
717
|
+
*/
|
|
718
|
+
setMediaViewed(mediaID) {
|
|
719
|
+
// Should get the userData to check if current mediaElement has entry
|
|
720
|
+
let { mediasViewed } = this.userInteraction
|
|
721
|
+
|
|
722
|
+
// Should create entry for medias viewed in userInteraction if none
|
|
723
|
+
if (!mediasViewed) {
|
|
724
|
+
mediasViewed = []
|
|
725
|
+
|
|
726
|
+
this.userInteraction.mediasViewed = mediasViewed
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
//Should add ID in media viewed list if viewed for the 1st time
|
|
730
|
+
if (mediasViewed.includes(mediaID)) return
|
|
731
|
+
|
|
732
|
+
mediasViewed.push(mediaID)
|
|
733
|
+
|
|
734
|
+
this.userInteraction.mediasViewed = mediasViewed //Update the userInteraction data of the page
|
|
735
|
+
}, //
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* @description - Method to manage the playing state of media element in the page.
|
|
739
|
+
* Only one media should play at a time. When receives signal of new media playing
|
|
740
|
+
* get the reference of the previous media in play from the store and
|
|
741
|
+
* put it in stop state. Also closes the transcript sidebar if it is open.
|
|
742
|
+
* @param {HTMLElement} media - the actual media that is playing
|
|
743
|
+
*/
|
|
744
|
+
managePlayingMedia(media) {
|
|
745
|
+
if (!media) return
|
|
746
|
+
//Should get all the media of the page from store
|
|
747
|
+
const { mElements } = this.getCurrentPage
|
|
748
|
+
if (!mElements || !mElements.length) return
|
|
749
|
+
|
|
750
|
+
// // Should stop any media playing
|
|
751
|
+
mElements.forEach((m) => {
|
|
752
|
+
if (m.id == media.id) return
|
|
753
|
+
|
|
754
|
+
const attrKeys = Object.keys(m) //
|
|
755
|
+
const playbarInstance = m[attrKeys[3]]
|
|
756
|
+
const HTMLmediaElement = m[attrKeys[1]]
|
|
757
|
+
|
|
758
|
+
//Close the transcript sidebar if it is open
|
|
759
|
+
if (playbarInstance.transcriptEnabled) {
|
|
760
|
+
playbarInstance.toggleViewTranscript()
|
|
761
|
+
}
|
|
762
|
+
//Check if the media is playing to stop it. Playing state is given by the instance of the target play-bar
|
|
763
|
+
if (playbarInstance.isPlaying) {
|
|
764
|
+
HTMLmediaElement.pause() // target the HTMLmediaElement to control it state
|
|
765
|
+
playbarInstance.isPlaying = false //change this isPlaying value of the instance
|
|
766
|
+
|
|
767
|
+
playbarInstance.timer.pause() // pause the timer of the instance
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
},
|
|
771
|
+
/**
|
|
772
|
+
* @description - Method to manage the state of the transcript and fullscreen buttons.
|
|
773
|
+
* Disables those buttons on other media playbar when a transcript sidebar is open.
|
|
774
|
+
*
|
|
775
|
+
* @param {HTMLElement} media - the actual media that is playing
|
|
776
|
+
* @param {Boolean} transcriptShown - current status of the transcript sidebar
|
|
777
|
+
*/
|
|
778
|
+
onVideoTranscriptToggle(media, transcriptShown) {
|
|
779
|
+
if (!media) return
|
|
780
|
+
//Should get all the media of the page from store
|
|
781
|
+
const { mElements } = this.getCurrentPage
|
|
782
|
+
if (!mElements || !mElements.length) return
|
|
783
|
+
mElements.forEach((m) => {
|
|
784
|
+
if (m.id == media.id) return
|
|
785
|
+
const attrKeys = Object.keys(m)
|
|
786
|
+
const playbarInstance = m[attrKeys[3]]
|
|
787
|
+
playbarInstance.otherVideoTranscriptShown = transcriptShown
|
|
788
|
+
})
|
|
789
|
+
},
|
|
790
|
+
setInitialInteraction() {
|
|
791
|
+
const { activityRef, id: pageID } = this.pageData
|
|
792
|
+
const { userInteraction: previousInteraction = {} } =
|
|
793
|
+
this.getPageInteraction(activityRef, pageID)
|
|
794
|
+
|
|
795
|
+
return previousInteraction
|
|
796
|
+
},
|
|
797
|
+
saveQuizAnswers(el, quiz) {
|
|
798
|
+
if (!this.userInteraction.quizAnswers)
|
|
799
|
+
return (this.userInteraction.quizAnswers = { ...quiz })
|
|
800
|
+
const quizID = Object.keys(quiz)[0]
|
|
801
|
+
|
|
802
|
+
if (!this.userInteraction.quizAnswers[quizID])
|
|
803
|
+
return (this.userInteraction.quizAnswers = {
|
|
804
|
+
...this.userInteraction.quizAnswers,
|
|
805
|
+
...quiz
|
|
806
|
+
})
|
|
807
|
+
const quizValue = Object.values(quiz)[0]
|
|
808
|
+
this.userInteraction.quizAnswers[quizID] = quizValue
|
|
809
|
+
// const {}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
</script>
|
|
814
|
+
<style lang="scss">
|
|
815
|
+
.img-popUp {
|
|
816
|
+
position: relative;
|
|
817
|
+
|
|
818
|
+
.box-trigger {
|
|
819
|
+
position: absolute;
|
|
820
|
+
width: 100%;
|
|
821
|
+
height: 100%;
|
|
822
|
+
top: 0;
|
|
823
|
+
left: 0;
|
|
824
|
+
|
|
825
|
+
.btn {
|
|
826
|
+
position: absolute;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.debug-pageInfo {
|
|
832
|
+
position: fixed;
|
|
833
|
+
right: -250px;
|
|
834
|
+
bottom: 20px;
|
|
835
|
+
|
|
836
|
+
color: #333;
|
|
837
|
+
background-color: rgba(#eaabb6b3, 0.9);
|
|
838
|
+
|
|
839
|
+
display: flex;
|
|
840
|
+
flex-direction: row;
|
|
841
|
+
|
|
842
|
+
&.open {
|
|
843
|
+
right: 0;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.w-btn {
|
|
847
|
+
padding: 10px;
|
|
848
|
+
height: 100%;
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
background: rgba(#fff, 0.05);
|
|
851
|
+
|
|
852
|
+
&:hover {
|
|
853
|
+
background: rgba(#fff, 0.1);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.ctn {
|
|
858
|
+
padding: 24px;
|
|
859
|
+
width: 250px;
|
|
860
|
+
|
|
861
|
+
ul {
|
|
862
|
+
margin-left: 15px;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
</style>
|