fcad-core-dragon 2.0.0-beta.1 → 2.0.0-beta.10

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 (163) hide show
  1. package/.editorconfig +6 -31
  2. package/.prettierrc +11 -0
  3. package/.vscode/extensions.json +8 -0
  4. package/.vscode/settings.json +16 -0
  5. package/CHANGELOG +153 -0
  6. package/README.md +28 -43
  7. package/documentation/.vitepress/config.js +114 -0
  8. package/documentation/api-examples.md +49 -0
  9. package/documentation/composants/app-base-button.md +58 -0
  10. package/documentation/composants/app-base-error-display.md +59 -0
  11. package/documentation/composants/app-base-popover.md +68 -0
  12. package/documentation/composants/app-comp-audio.md +75 -0
  13. package/documentation/composants/app-comp-branch-buttons.md +111 -0
  14. package/documentation/composants/app-comp-button-progress.md +53 -0
  15. package/documentation/composants/app-comp-carousel.md +53 -0
  16. package/documentation/composants/app-comp-container.md +53 -0
  17. package/documentation/composants/app-comp-input-checkbox-next.md +42 -0
  18. package/documentation/composants/app-comp-input-dropdown-next.md +34 -0
  19. package/documentation/composants/app-comp-input-radio-next.md +39 -0
  20. package/documentation/composants/app-comp-input-text-next.md +35 -0
  21. package/documentation/composants/app-comp-input-text-table-next.md +34 -0
  22. package/documentation/composants/app-comp-input-text-to-fill-dropdown-next.md +53 -0
  23. package/documentation/composants/app-comp-input-text-to-fill-next.md +31 -0
  24. package/documentation/composants/app-comp-jauge.md +31 -0
  25. package/documentation/composants/app-comp-menu-item.md +55 -0
  26. package/documentation/composants/app-comp-menu.md +29 -0
  27. package/documentation/composants/app-comp-navigation.md +41 -0
  28. package/documentation/composants/app-comp-note-call.md +53 -0
  29. package/documentation/composants/app-comp-note-credit.md +53 -0
  30. package/documentation/composants/app-comp-play-bar-next.md +53 -0
  31. package/documentation/composants/app-comp-pop-up-next.md +93 -0
  32. package/documentation/composants/app-comp-quiz-next.md +235 -0
  33. package/documentation/composants/app-comp-quiz-recall.md +53 -0
  34. package/documentation/composants/app-comp-svg-next.md +53 -0
  35. package/documentation/composants/app-comp-table-of-content.md +50 -0
  36. package/documentation/composants/app-comp-video-player.md +82 -0
  37. package/documentation/composants.md +46 -0
  38. package/documentation/composants_critiques/ModelPageComposant.md +53 -0
  39. package/documentation/composants_critiques/app-base-module.md +43 -0
  40. package/documentation/composants_critiques/app-base-page.md +48 -0
  41. package/documentation/composants_critiques/app-base.md +311 -0
  42. package/documentation/composants_critiques/main.md +15 -0
  43. package/documentation/demarrage.md +50 -0
  44. package/documentation/deploiement.md +58 -0
  45. package/documentation/index.md +33 -0
  46. package/documentation/markdown-examples.md +85 -0
  47. package/documentation/public/npm_version.png +0 -0
  48. package/documentation/public/vite.svg +15 -0
  49. package/documentation/public/vuejs.svg +2 -0
  50. package/documentation/public/vuetify.svg +6 -0
  51. package/eslint.config.js +60 -0
  52. package/package.json +43 -47
  53. package/src/$locales/en.json +86 -108
  54. package/src/$locales/fr.json +66 -127
  55. package/src/assets/data/onboardingMessages.json +1 -1
  56. package/src/components/AppBase.vue +960 -405
  57. package/src/components/AppBaseButton.test.js +21 -0
  58. package/src/components/AppBaseButton.vue +42 -10
  59. package/src/components/AppBaseErrorDisplay.vue +207 -189
  60. package/src/components/AppBaseFlipCard.vue +1 -0
  61. package/src/components/AppBaseModule.vue +769 -977
  62. package/src/components/AppBasePage.vue +635 -81
  63. package/src/components/AppBasePopover.vue +41 -0
  64. package/src/components/AppBaseSkeleton.vue +66 -0
  65. package/src/components/AppCompAudio.vue +256 -0
  66. package/src/components/AppCompBranchButtons.vue +79 -153
  67. package/src/components/AppCompButtonProgress.vue +21 -36
  68. package/src/components/AppCompCarousel.vue +231 -87
  69. package/src/components/{AppCompTranscript.vue → AppCompContainer.vue} +12 -2
  70. package/src/components/AppCompInputCheckBoxNx.vue +323 -0
  71. package/src/components/AppCompInputDropdownNx.vue +299 -0
  72. package/src/components/AppCompInputRadioNx.vue +284 -0
  73. package/src/components/AppCompInputTextNx.vue +153 -0
  74. package/src/components/AppCompInputTextTableNx.vue +202 -0
  75. package/src/components/AppCompInputTextToFillDropdownNx.vue +340 -0
  76. package/src/components/AppCompInputTextToFillNx.vue +313 -0
  77. package/src/components/AppCompJauge.vue +36 -10
  78. package/src/components/AppCompMenu.vue +246 -32
  79. package/src/components/AppCompMenuItem.vue +87 -21
  80. package/src/components/AppCompNavigation.vue +470 -447
  81. package/src/components/AppCompNoteCall.vue +93 -58
  82. package/src/components/AppCompNoteCredit.vue +423 -96
  83. package/src/components/AppCompPlayBarNext.vue +2288 -0
  84. package/src/components/AppCompPopUpNext.vue +504 -0
  85. package/src/components/AppCompQuizNext.vue +510 -0
  86. package/src/components/AppCompQuizRecall.vue +199 -99
  87. package/src/components/AppCompSVGNext.vue +346 -0
  88. package/src/components/AppCompSettingsMenu.vue +17 -16
  89. package/src/components/AppCompTableOfContent.vue +262 -99
  90. package/src/components/AppCompVideoPlayer.vue +183 -142
  91. package/src/components/BaseModule.vue +8 -20
  92. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  93. package/src/components/tests__/useTimer.spec.js +91 -0
  94. package/src/composables/useIdleDetector.js +56 -0
  95. package/src/composables/useQuiz.js +89 -0
  96. package/src/composables/useTimer.js +172 -0
  97. package/src/directives/nvdaFix.js +53 -0
  98. package/src/externalComps/ModuleView.vue +22 -0
  99. package/src/externalComps/SummaryView.vue +91 -0
  100. package/src/main.js +397 -148
  101. package/src/module/stores/appStore.js +947 -0
  102. package/src/module/xapi/ADL.js +241 -60
  103. package/src/module/xapi/Crypto/Hasher.js +8 -8
  104. package/src/module/xapi/Crypto/WordArray.js +6 -6
  105. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +4 -4
  106. package/src/module/xapi/Crypto/algorithms/C_algo.js +14 -18
  107. package/src/module/xapi/Crypto/algorithms/HMAC.js +1 -1
  108. package/src/module/xapi/Crypto/algorithms/SHA1.js +1 -1
  109. package/src/module/xapi/Crypto/encoders/Base.js +7 -7
  110. package/src/module/xapi/Crypto/encoders/Base64.js +3 -3
  111. package/src/module/xapi/Crypto/encoders/Hex.js +2 -2
  112. package/src/module/xapi/Crypto/encoders/Latin1.js +3 -3
  113. package/src/module/xapi/Crypto/encoders/Utf8.js +3 -3
  114. package/src/module/xapi/Statement/index.js +3 -3
  115. package/src/module/xapi/launch.js +10 -10
  116. package/src/module/xapi/utils.js +17 -17
  117. package/src/module/xapi/wrapper.js +219 -214
  118. package/src/module/xapi/xapiStatement.js +29 -29
  119. package/src/plugins/analytics.js +34 -0
  120. package/src/plugins/bus.js +7 -2
  121. package/src/plugins/gsap.js +5 -7
  122. package/src/plugins/helper.js +97 -34
  123. package/src/plugins/i18n.js +13 -18
  124. package/src/plugins/idb.js +45 -30
  125. package/src/plugins/save.js +1 -1
  126. package/src/plugins/scorm.js +15 -15
  127. package/src/plugins/xapi.js +2 -2
  128. package/src/public/index.html +22 -10
  129. package/src/router/index.js +29 -13
  130. package/src/router/routes.js +29 -54
  131. package/src/shared/generalfuncs.js +186 -30
  132. package/src/shared/validators.js +809 -40
  133. package/vitest.config.js +19 -0
  134. package/.eslintignore +0 -29
  135. package/.eslintrc.js +0 -86
  136. package/.prettierrc.js +0 -5
  137. package/babel.config.js +0 -3
  138. package/src/components/AppBaseDragChoice.vue +0 -91
  139. package/src/components/AppBaseDropZone.vue +0 -112
  140. package/src/components/AppCompBif.vue +0 -120
  141. package/src/components/AppCompDragAndDrop.vue +0 -339
  142. package/src/components/AppCompInputAssociation.vue +0 -332
  143. package/src/components/AppCompInputCheckBox.vue +0 -227
  144. package/src/components/AppCompInputDropdown.vue +0 -184
  145. package/src/components/AppCompInputRadio.vue +0 -169
  146. package/src/components/AppCompInputTextBox.vue +0 -91
  147. package/src/components/AppCompInputTextTable.vue +0 -155
  148. package/src/components/AppCompInputTextToFillDropdown.vue +0 -255
  149. package/src/components/AppCompInputTextToFillText.vue +0 -164
  150. package/src/components/AppCompMediaPlayer.vue +0 -397
  151. package/src/components/AppCompPlayBar.vue +0 -1319
  152. package/src/components/AppCompPopUp.vue +0 -522
  153. package/src/components/AppCompPopover.vue +0 -27
  154. package/src/components/AppCompQuiz.vue +0 -2989
  155. package/src/components/AppCompSVG.vue +0 -309
  156. package/src/mixins/$pageMixins.js +0 -459
  157. package/src/mixins/$quizMixins.js +0 -456
  158. package/src/mixins/timerMixin.js +0 -156
  159. package/src/module/store.js +0 -895
  160. package/src/plugins/timeManager.js +0 -77
  161. package/src/routes_bckp.js +0 -313
  162. package/src/routes_static.js +0 -344
  163. package/vue.config.js +0 -83
