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.
@@ -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">&lt;</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">&lt;</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>