fcad-core-dragon 2.0.0-beta.3 → 2.0.0-beta.4

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