@@ -1,57 +1,64 @@
1
1
  <!--
2
- @ Description: Root component for creation a page for an activity
2
+ @ Description: a prototype to test refactoring of pageMixin paradigm and fonctionality
3
3
  @ What it does: The component received a data and validate the type of page that will be created
4
4
  The component update the store information for the type of page created and also the timeline
5
5
  @Note: Page not created with this component will not have the data automatically traked by the application
6
6
  -->
7
7
  <template>
8
- <div :id="page.id" class="app-page" role="main">
9
- <slot v-if="!errorPage.length" name="content">
10
- Page
11
- </slot>
12
- <b-row v-else-if="errorPage.length > 0" class="warning-error">
13
- <b-col class="box-error">
14
- <div class="yellow-box">
15
- <h2>
16
- <b-icon icon="exclamation-triangle" />
17
- Erreure: Création de PAGE!
18
- </h2>
19
- <p>
20
- Vous avez une/des erreur(s) dans la création de votre PAGE. Veuillez
21
- corriger les erreurs ci-dessous:
22
- <br />
23
- </p>
24
- <ul>
25
- <li v-for="(err, index) in errorPage" :key="`error_type_${index}`">
26
- {{ err }}
27
- </li>
28
- </ul>
29
- </div>
30
- <div class="box">
31
- <p class="doc">
32
- Visitez
33
- <a href="https://fcaddocumentation.netlify.app/" target="blank">
34
- la documentation
35
- </a>
36
- pour plus de details
37
- </p>
38
- </div>
39
- </b-col>
40
- </b-row>
8
+ <div :id="pageData.id" :key="pageData.id" class="app-page" role="main">
9
+ <div v-if="appDebugMode" class="debug-pageInfo" :class="{open : push}" >
10
+ <button class="w-btn" @click="push = !push"> < </button>
11
+ <div class="ctn">Activity info:
12
+
13
+ Route : {{ $route.path }}<br/>
14
+
15
+ Activity id : {{ getDebugModeInfo.id }}<br/>
16
+ Nombre de page : {{ getDebugModeInfo.size }}<br/>
17
+ Page complete :
18
+ <ul>
19
+ <li v-for="(value, key) in getDebugModeInfo.progression" :key="key">
20
+ {{value }}
21
+ </li>
22
+ </ul>
23
+ </div>
24
+
25
+ </div>
26
+ <slot v-if="!errorPage.length" name="content">Page</slot>
27
+ <app-base-error-display
28
+ v-else
29
+ :error-group="'component'"
30
+ :error-title="'ERREUR: CRÉATION DE LA PAGE'"
31
+ :errors-list="errorPage"
32
+ :error-text="`Vous avez une/des erreur(s) dans la création de votre PAGE. Veuillez
33
+ corriger les erreurs ci-dessous:`"
34
+ />
35
+ <span
36
+ id="page_info"
37
+ ref="page_info"
38
+ class="sr-only"
39
+ aria-hidden="true"
40
+ ></span>
41
+ <div id="hiddenAlertContainer" role="alert" class="sr-only"></div>
41
42
  </div>
42
43
  </template>
43
44
 
44
45
  <script>
45
- import { mapGetters } from 'vuex'
46
+ import { computed } from 'vue'
47
+ import { useAppStore } from '../module/stores/appStore'
48
+ import { mapState, mapActions } from 'pinia'
49
+
46
50
  export default {
51
+ provide() {
52
+ return { userInteraction: computed(() => this.userInteraction) }
53
+ },
47
54
  props: {
48
- page: {
55
+ pageData: {
49
56
  type: Object,
50
57
  required: true,
51
58
 
52
59
  validator(value) {
53
60
  let isValid = true
54
- if (process.env.NODE_ENV === 'development') {
61
+ if (import.meta.env.DEV) {
55
62
  const requiredPageKeys = ['id', 'activityRef', 'type']
56
63
  let requiredTypeValues = [
57
64
  'pg_normal',
@@ -86,23 +93,100 @@ export default {
86
93
  }
87
94
  }
88
95
  },
96
+ setup(props) {
97
+ const store = useAppStore()
98
+ const { activityRef, id: pageID, type: pageType } = props.pageData
99
+
100
+ //Getting initial existing userIntaction from store
101
+ const { userInteraction: previousInteraction = {} } =
102
+ store.getPageInteraction(activityRef, pageID)
103
+
104
+ return { store, previousInteraction, pageID, pageType }
105
+ },
106
+
89
107
  data() {
90
108
  return {
91
- pageData: null,
92
- getHash: null
109
+ getHash: null,
110
+ //========PageMisins values ===================
111
+ userInteraction: this.previousInteraction,
112
+ state: null,
113
+ anchorEnable: true,
114
+ anchorInfo: null,
115
+ drModeActive: null,
116
+ error: null,
117
+ id: this.pageID,
118
+ type: this.pageType,
119
+ push:false
120
+
121
+ //======== End PageMisins values ===================
93
122
  }
94
123
  },
95
124
  computed: {
96
- ...mapGetters([
97
- 'getRouteHistory',
98
- 'getAllActivitiesState',
99
- 'getAllCompleted',
100
- 'getErrorChoiceDetect'
125
+ ...mapState(useAppStore, [
126
+ 'getDataNoteCredit',
127
+ 'getAllActivities',
128
+ 'getApplicationSettings',
129
+ 'getAppDebugMode'
101
130
  ]),
131
+
132
+ appDebugMode() {
133
+ return this.getAppDebugMode
134
+ },
135
+ getRouteHistory() {
136
+ return this.store.getRouteHistory
137
+ },
138
+ getAllActivitiesState() {
139
+ return this.store.getAllActivitiesState
140
+ },
141
+
142
+ getAllCompleted() {
143
+ return this.store.getAllCompleted
144
+ },
145
+ getErrorChoiceDetect() {
146
+ return this.store.getErrorChoiceDetect
147
+ },
148
+ getUserInteraction() {
149
+ return this.store.getUserInteraction
150
+ },
151
+ getPageInteraction() {
152
+ return this.store.getPageInteraction
153
+ },
154
+
155
+ getCurrentPage() {
156
+ return this.store.getCurrentPage
157
+ },
158
+
159
+ getCurrentBranching() {
160
+ return this.store.getCurrentBranching
161
+ },
162
+ updateCurrentBranching() {
163
+ return this.store.updateCurrentBranching
164
+ },
165
+ getModuleInfo() {
166
+ return this.store.getModuleInfo
167
+ },
168
+
169
+ getAllActivities() {
170
+ return this.store.getAllActivities()
171
+ },
172
+ getConnectionInfo() {
173
+ return this.store.getConnectionInfo
174
+ },
175
+
176
+ getAnchorsForActivity() {
177
+ return this.store.getAnchorsForActivity()
178
+ },
179
+ getBifChoice() {
180
+ return this.store.getBifChoice
181
+ },
182
+
183
+ isBranchingPage() {
184
+ return this.$route.meta.type === 'branching' && this.type !== 'pg_branch'
185
+ },
186
+
187
+ //================================================
102
188
  settingsOptions() {
103
- return this.$store.state.isDr
104
- ? this.settingsOptionsEL
105
- : this.settingsOptionsELPlus
189
+ return this.settingsOptionsELPlus
106
190
  },
107
191
 
108
192
  settingsSelected() {
@@ -112,7 +196,7 @@ export default {
112
196
 
113
197
  errorPage() {
114
198
  let err = false
115
- if (process.env.NODE_ENV === 'development') {
199
+ if (import.meta.env.DEV) {
116
200
  const requiredPageKeys = ['id', 'activityRef', 'type']
117
201
  const errorList = []
118
202
  let count = 0
@@ -126,25 +210,25 @@ export default {
126
210
 
127
211
  requiredPageKeys.forEach((key) => {
128
212
  // required key is missing in $data that was passed for the page
129
- if (!Object.keys(this.page).includes(key)) {
213
+ if (!Object.keys(this.pageData).includes(key)) {
130
214
  errorList.push(`Missing page ${key} in $data`)
131
215
  }
132
216
  // Validator value for type
133
217
  else if (
134
218
  key === 'type' &&
135
- !requiredTypeValues.includes(this.page[key])
219
+ !requiredTypeValues.includes(this.pageData[key])
136
220
  ) {
137
221
  errorList.push(`Invalid value assigment for page type in $data`)
138
222
  } else if (count < 1) {
139
223
  let errString = null
140
224
  const requiredValues = ['video', 'audio']
141
- switch (this.page.type) {
225
+ switch (this.type) {
142
226
  // validation for animation page type content
143
227
 
144
228
  case 'pg_animation':
145
- if (!this.page.animation)
229
+ if (!this.pageData.animation)
146
230
  errString = `Missing >>information in $data<< for animation type page`
147
- if (this.page.animation && !this.page.animation.refName)
231
+ if (this.pageData.animation && !this.pageData.animation.refName)
148
232
  errString = `Missing >>refName<< for your animation `
149
233
  if (errString) {
150
234
  errorList.push(errString)
@@ -157,8 +241,8 @@ export default {
157
241
  // valdation for media page type content
158
242
  case 'pg_media':
159
243
  if (
160
- !this.page.mediaData ||
161
- Object.keys(this.page.mediaData).length < 1
244
+ !this.pageData.mediaData ||
245
+ Object.keys(this.pageData.mediaData).length < 1
162
246
  ) {
163
247
  errString = `Missing >>media information<< for media type page`
164
248
  errorList.push(errString)
@@ -168,13 +252,8 @@ export default {
168
252
  )
169
253
  } else {
170
254
  // validation for mediaData content. Must have mType && mSource
171
- const {
172
- mType,
173
- mSources,
174
- mSubtitle,
175
- mTranscript,
176
- mPoster
177
- } = this.page.mediaData
255
+ const { mType, mSources, mSubtitle, mTranscript, mPoster } =
256
+ this.pageData.mediaData
178
257
  if (!mType || !mSources) {
179
258
  errString = `Missing key(s) in mediaData declaration for media`
180
259
  errorList.push(errString)
@@ -251,9 +330,76 @@ export default {
251
330
  if (errorList.length > 0) err = errorList
252
331
  }
253
332
  return err
333
+ },
334
+
335
+ pgNumber() {
336
+ if (!this.pageData.id) return
337
+ let n = parseInt(this.pageData.id.replace('P', ''))
338
+ return n
339
+ },
340
+
341
+ A11yPageInfo() {
342
+ if (!this.$route || !this.$route.path) return ''
343
+
344
+ let A11YTxt = ''
345
+ const path = this.$route.path
346
+ let reg = /[/|-]/g
347
+
348
+ A11YTxt = path.replaceAll(reg, ' ') // replace all '/' and '-' by space
349
+
350
+ if (A11YTxt.includes('activite'))
351
+ A11YTxt = A11YTxt.replace('activite', this.$t('text.activity'))
352
+
353
+ return A11YTxt.toLowerCase()
354
+ },
355
+
356
+ getDebugModeInfo(){
357
+ const allActivitiesState = JSON.parse(
358
+ JSON.stringify(this.getAllActivitiesState)
359
+ )
360
+ let size = allActivitiesState[this.pageData.activityRef]
361
+ ? allActivitiesState[this.pageData.activityRef].size
362
+ : 0
363
+
364
+ let Pprogress = allActivitiesState[this.pageData.activityRef].progressions
365
+
366
+ let info = {
367
+ id: this.pageData.activityRef,
368
+ size : size,
369
+ progression : Pprogress
370
+ }
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
254
401
  }
255
402
  },
256
- watch: {},
257
403
  created() {
258
404
  /*
259
405
  * Create a custome object for this page that will be added in the collection
@@ -262,29 +408,406 @@ export default {
262
408
  * will update the store information for the currentPage
263
409
  * wil update the store information for the currentTimeline (GSap animation)
264
410
  */
265
- if (this.page && this.errorPage === false) {
266
- this.pageData = this.page
267
- // Update the store with the current page information
268
- this.$store.dispatch('updateCurrentPage', {
269
- activity_Id: this.page.activityRef,
270
- page_Id: this.page.id
271
- })
411
+
412
+ if (this.pageData && this.errorPage === false) {
272
413
  // Handeling presence of animation in the page
273
414
  if (this.pageData.animation) {
274
415
  // update the store with the information of currentTimeline
275
- this.$store.dispatch(
276
- 'updateCurrentTimeline',
277
- this.$gsap.timeline({ paused: true })
278
- )
279
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()
280
461
  }
281
462
 
282
- //window.scrollTo(0, 0)
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
283
470
  },
284
- mounted() {},
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
+ },
285
542
 
286
- beforeDestroy() {
287
- this.pageData = null
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
+ }
288
811
  }
289
812
  }
290
813
  </script>
@@ -305,8 +828,39 @@ export default {
305
828
  }
306
829
  }
307
830
 
308
- .overlay-close-widget {
309
- width: 100%;
310
- height: 100%;
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
+ }
311
865
  }
312
866
  </style>