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

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 (78) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +13 -18
  2. package/bk.scss +117 -0
  3. package/package.json +23 -39
  4. package/src/$locales/en.json +30 -16
  5. package/src/$locales/fr.json +29 -16
  6. package/src/components/AppBase.vue +740 -305
  7. package/src/components/AppBaseButton.vue +33 -5
  8. package/src/components/AppBaseErrorDisplay.vue +43 -35
  9. package/src/components/AppBaseModule.vue +447 -623
  10. package/src/components/AppBasePage.vue +37 -25
  11. package/src/components/AppCompAudio.vue +266 -0
  12. package/src/components/AppCompBranchButtons.vue +52 -63
  13. package/src/components/AppCompButtonProgress.vue +1 -16
  14. package/src/components/AppCompCarousel.vue +43 -39
  15. package/src/components/AppCompInputCheckBox.vue +9 -3
  16. package/src/components/AppCompInputDropdown.vue +2 -4
  17. package/src/components/AppCompInputRadio.vue +8 -15
  18. package/src/components/AppCompInputTextTable.vue +15 -12
  19. package/src/components/AppCompInputTextToFillDropdown.vue +16 -14
  20. package/src/components/AppCompInputTextToFillText.vue +2 -2
  21. package/src/components/AppCompJauge.vue +13 -1
  22. package/src/components/AppCompMenu.vue +203 -10
  23. package/src/components/AppCompMenuItem.vue +20 -3
  24. package/src/components/AppCompNavigation.vue +351 -355
  25. package/src/components/AppCompNoteCall.vue +62 -47
  26. package/src/components/AppCompNoteCredit.vue +182 -79
  27. package/src/components/AppCompPlayBar.vue +975 -1023
  28. package/src/components/AppCompPlayBarProgress.vue +73 -0
  29. package/src/components/AppCompPopUp.vue +175 -114
  30. package/src/components/AppCompQuiz.vue +67 -81
  31. package/src/components/AppCompQuizRecall.vue +32 -5
  32. package/src/components/AppCompSVG.vue +66 -40
  33. package/src/components/AppCompSettingsMenu.vue +6 -8
  34. package/src/components/AppCompTableOfContent.vue +166 -45
  35. package/src/components/AppCompVideoPlayer.vue +154 -110
  36. package/src/components/BaseModule.vue +21 -17
  37. package/src/main.js +124 -88
  38. package/src/mixins/$mediaMixins.js +827 -0
  39. package/src/mixins/$pageMixins.js +65 -109
  40. package/src/mixins/$quizMixins.js +12 -26
  41. package/src/mixins/timerMixin.js +8 -9
  42. package/src/module/store.js +187 -68
  43. package/src/module/xapi/ADL.js +90 -53
  44. package/src/module/xapi/Crypto/Hasher.js +8 -8
  45. package/src/module/xapi/Crypto/WordArray.js +6 -6
  46. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +4 -4
  47. package/src/module/xapi/Crypto/algorithms/C_algo.js +14 -18
  48. package/src/module/xapi/Crypto/algorithms/HMAC.js +1 -1
  49. package/src/module/xapi/Crypto/algorithms/SHA1.js +1 -1
  50. package/src/module/xapi/Crypto/encoders/Base.js +7 -7
  51. package/src/module/xapi/Crypto/encoders/Base64.js +3 -3
  52. package/src/module/xapi/Crypto/encoders/Hex.js +2 -2
  53. package/src/module/xapi/Crypto/encoders/Latin1.js +3 -3
  54. package/src/module/xapi/Crypto/encoders/Utf8.js +3 -3
  55. package/src/module/xapi/Statement/index.js +1 -1
  56. package/src/module/xapi/launch.js +10 -10
  57. package/src/module/xapi/utils.js +17 -17
  58. package/src/module/xapi/wrapper.js +123 -50
  59. package/src/module/xapi/xapiStatement.js +29 -29
  60. package/src/plugins/helper.js +8 -9
  61. package/src/plugins/i18n.js +23 -10
  62. package/src/plugins/scorm.js +14 -14
  63. package/src/router/index.js +3 -4
  64. package/src/router/routes.js +10 -30
  65. package/src/shared/generalfuncs.js +31 -24
  66. package/src/shared/validators.js +730 -20
  67. package/.prettierrc.js +0 -5
  68. package/babel.config.js +0 -3
  69. package/src/components/AppBaseDragChoice.vue +0 -91
  70. package/src/components/AppBaseDropZone.vue +0 -112
  71. package/src/components/AppCompBif.vue +0 -120
  72. package/src/components/AppCompDragAndDrop.vue +0 -339
  73. package/src/components/AppCompInputAssociation.vue +0 -332
  74. package/src/components/AppCompMediaPlayer.vue +0 -397
  75. package/src/plugins/timeManager.js +0 -77
  76. package/src/routes_bckp.js +0 -313
  77. package/src/routes_static.js +0 -344
  78. package/vue.config.js +0 -83
@@ -1,12 +1,17 @@
1
1
  <!--
2
2
  *@ Description: This component is used as main container to display application and containt to display
3
3
  *@ What it does: The component fetch the data for the page to display on navigation.
4
- * controle and scorm & xAPI logic and communication
4
+ *
5
5
  *@Note :Must be used
6
6
  -->
7
7
 
8
8
  <template>
9
9
  <div fluid class="module">
10
+ <div
11
+ id="page_info_section"
12
+ aria-labelledby="page_info"
13
+ aria-live="true"
14
+ ></div>
10
15
  <a class="skip-link" href="" @click.prevent="skipToMain">
11
16
  {{ $t('message.skip_content') }}
12
17
  </a>
@@ -23,17 +28,10 @@
23
28
  <!------------------------ Nav for FullMode ---------------->
24
29
 
25
30
  <app-comp-navigation
26
- :app-status="isReady"
31
+ :app-status="appReady"
27
32
  :auto-navigate="theNavigationBetweenActivity"
28
33
  />
29
34
  </b-navbar>
30
-
31
- <portal-target
32
- v-show="$store.state.$appStore.isDr"
33
- id="DR_anchors_progress_wrapper"
34
- name="DR_anchors_progress"
35
- ></portal-target>
36
-
37
35
  <base-module :m-data="$data">
38
36
  <b-container
39
37
  id="wrapper-content"
@@ -41,21 +39,11 @@
41
39
  :class="{ active: moduleConfig.videoFull }"
42
40
  class="scroll-bar"
43
41
  >
44
- <div
45
- v-show="!appReady"
46
- id="Loading"
47
- key="Loading"
48
- :class="{ fadeIn: !appReady && changePage }"
49
- >
50
- <!-- App ready: {{ appReady }} -->
51
- <div class="overlay" />
52
- <div class="box-loading-anime disableBlur">
53
- <img src="../assets/img/BeanEater-1s-200px.svg" alt="loading" />
54
- </div>
55
- </div>
56
42
  <div class="box">
57
- <router-view v-show="appReady" ref="main" :key="$route.fullPath" />
43
+ <router-view ref="main" :key="$route.fullPath" />
44
+ <!-- <router-view v-show="appReady" ref="main" :key="$route.fullPath" /> -->
58
45
  </div>
46
+
59
47
  <portal-target
60
48
  id="primary_nav_wrapper"
61
49
  name="primary_nav"
@@ -64,43 +52,9 @@
64
52
  </base-module>
65
53
 
66
54
  <!------------------POPUP QUICK -------------------------->
67
- <app-comp-pop-up v-slot:content />
55
+ <app-comp-pop-up #content />
68
56
  <!------------------END USE POP UP-------------------------->
69
- <!------------------Settings Splash-screeen----------------------------->
70
- <!-- <b-modal
71
- id="splashscreen"
72
- ref="splashscreen"
73
- :title="$t('label.splashscreen')"
74
- size="lg"
75
- ok-only
76
- no-stacking
77
- :ok-title="$t('user_settings.close')"
78
- :header-close-label="$t('button.closePopUp')"
79
- >
80
- <span>
81
- {{ $t('message.splash_welcome') }}
82
- </span>
83
- <b-form-group
84
- id="splash-form"
85
- v-slot="{ ariaDescribedby }"
86
- :label="$t('user_settings.title')"
87
- >
88
- <b-form-checkbox
89
- v-for="(setting, index) in Object.entries(settingsSelected)"
90
- :key="index"
91
- v-model="settingsSelected[setting[0]]"
92
- :aria-describedby="ariaDescribedby"
93
- switch
94
- stacked
95
- size="lg"
96
- :name="`parameter-${setting[0]}`"
97
- :text-field="$t(`user_settings.${setting[0]}`)"
98
- >
99
- <span>{{ $t(`user_settings.${setting[0]}`) }}</span>
100
- </b-form-checkbox>
101
- </b-form-group>
102
- </b-modal> -->
103
- <!------------------END Settings splash-screen-------------------------->
57
+
104
58
  <!--------------RIGHT SIDEBAR (for display of extra contents)------------>
105
59
 
106
60
  <b-sidebar
@@ -108,12 +62,22 @@
108
62
  id="right-sidebar"
109
63
  ref="right-sidebar"
110
64
  :key="dynamicSidebarContent._id"
65
+ :sidebar-class="{
66
+ 'v-media': dynamicSidebarContent._context === 'ctxTranscript'
67
+ }"
111
68
  v-bind="dynamicSidebarContent._props"
112
69
  :visible="rightSidebarVisible"
113
70
  :aria-label="dynamicSidebarContent._label"
114
- @hidden="closeSidebar(dynamicSidebarContent._context)"
71
+ @hidden="
72
+ closeSidebar(
73
+ dynamicSidebarContent._context,
74
+ dynamicSidebarContent._container
75
+ ? dynamicSidebarContent._container
76
+ : null
77
+ )
78
+ "
115
79
  >
116
- <template #header="{hide}">
80
+ <template #header="{ hide }">
117
81
  <button @click="hide">
118
82
  <svg width="32" height="32">
119
83
  <use href="#close-square-icon" fill="red" />
@@ -129,6 +93,7 @@
129
93
  >
130
94
  <component
131
95
  :is="dynamicSidebarContent._component"
96
+ class="v-media"
132
97
  v-bind="{ ...dynamicSidebarContent._comProps }"
133
98
  />
134
99
  </transition>
@@ -154,12 +119,7 @@ export default {
154
119
  type: Object,
155
120
  default: () => {
156
121
  return {
157
- id: 'mod_001',
158
- title: null, // edit to set the title of the lesson
159
- description: null,
160
- introActive: false, // Controle use INTODUCTION page in the Lesson. set to false if there is no introduction
161
- concluActive: false, // Controle use CONCLUSION page in the Lesson. set to false if there is no conclusion
162
- allowNavigationToActivity: false, // set Previous/Next can allow navigation between activities. Set to false if do not want navigation between activities with Previous/Next,É
122
+ allowNavigationToActivity: false, // set Previous/Next can allow navigation between activities. Set to false if do not want navigation between activities with Previous/Next
163
123
  main: ''
164
124
  }
165
125
  }
@@ -171,7 +131,6 @@ export default {
171
131
  videoFull: null,
172
132
  routeData: [],
173
133
  changePage: false,
174
- appReady: false,
175
134
  randKey: Math.floor(Math.random() * 10001),
176
135
  popupIsOpen: null,
177
136
  hidePlayBar: false, // Controle visibility of the play bar. set to true to hide play bar
@@ -190,8 +149,11 @@ export default {
190
149
  infocusTabIndex: null,
191
150
  transcriptVisible: false,
192
151
  transcriptContent: null,
152
+ transcriptContainer: null,
193
153
  branchingVisible: false,
194
- mediaToShow: null
154
+ // mediaToShow: null,
155
+ lessonCompletionStatus: false,
156
+ checkedDataFromServer: 0
195
157
  }
196
158
  },
197
159
  computed: {
@@ -208,37 +170,36 @@ export default {
208
170
  'getConnectionInfo',
209
171
  'hasMediaElOrTimeline',
210
172
  'getModuleChildren',
211
- 'getAllActivities',
212
173
  'getPageData',
213
174
  'getMenuSettings',
214
175
  'getCurrentSection',
215
176
  'getRouteHistory',
216
177
  'getOnboardingEnabled',
217
- 'getApplicationSettings'
178
+ 'getApplicationSettings',
179
+ 'getDataFromServer',
180
+ 'getSkipMenu' //Must Check This LINE WITH CYNTHIA
218
181
  ]),
219
182
  isMenu() {
220
183
  return this.$route.name === 'menu'
221
184
  },
222
- isReady() {
223
- let readyState
224
- if (this.getAppStatus === 'loading') readyState = false
225
- else if (this.getAppStatus === 'ready') readyState = true
226
- return readyState
185
+ appReady() {
186
+ return this.getAppStatus === 'ready' ? true : false
227
187
  },
228
188
 
229
189
  hasMedia() {
230
190
  return typeof this.hasMediaElOrTimeline === 'object'
231
191
  },
192
+ activityHasChanged() {
193
+ const rd = this.routeData.toReversed()
232
194
 
233
- displayLang() {
234
- let lang = false
235
- const displayList = ['en-US', 'fr-FR', 'es-ES'] // list of xapi verbs language display
236
-
237
- if (this.getModuleInfo.packageType === 'xapi') {
238
- lang = displayList.find((l) => l.includes(this.$i18n.locale))
239
- }
240
- return lang
195
+ if (
196
+ rd.length <= 1 ||
197
+ rd[1].activity_ref !== this.$route.meta.activity_ref
198
+ )
199
+ return true
200
+ else return false
241
201
  },
202
+
242
203
  /**
243
204
  * @description Set the id the module
244
205
  */
@@ -285,7 +246,6 @@ export default {
285
246
  */
286
247
  theNavigationBetweenActivity() {
287
248
  let navBwteenActivity = false
288
-
289
249
  if (this.moduleConfig.allowNavigationToActivity)
290
250
  navBwteenActivity = this.moduleConfig.allowNavigationToActivity
291
251
  return navBwteenActivity
@@ -319,8 +279,8 @@ export default {
319
279
  if (this.transcriptVisible) {
320
280
  _label =
321
281
  this.$i18n.locale === 'fr'
322
- ? 'contenu de la transciption'
323
- : 'content of the transcript'
282
+ ? 'Contenu de la transcription'
283
+ : 'Content of the transcript'
324
284
 
325
285
  sidebarSettings = {
326
286
  _props: {
@@ -332,8 +292,11 @@ export default {
332
292
  no_close_on_backdrop: false
333
293
  },
334
294
  _component: AppCompTranscript,
335
- _comProps: { content: this.transcriptContent },
295
+ _comProps: {
296
+ content: this.transcriptContent
297
+ },
336
298
  _context: 'ctxTranscript',
299
+ _container: this.transcriptContainer,
337
300
  _label,
338
301
  _id: 'transcript'
339
302
  }
@@ -380,7 +343,7 @@ export default {
380
343
  },
381
344
  $route: {
382
345
  handler() {
383
- this.mediaToShow = this.formateData(this.getCurrentPage)
346
+ //this.mediaToShow = this.formateData(this.getCurrentPage)
384
347
  this.getFocusables().then((r) => {
385
348
  //Pressing Tab or Shit + tab should make it possible to cycle focus through them
386
349
  if (!r) return
@@ -391,121 +354,44 @@ export default {
391
354
  //update the routeChangeCounter when navigation
392
355
  if (this.routeChangeCounter < 3) this.routeChangeCounter += 1
393
356
 
394
- /* send xapi statement only when there is a change of activity:
395
- Activity has changed when the type of the route is one of the following or the activity__ref or previous route is different of activity_ref of current route
357
+ /*
358
+ *Start Timer on New activities
396
359
  */
397
- const trackedRouteType = ['menu', 'introduction', 'activity']
398
-
399
- if (
400
- trackedRouteType.includes(this.$route.meta.type) &&
401
- this.getModuleInfo.packageType === 'xapi' &&
402
- this.getConnectionInfo &&
403
- this.getConnectionInfo.actor &&
404
- this.getConnectionInfo.remote
405
- ) {
406
- const stmt = {
407
- id: `${this.$route.name.split('.')[0]}`,
408
- description: `${this.$route.name.split('.')[0]} de ${
409
- this.getModuleInfo.id
410
- }`,
411
- definition: `${this.$route.name.split('.')[0]} de ${
412
- this.getModuleInfo.id
413
- }`
414
- }
360
+
361
+ const trackedRouteType = ['introduction', 'conclusion', 'normal']
362
+ if (trackedRouteType.includes(this.$route.meta.type)) {
363
+ if (this.$route.name.includes('.')) return
415
364
 
416
365
  //Start the timer every time that there is a new activity
417
- if (
418
- this.$route.meta.type === 'activity' ||
419
- this.$route.meta.type === 'introduction'
420
- ) {
421
- if (this.timerState === 'started') this.stopTimer('activity') // reset the activity timer
422
- this.startTimer('activity')
423
- }
366
+ if (this.timerState === 'started') this.stopTimer('activity') // reset the activity timer
367
+ this.startTimer('activity')
424
368
 
425
- this.sendXapiStatement(stmt)
369
+ //Send statement when activity as changed
370
+ if (this.activityHasChanged)
371
+ this.sendStartStatement({ id: this.$route.meta.activity_ref })
426
372
  }
427
373
  this.transcriptVisible = false
428
374
  },
429
375
  immediate: true
430
376
  },
431
- /**
432
- * @description Defined in Store
433
- */
434
- getAppStatus(newValue) {
435
- if (newValue === 'ready') {
436
- this.appReady = true
437
- } else {
438
- this.appReady = false
439
- }
440
- },
377
+
441
378
  /**
442
379
  * @description Defined in Store Watch all completed activities to send a lesson completion statement
443
380
  */
444
381
  getAllCompleted: {
445
382
  handler() {
446
- if (
447
- this.getModuleInfo.packageType === 'xapi' &&
448
- this.getConnectionInfo &&
449
- this.getConnectionInfo.actor &&
450
- this.getConnectionInfo.remote
451
- ) {
452
- //======================================================
453
- let completedSize = 0
454
- Object.entries(this.getAllCompleted).forEach((value) => {
455
- completedSize = completedSize + value[1].length
456
- })
457
- const completedState = this.$xapi._getLessonStatus(
458
- this.getConnectionInfo.actor.mbox.replace('mailto:', ''),
459
- this.getConnectionInfo.activity_id
460
- )
461
-
462
- if (
463
- completedSize === this.getAllActivities.pageSize &&
464
- !completedState.completion
465
- ) {
466
- let text
467
- //Defining the text to display for stmt description and definition
468
- switch (this.$i18n.locale) {
469
- case 'fr':
470
- if (this.getModuleInfo.courseID)
471
- text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
472
- else text = `Le ${this.getModuleInfo.id}`
473
- break
474
- case 'en':
475
- if (this.getModuleInfo.courseID)
476
- text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
477
- else text = `The ${this.getModuleInfo.id}`
478
- break
479
- }
480
-
481
- // Retrive only user data in lessons front its interaction that we want to send to the LRS.
482
- // Note: User Settings are sent on a different URI and statement
483
- const {
484
- isFistTime,
485
- userSettings,
486
- ...lessonsData
487
- } = this.getUserInteraction
488
-
489
- this.sendXapiStatement({
490
- verb: 'completed',
491
- definition: text,
492
- description: text,
493
- extension: [
494
- {
495
- id: 'user-data',
496
- content: {
497
- routeHistory: this.getRouteHistory,
498
- ...lessonsData
499
- }
500
- }
501
- ],
502
- duration: this.lessonDuration,
503
- completion: true
504
- })
505
- }
383
+ // Check once the server to set the completion status of the lesson
384
+ if (this.getDataFromServer && this.checkedDataFromServer < 1) {
385
+ const { completedState } = this.getDataFromServer
386
+
387
+ this.lessonCompletionStatus =
388
+ !completedState || !completedState.completion
389
+ ? false
390
+ : completedState.completion
391
+ this.checkedDataFromServer += 1
506
392
  }
507
393
 
508
- //================================================================
394
+ if (!this.lessonCompletionStatus) this.sendCompletionStatus('LESSON')
509
395
  },
510
396
  immediate: true
511
397
  },
@@ -516,17 +402,17 @@ export default {
516
402
  if (this.timerState !== 'started') return
517
403
  //send a statement every x time (second)
518
404
  if (
519
- (this.timerState === 'started',
520
- this.elapsedTime % 30 === 0 &&
521
- this.getModuleInfo.packageType === 'xapi' &&
522
- this.getConnectionInfo &&
523
- this.getConnectionInfo.actor &&
524
- this.getConnectionInfo.remote)
405
+ this.timerState === 'started' &&
406
+ this.elapsedTime % 500 === 0 &&
407
+ this.getModuleInfo.packageType === 'xapi' &&
408
+ this.getConnectionInfo &&
409
+ this.getConnectionInfo.actor &&
410
+ this.getConnectionInfo.remote &&
411
+ this.getDataFromServer
525
412
  ) {
526
- const lastReached = this.$xapi._getLessonPosition(
527
- this.getConnectionInfo.actor.mbox.replace('mailto:', ''),
528
- this.getConnectionInfo.activity_id
529
- )[0]
413
+ const { lessonPosition } = this.getDataFromServer
414
+
415
+ const lastReached = lessonPosition.length ? lessonPosition[0] : ''
530
416
 
531
417
  //only Send savepoint statement when 3 navigation happen and the route is different from last saved
532
418
  if (this.routeChangeCounter >= 3 && lastReached !== this.$route.name) {
@@ -550,7 +436,7 @@ export default {
550
436
  verb: 'progressed',
551
437
  definition: text,
552
438
  description: text,
553
- extension: [
439
+ extensions: [
554
440
  {
555
441
  id: 'ending-point',
556
442
  content: this.$route.name
@@ -559,48 +445,44 @@ export default {
559
445
  duration: this.lessonDuration
560
446
  }
561
447
 
562
- this.sendXapiStatement(stmt)
448
+ this.$bus.$emit('send-xapi-statement', stmt)
563
449
  this.routeChangeCounter = 0 //reset counter after saving
564
450
  }
565
451
  }
566
452
  }
567
453
  },
568
- beforeDestroy() {
454
+ beforeUnmount() {
569
455
  //Communication events
570
- this.$bus.$off('open-popup')
571
-
572
- this.$bus.$off('close-popup')
573
-
574
- this.$bus.$off('start-onboarding')
575
-
576
- this.$bus.$off('open-branch')
577
-
578
- this.$bus.$off('update-page')
579
-
580
- this.$bus.$off('videoFullScreen')
581
-
582
- this.$bus.$off('save-to-scorm')
583
-
584
- this.$bus.$off('send-xapi-statement')
585
-
586
- this.$bus.$off('launch-xapi-resource')
587
-
588
- this.$bus.$off('fire-exit-event', this.endLesson)
589
-
456
+ this.$bus.$off('close-sidebar', this.closeSidebar)
457
+ this.$bus.$off('open-sidebar', this.openSidebar)
458
+ this.$bus.$off('open-popup', this.openPopup)
459
+ this.$bus.$off('close-popup', this.closePopup)
460
+ this.$bus.$off('start-onboarding', this.startOnboarding)
461
+ this.$bus.$off('videoFullScreen', this.onVideoFullScreen)
462
+ this.$bus.$off('save-to-scorm', this.saveToScorm)
463
+ this.$bus.$off('launch-xapi-resource', this.launchResource)
590
464
  this.$bus.$off('update-route-history', this.updateRouteHistory)
591
-
592
465
  this.$bus.$off('update-content', this.updateContent)
593
-
594
466
  this.$bus.$off('show-transcript', this.openTranscript)
595
-
467
+ this.$bus.$off('send-completion-event', this.sendCompletionStatus)
468
+ this.$bus.$off('send-starting-event', this.sendStartStatement)
469
+ //nav mouseleave event
470
+ let nav = document.getElementById('navTool')
471
+ if (nav) {
472
+ nav.removeEventListener('mouseleave', this.onNavMouseleave)
473
+ }
474
+ //sidebar scroll event
475
+ const rightSidebar = this.getRightSidebar()
476
+ if (rightSidebar) {
477
+ rightSidebar.removeEventListener('scroll', this.handleRightSidebarScroll)
478
+ }
479
+ //keayboard listener
596
480
  document.removeEventListener('keydown', this.handleKeyboardControls)
597
481
  },
598
482
  created() {
599
- //TODO: get course name from app settings
600
- // Correct animation slide off when transcript off
601
- // correct toggle view hide branching content
602
483
  let lessonLabel, lessonNumber, lessonTitle, titleString
603
- this.mediaToShow = this.formateData(this.getCurrentPage)
484
+
485
+ //this.mediaToShow = this.formateData(this.getCurrentPage)
604
486
  lessonLabel = this.$t('text.lesson')
605
487
  lessonNumber = this.moduleConfig.id.replace('module_', '')
606
488
  lessonTitle = this.theTitle
@@ -618,50 +500,27 @@ export default {
618
500
  this.$store.commit('UPDATE_INTRO_STATE', this.theIntroIsActivated)
619
501
 
620
502
  //Communication events
621
- this.$bus.$on('open-popup', (data) => {
622
- this.openPopup(data)
623
- })
624
-
625
- this.$bus.$on('close-popup', () => {
626
- this.closePopup()
627
- })
503
+ this.$bus.$on('open-popup', this.openPopup)
628
504
 
629
- this.$bus.$on('start-onboarding', (data) => {
630
- this.startOnboarding(data)
631
- })
505
+ this.$bus.$on('close-popup', this.closePopup)
632
506
 
633
- this.$bus.$on('open-sidebar', (data) => {
634
- this.openSidebar(data)
635
- })
636
-
637
- this.$bus.$on('close-sidebar', (data) => {
638
- this.closeSidebar(data)
639
- })
507
+ this.$bus.$on('start-onboarding', this.startOnboarding)
640
508
 
641
- this.$bus.$on('videoFullScreen', (value) => {
642
- this.videoFull = value
643
- })
509
+ this.$bus.$on('open-sidebar', this.openSidebar)
644
510
 
645
- this.$bus.$on('save-to-scorm', () => {
646
- this.saveToScorm()
647
- })
511
+ this.$bus.$on('close-sidebar', this.closeSidebar)
648
512
 
649
- this.$bus.$on(
650
- 'send-xapi-statement',
651
- (statement, cb = null, option = false) => {
652
- this.sendXapiStatement(statement, cb, option)
653
- }
654
- )
513
+ this.$bus.$on('videoFullScreen', this.onVideoFullScreen)
655
514
 
656
- this.$bus.$on('launch-xapi-resource', (res) => {
657
- this.launchResource(res)
658
- })
515
+ this.$bus.$on('save-to-scorm', this.saveToScorm)
659
516
 
660
- this.$bus.$on('fire-exit-event', this.endLesson)
517
+ this.$bus.$on('launch-xapi-resource', this.launchResource)
661
518
 
662
519
  this.$bus.$on('update-route-history', this.updateRouteHistory)
663
520
  this.$bus.$on('update-content', this.updateContent)
664
521
  this.$bus.$on('show-transcript', this.openTranscript)
522
+ this.$bus.$on('send-completion-event', this.sendCompletionStatus)
523
+ this.$bus.$on('send-starting-event', this.sendStartStatement)
665
524
 
666
525
  this.initLesson()
667
526
 
@@ -681,7 +540,12 @@ export default {
681
540
 
682
541
  let nav = document.getElementById('navTool')
683
542
 
684
- nav.addEventListener('mouseleave', () => {
543
+ nav.addEventListener('mouseleave', this.onNavMouseleave)
544
+
545
+ document.addEventListener('keydown', this.handleKeyboardControls)
546
+ },
547
+ methods: {
548
+ onNavMouseleave() {
685
549
  let widgetOpen = document.getElementsByClassName('open')
686
550
 
687
551
  if (widgetOpen.length == 0) {
@@ -691,15 +555,14 @@ export default {
691
555
  } else {
692
556
  this.closeDelay = true
693
557
  }
694
- })
695
-
696
- document.addEventListener('keydown', this.handleKeyboardControls)
697
- },
698
- methods: {
558
+ },
559
+ onVideoFullScreen(value) {
560
+ this.videoFull = value
561
+ },
699
562
  /* Get All Element related to the media return a list of all DOM element*/
700
563
  async getFocusables() {
701
- return new Promise(function(resolve, reject) {
702
- setTimeout(function() {
564
+ return new Promise(function (resolve, reject) {
565
+ setTimeout(function () {
703
566
  const getAllFocusables = () => {
704
567
  const listItems = document.querySelectorAll('.v-media')
705
568
  if (!listItems || !listItems.length) return null
@@ -716,17 +579,16 @@ export default {
716
579
  *
717
580
  */
718
581
  async handleKeyboardControls(evt) {
719
- let { key } = evt
582
+ let { code } = evt
720
583
 
721
- if (key === 'Escape' && this.rightSidebarVisible)
584
+ if (code === 'Escape' && this.rightSidebarVisible)
722
585
  return (this.rightSidebarVisible = false)
723
586
 
724
587
  //==========================================
725
- const videoRange = await this.getFocusables()
588
+ /*const videoRange = await this.getFocusables()
726
589
  if (videoRange && videoRange.indexOf(document.activeElement) === -1)
727
590
  return
728
-
729
- this.$bus.$emit('play-media', key)
591
+ this.$bus.$emit('play-media', code)*/
730
592
  },
731
593
 
732
594
  /**
@@ -737,13 +599,13 @@ export default {
737
599
  */
738
600
  openSidebar(obj) {
739
601
  if (!obj || !obj.ctx || !obj.e) return
740
-
602
+ const wrapper = obj.w ? obj.w : null
741
603
  this.lastInFocus = document.activeElement
742
604
  const { ctx, e: content } = obj
743
605
 
744
606
  switch (ctx) {
745
607
  case 'ctxTranscript':
746
- this.openTranscript(content)
608
+ this.openTranscript(content, wrapper)
747
609
  break
748
610
 
749
611
  case 'ctxBranching':
@@ -771,14 +633,14 @@ export default {
771
633
  * @param {String} ctx - context in which side bar was opened and will now be closed
772
634
  *
773
635
  */
774
- closeSidebar(ctx) {
636
+ closeSidebar(ctx, wrapper = null) {
775
637
  //delay animation
776
638
  this.rightSidebarVisible = false // this will allow to run the animation of sidebar closing 1rst
777
639
 
778
640
  this.resetFocus(this.lastInFocus)
779
641
  switch (ctx) {
780
642
  case 'ctxTranscript':
781
- this.closeTranscript()
643
+ this.closeTranscript(wrapper)
782
644
  break
783
645
 
784
646
  case 'ctxBranching':
@@ -835,9 +697,17 @@ export default {
835
697
  * @param {Object} c content to display in the sidebar
836
698
  */
837
699
 
838
- openTranscript(c) {
700
+ openTranscript(c, container) {
839
701
  if (!c) return (this.transcriptVisible = false)
840
- if (this.transcriptContent == null) this.transcriptContent = c
702
+ //Changer le contenu du transcript
703
+ if (this.transcriptContent !== c) this.transcriptContent = c
704
+ //Changer le format du Container du media
705
+ if (this.transcriptContainer && this.transcriptContainer !== container) {
706
+ /*console.log('transcriptContainer est différent de container')
707
+ console.log({ ancien: this.transcriptContainer, nouveau: container })
708
+ console.log('agrandir ancien container et réduire nouveau container')*/
709
+ }
710
+ this.transcriptContainer = container
841
711
  this.transcriptVisible = true
842
712
  },
843
713
  /**
@@ -845,13 +715,13 @@ export default {
845
715
  * @summary reset the value of the transcriptContent and transcripVisibility
846
716
  * @fires 'transcript-hidden' to AppCompPlaybar
847
717
  */
848
- closeTranscript() {
718
+ closeTranscript(container = this.transcriptContainer) {
849
719
  setTimeout(() => {
850
720
  this.transcriptContent = null
851
721
  this.transcriptVisible = false
852
722
  }, 300)
853
723
  this.$bus.$emit('transcript-hidden')
854
- this.$bus.$emit('resize-media', 'lg')
724
+ this.$bus.$emit('resize-media', 'lg', container)
855
725
  },
856
726
 
857
727
  /**
@@ -904,8 +774,9 @@ export default {
904
774
  return new Promise((res) => {
905
775
  this.$bus.$emit('set-comp-status', 'appBaseModule', 'loading')
906
776
  this.$store.dispatch('updateCurrentTimeline', '')
907
- this.$store.dispatch('updateCurrentMediaElement', '')
777
+ this.$store.dispatch('updateCurrentMediaElements', [])
908
778
  this.$store.dispatch('updateCurrentPage', {})
779
+ this.$bus.$emit('set-comp-status', 'appBaseModule', 'ready')
909
780
  res(this.$store.state.$appStore.compStatusTracker)
910
781
  })
911
782
  },
@@ -1014,16 +885,15 @@ export default {
1014
885
  case 'xapi':
1015
886
  {
1016
887
  //Check if there is a user connection
888
+
1017
889
  if (
1018
890
  this.getConnectionInfo &&
1019
891
  this.getConnectionInfo.actor &&
1020
- this.getConnectionInfo.remote
892
+ this.getConnectionInfo.remote &&
893
+ this.getDataFromServer
1021
894
  ) {
1022
- // check for the bookmark existance in LMS
1023
- const bookmark = this.$xapi._getLessonPosition(
1024
- this.getConnectionInfo.actor.mbox.replace('mailto:', ''),
1025
- this.getConnectionInfo.activity_id
1026
- )[0]
895
+ const { lessonPosition } = this.getDataFromServer
896
+ const bookmark = lessonPosition.length ? lessonPosition[0] : ''
1027
897
  // if none stored in the LMS redirect to the 1st page
1028
898
  if (
1029
899
  !this.bookmarkActive ||
@@ -1045,10 +915,6 @@ export default {
1045
915
  ? this.$router.push({ name: this.$route.name })
1046
916
  : this.$router.push({ name: 'menu' })
1047
917
  }
1048
- // set the current page
1049
- this.fetchPage().then((res) => {
1050
- //this.mediaToShw = this.formateData(res)
1051
- })
1052
918
  }
1053
919
  break
1054
920
  }
@@ -1144,15 +1010,10 @@ export default {
1144
1010
  !this.theIntroIsActivated
1145
1011
  )
1146
1012
  toName = 'menu'
1147
- // next({ name: toName }) //navigate to correct path
1148
1013
  }
1149
1014
  //When User inter path to module
1150
1015
  else if (to.name === 'module') {
1151
- //Intro is not active set navigation path to menu
1152
- //if (!this.theIntroIsActivated) toName = 'menu'
1153
- //Intro active set navigation path to introduction
1154
1016
  toName = 'menu'
1155
- // next({ name: toName }) //navigate to correct path
1156
1017
  }
1157
1018
  //Default redirection
1158
1019
  else {
@@ -1162,10 +1023,6 @@ export default {
1162
1023
  //===================== Handeling a page request from Store=========================== ====//
1163
1024
  //get the page from store
1164
1025
  this.fetchPage().then((res) => {
1165
- this.$bus.$emit('set-comp-status', 'AppbaseModule', 'ready')
1166
-
1167
- // this.mediaToShw = this.formateData(res)
1168
-
1169
1026
  //Save current page has bookmark in scorm
1170
1027
  if (
1171
1028
  this.getModuleInfo.packageType === 'scorm' &&
@@ -1198,193 +1055,6 @@ export default {
1198
1055
  if (this.routeData.length >= 4) this.routeData.shift()
1199
1056
  },
1200
1057
 
1201
- /**
1202
- * @description Send a custom statement to the lrs. Receive some custom params and build a statement to send
1203
- * @param {Object} stmtObj
1204
- * @param {Function} cb
1205
- */
1206
- sendXapiStatement(stmtObj, cb = null, withFetch = false) {
1207
- cb = cb || null
1208
- const crsParams = this.getModuleInfo
1209
-
1210
- if (
1211
- this.getConnectionInfo &&
1212
- this.getConnectionInfo.actor &&
1213
- this.getConnectionInfo.remote
1214
- ) {
1215
- const {
1216
- id,
1217
- result = null,
1218
- definition,
1219
- objectType,
1220
- type,
1221
- description,
1222
- verb,
1223
- extension,
1224
- duration,
1225
- completion
1226
- } = stmtObj
1227
- //unload
1228
- //define the activity id
1229
- let activityId
1230
- //=========================== activity ID of Object ==========================================
1231
- /*
1232
- * Define the acitivity id of the stmt according to following:
1233
- * The statement is sent at component/ element level : there is Id and Id is not null (id of the element)
1234
- * The statement is sent at module level. there is no id
1235
- * the Statement must be sent at the course level: There course_id exist and id is the course_id
1236
- */
1237
-
1238
- if (id && id !== crsParams.courseID)
1239
- activityId = `${this.getConnectionInfo.activity_id}/${id}`
1240
- else if (crsParams.courseID && id === crsParams.courseID)
1241
- activityId = this.getConnectionInfo.activity_id.replace(
1242
- `/${crsParams.id}`,
1243
- ''
1244
- )
1245
- else activityId = `${this.getConnectionInfo.activity_id}`
1246
-
1247
- //define the statement object
1248
- let stmt = {
1249
- actor: this.getConnectionInfo.actor,
1250
- verb: (() => {
1251
- if (verb && this.$xapi.verbs[verb.trim()])
1252
- return this.$xapi.verbs[verb.trim()]
1253
- else {
1254
- //check if the agent exist in the learning record for this activity
1255
- // to determine wether he is lunching the activity or resuming the activity
1256
- if (
1257
- this.$xapi._getAgent(
1258
- this.getConnectionInfo.actor.mbox.replace('mailto:', ''),
1259
- activityId
1260
- )
1261
- )
1262
- return this.$xapi.verbs.resumed
1263
- else return this.$xapi.verbs.started
1264
- }
1265
- })(),
1266
- object: {
1267
- id: activityId,
1268
- definition: {
1269
- name: {
1270
- [this.displayLang]: `${definition}`
1271
- },
1272
- description: {
1273
- [this.displayLang]: `${description}`,
1274
- type: type || 'http://activitystrea.ms/schema/1.0/page'
1275
- }
1276
- },
1277
- objectType: objectType || 'Activity'
1278
- },
1279
- context: {
1280
- contextActivities: {}
1281
- }
1282
- }
1283
- //===================== contextActivity parent =====================//
1284
- /*
1285
- Define parent in the contextActivity
1286
- When:
1287
- 1- when we have the id (parent === module)
1288
- 2- when with have no id but we have a course_id (parent is cours_id)
1289
- */
1290
- const activityParent = (() => {
1291
- if (crsParams.courseID && !id)
1292
- return {
1293
- id: this.getConnectionInfo.activity_id.replace(
1294
- `/${crsParams.id}`,
1295
- ''
1296
- ),
1297
- objectType: objectType || 'Activity'
1298
- }
1299
- else if (id && id !== crsParams.courseID)
1300
- return {
1301
- id: `${this.getConnectionInfo.activity_id}`,
1302
- objectType: objectType || 'Activity'
1303
- }
1304
- else return null
1305
- })()
1306
-
1307
- // Add the parent key of the context activity
1308
- if (activityParent)
1309
- stmt.context.contextActivities['parent'] = [activityParent]
1310
-
1311
- //===================== contextActivity grouping =====================//
1312
-
1313
- //Defining the Grouping of the context activity
1314
- const activityGrouping = (() => {
1315
- if (
1316
- activityParent &&
1317
- crsParams.courseID &&
1318
- activityParent.id.includes(crsParams.id)
1319
- )
1320
- return {
1321
- id: this.getConnectionInfo.activity_id.replace(
1322
- `/${crsParams.id}`,
1323
- ''
1324
- )
1325
- }
1326
- else return null
1327
- })()
1328
- //Adding the grouping of the context activity
1329
- if (activityGrouping)
1330
- stmt.context.contextActivities['grouping'] = [activityGrouping]
1331
-
1332
- //add result data to statement
1333
- if (result) stmt['result'] = result
1334
-
1335
- // Add duration info to statment when activity is complete
1336
- if (verb === 'completed' || verb === 'suspended' || verb === 'exited') {
1337
- if (!stmt.result) stmt.result = {} // check if exist
1338
-
1339
- let d
1340
-
1341
- duration
1342
- ? (d = `PT${duration.split(':')[0]}H${duration.split(':')[1]}M${
1343
- duration.split(':')[2]
1344
- }S`)
1345
- : (d = `PT${this.activityDuration.split(':')[0]}H${
1346
- this.activityDuration.split(':')[1]
1347
- }M${this.activityDuration.split(':')[2]}S`)
1348
-
1349
- stmt.result['duration'] = d
1350
- //Set the completion status of the result
1351
- if (completion) stmt.result['completion'] = completion
1352
- }
1353
-
1354
- // ===================== Extension of the Object definition =====================//
1355
- if (
1356
- extension &&
1357
- extension.constructor === Array &&
1358
- extension.length > 0
1359
- ) {
1360
- //Validate each entry given in the extension Array
1361
- extension.forEach((e) => {
1362
- //entry must be of type Object
1363
- if (e.constructor !== Object)
1364
- throw new Error(`'${e}' is not a valid value. Must be Object`)
1365
-
1366
- //Entry Must have id and content keys
1367
- const validKey = ['id', 'content']
1368
- Object.keys(e).forEach((key) => {
1369
- if (!validKey.includes(key))
1370
- throw new Error(`Not valid key '${key}' for entry ${e}`)
1371
-
1372
- //id must be a String
1373
- if (key === 'id' && e[key] && e[key].constructor !== String)
1374
- throw new Error(`'${key}' must be of type String`)
1375
- })
1376
-
1377
- stmt.object.definition['extensions'] = {
1378
- ...stmt.object.definition['extensions'],
1379
- [`${this.getConnectionInfo.activity_id}/${e.id}`]: e.content
1380
- }
1381
- })
1382
- }
1383
-
1384
- this.$xapi._sendStatement(stmt, cb, withFetch)
1385
- }
1386
- },
1387
-
1388
1058
  /**
1389
1059
  * @description Helper fonction to launch extra ressource from this activity. Extra ressource is another lesson.
1390
1060
  * @param {Object} res - Data of the ressource to launch
@@ -1411,67 +1081,12 @@ export default {
1411
1081
  res.url = `${baseDomain}/${res.url}`
1412
1082
 
1413
1083
  const newUrlToLaunch = `${res.url}?endpoint=${endpoint}&auth=${auth}&actor=${actor}&registration=${registration}&activity_id=${activity_id}`
1414
-
1415
- this.endLesson((evt) => {
1084
+ this.routeChangeCounter = 0 //reset counter after saving
1085
+ this.$bus.$emit('fire-exit-event', () => {
1416
1086
  window.location.replace(newUrlToLaunch)
1417
1087
  })
1418
1088
  },
1419
1089
 
1420
- /**
1421
- * @param {Function} cb
1422
- * @param {Bool} option - set if must use Fetch API or not. default = false
1423
- */
1424
- endLesson(cb, option = false) {
1425
- let text
1426
- //Defining the text to display for stmt description and definition
1427
- switch (this.$i18n.locale) {
1428
- case 'fr':
1429
- if (this.getModuleInfo.courseID)
1430
- text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
1431
- else text = `Le ${this.getModuleInfo.id}`
1432
- break
1433
- case 'en':
1434
- if (this.getModuleInfo.courseID)
1435
- text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
1436
- else text = `The ${this.getModuleInfo.id}`
1437
- break
1438
- }
1439
-
1440
- // Retrieve only user data in lessons front its interaction that we want to send to the LRS.
1441
- // Note: User Settings are sent on a different URI and statement
1442
- const {
1443
- isFistTime,
1444
- userSettings,
1445
- ...lessonsData
1446
- } = this.getUserInteraction
1447
-
1448
- const stmt = {
1449
- verb: 'exited',
1450
- definition: text,
1451
- description: text,
1452
- extension: [
1453
- {
1454
- id: 'ending-point',
1455
- content: this.$route.name
1456
- },
1457
- {
1458
- id: 'user-data',
1459
- content: {
1460
- routeHistory: this.getRouteHistory,
1461
- ...lessonsData
1462
- }
1463
- }
1464
- ],
1465
- duration: this.lessonDuration
1466
- }
1467
-
1468
- this.sendXapiStatement(stmt, null, option)
1469
-
1470
- if (this.timerState === 'started') setTimeout(() => this.stopTimer(), 0) //clear the timer
1471
-
1472
- if (cb) cb()
1473
- },
1474
-
1475
1090
  /**
1476
1091
  * @description start the tutorial for first time users
1477
1092
  * @requires '../assets/data/onboardingMessages.json'
@@ -1487,13 +1102,17 @@ export default {
1487
1102
  this.nextOnboarding('message_1')
1488
1103
  } catch (e) {
1489
1104
  //fetch default values for popups
1490
- this.onboardingMessages = require('../assets/data/onboardingMessages.json')
1105
+ this.onboardingMessages = import(
1106
+ '../assets/data/onboardingMessages.json'
1107
+ )
1491
1108
  this.nextOnboarding('message_1')
1492
1109
  }
1493
1110
  }
1494
1111
  } else {
1495
1112
  if (this.getOnboardingEnabled) {
1496
- this.onboardingMessages = require('../assets/data/onboardingMessages.json')
1113
+ this.onboardingMessages = import(
1114
+ '../assets/data/onboardingMessages.json'
1115
+ )
1497
1116
  this.nextOnboarding('message_1')
1498
1117
  }
1499
1118
  }
@@ -1518,8 +1137,6 @@ export default {
1518
1137
  this.toolTipTarget = messages[postNextMessage].value.target
1519
1138
  }
1520
1139
  }
1521
-
1522
- //check if next message exist
1523
1140
  //check if the next message is a popup or other componant
1524
1141
  if (messages[nextMessage].type == 'popup-avert') {
1525
1142
  this.openPopup(messages[nextMessage])
@@ -1531,29 +1148,20 @@ export default {
1531
1148
  * @description- Skip directly to the main containt*
1532
1149
  * main content is Node with page ID by default
1533
1150
  * Main Content can be defined by front-end using prop "main" in Module.vue of project
1151
+ * @fire {event} 'move-to-target' to AppBase
1534
1152
  */
1535
1153
 
1536
1154
  skipToMain() {
1537
1155
  const { main } = this.moduleConfig // check for main input
1538
- let skipTo = document.querySelector(`#wrapper-content`) //default definition of main element
1539
-
1540
- if (main) {
1541
- let targetEl = document.querySelector(`#${main}`) // search for node element specified as main
1542
1156
 
1543
- if (targetEl) skipTo = targetEl
1544
- }
1545
- let targetTop = skipTo.offsetTop
1157
+ let skipTo = main ? main : 'wrapper-content' // search for node element specified as main
1546
1158
 
1547
- let scrollOpt = {
1548
- top: targetTop - 100,
1159
+ //fires event
1160
+ this.$bus.$emit('move-to-target', skipTo, {
1161
+ top: 100, // offset target top to 100 pixel
1549
1162
  left: 0,
1550
- behavior: 'auto' //auto
1551
- }
1552
-
1553
- //Allowing accessibility control with keyboard
1554
- skipTo.setAttribute('tabIndex', -1)
1555
- window.scrollTo(scrollOpt)
1556
- this.resetFocus(skipTo)
1163
+ behavior: 'auto'
1164
+ })
1557
1165
  },
1558
1166
 
1559
1167
  /**
@@ -1703,7 +1311,6 @@ export default {
1703
1311
  * @param {HTMLElement} e - element that will get focus
1704
1312
  */
1705
1313
  resetFocus(e) {
1706
- //console.log('set focus IN ', { from: document.activeElement, to: e })
1707
1314
  if (e) e.focus()
1708
1315
  },
1709
1316
 
@@ -1752,76 +1359,293 @@ export default {
1752
1359
  this.closeDelay = false
1753
1360
  clearTimeout(this.timeOut)
1754
1361
  }
1362
+ },
1363
+
1364
+ /**
1365
+ * @description Method to handle to sanding of completion status to LRS
1366
+ * determine wether to send a completion for an activity or the Lesson
1367
+ * Completion is sent once
1368
+ * @param {String} context - 'ACTIVITY | LESSON'
1369
+ */
1370
+
1371
+ sendCompletionStatus(context = null) {
1372
+ if (
1373
+ !this.getModuleInfo.packageType === 'xapi' ||
1374
+ !this.getConnectionInfo ||
1375
+ !this.getConnectionInfo.actor ||
1376
+ !this.getConnectionInfo.remote ||
1377
+ !this.getDataFromServer ||
1378
+ !context
1379
+ )
1380
+ return
1381
+
1382
+ let stmt = null
1383
+
1384
+ switch (context) {
1385
+ case 'ACTIVITY': {
1386
+ let text
1387
+ let completedSize = 0
1388
+ Object.entries(this.getAllCompleted).forEach((value) => {
1389
+ completedSize = completedSize + value[1].length
1390
+ })
1391
+ let recordInServerLength,
1392
+ thisActivityProgressLength,
1393
+ thisActivityLength
1394
+
1395
+ const { userProgress } = this.getDataFromServer
1396
+ const thisActivityServerState =
1397
+ userProgress[this.$route.meta.activity_ref]
1398
+
1399
+ if (thisActivityServerState)
1400
+ recordInServerLength = Object.keys(thisActivityServerState).length
1401
+
1402
+ thisActivityProgressLength = this.getAllCompleted[
1403
+ this.$route.meta.activity_ref
1404
+ ]
1405
+ ? this.getAllCompleted[this.$route.meta.activity_ref].length
1406
+ : 0
1407
+
1408
+ //get The total length of the current activity
1409
+ thisActivityLength = this.getAllActivities(
1410
+ this.getCurrentPage.activityRef
1411
+ ).pageSize
1412
+
1413
+ if (thisActivityProgressLength !== thisActivityLength) return
1414
+ if (thisActivityProgressLength == recordInServerLength) return
1415
+
1416
+ //Defining the text to display for stmt description and definition
1417
+ const id = this.getCurrentPage.activityRef
1418
+ let aName = ''
1419
+ switch (true) {
1420
+ case id == 'A00':
1421
+ aName = 'Introduction'
1422
+ break
1423
+ case id == 'A99':
1424
+ aName = 'Conclusion'
1425
+ break
1426
+
1427
+ default: {
1428
+ let d = id.replace('A', '').trim()
1429
+ d = parseInt(d)
1430
+ aName = `${this.$t('text.activity')} ${d}`
1431
+ }
1432
+ }
1433
+
1434
+ switch (this.$i18n.locale) {
1435
+ case 'fr':
1436
+ if (this.getModuleInfo.courseID)
1437
+ text = `${aName} de ${this.getModuleInfo.id} `
1438
+ else text = `Le ${this.getModuleInfo.id}`
1439
+ break
1440
+ case 'en':
1441
+ if (this.getModuleInfo.courseID)
1442
+ text = `${aName} of ${this.getModuleInfo.id}`
1443
+ else text = `The ${this.getModuleInfo.id}`
1444
+ break
1445
+ }
1446
+
1447
+ // Retrive only user data in lessons front its interaction that we want to send to the LRS.
1448
+ // Note: User Settings are sent on a different URI and statement
1449
+ const { isFistTime, userSettings, ...lessonsData } =
1450
+ this.getUserInteraction
1451
+
1452
+ stmt = {
1453
+ id: this.getCurrentPage.activityRef,
1454
+ verb: 'completed',
1455
+ definition: text,
1456
+ description: text,
1457
+ extensions: [
1458
+ {
1459
+ id: 'user-data',
1460
+ content: {
1461
+ routeHistory: this.getRouteHistory,
1462
+ ...lessonsData
1463
+ }
1464
+ }
1465
+ ],
1466
+ duration: this.activityDuration,
1467
+ completion: true
1468
+ }
1469
+
1470
+ break
1471
+ }
1472
+
1473
+ case 'LESSON': {
1474
+ //======================================================
1475
+ let completedSize = 0
1476
+ Object.entries(this.getAllCompleted).forEach((value) => {
1477
+ completedSize = completedSize + value[1].length
1478
+ })
1479
+
1480
+ if (completedSize === this.getAllActivities().pageSize) {
1481
+ let text
1482
+ //Defining the text to display for stmt description and definition
1483
+ switch (this.$i18n.locale) {
1484
+ case 'fr':
1485
+ if (this.getModuleInfo.courseID)
1486
+ text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
1487
+ else text = `Le ${this.getModuleInfo.id}`
1488
+ break
1489
+ case 'en':
1490
+ if (this.getModuleInfo.courseID)
1491
+ text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
1492
+ else text = `The ${this.getModuleInfo.id}`
1493
+ break
1494
+ }
1495
+
1496
+ // Retrive only user data in lessons front its interaction that we want to send to the LRS.
1497
+ // Note: User Settings are sent on a different URI and statement
1498
+ const { isFistTime, userSettings, ...lessonsData } =
1499
+ this.getUserInteraction
1500
+
1501
+ stmt = {
1502
+ verb: 'completed',
1503
+ definition: text,
1504
+ description: text,
1505
+ extensions: [
1506
+ {
1507
+ id: 'user-data',
1508
+ content: {
1509
+ routeHistory: this.getRouteHistory,
1510
+ ...lessonsData
1511
+ }
1512
+ }
1513
+ ],
1514
+ duration: this.lessonDuration,
1515
+ completion: true
1516
+ }
1517
+ this.lessonCompletionStatus = true // set the status of this lesson as completed
1518
+ let completedState = {
1519
+ duration: this.lessonDuration,
1520
+ completion: this.lessonCompletionStatus
1521
+ }
1522
+ this.$store.dispatch('updateDataFetchFromServer', {
1523
+ completedState
1524
+ })
1525
+ }
1526
+
1527
+ break
1528
+ }
1529
+ }
1530
+ if (!stmt) return
1531
+
1532
+ this.$bus.$emit('send-xapi-statement', stmt)
1533
+ },
1534
+ /**
1535
+ * @description Method handle start event of activity to LRS
1536
+ * Check if the activity is already initiated in The LRS record to determine wether it a start or a resume
1537
+ * @param {Object} a - Data of the activity
1538
+ * @fires 'send-xapi-statement' to APPBASE
1539
+ */
1540
+
1541
+ sendStartStatement(a) {
1542
+ if (!a || !this.getDataFromServer) return
1543
+ const { id } = a
1544
+ let aName = null
1545
+
1546
+ switch (true) {
1547
+ case id == 'A00':
1548
+ aName = 'Introduction'
1549
+ break
1550
+ case id == 'A99':
1551
+ aName = 'Conclusion'
1552
+ break
1553
+
1554
+ default: {
1555
+ let d = id.replace('A', '').trim()
1556
+ d = parseInt(d)
1557
+ aName = `${this.$t('text.activity')} ${d}`
1558
+ }
1559
+ }
1560
+
1561
+ let text =
1562
+ this.$i18n.locale == 'fr'
1563
+ ? `${aName} de ${this.getModuleInfo.id}`
1564
+ : `${aName} of ${this.getModuleInfo.id}`
1565
+
1566
+ /*
1567
+ *Determine if activity as been initialized. Activity is initialized when it is in the serverRecords
1568
+ */
1569
+ const { userProgress } = this.getDataFromServer
1570
+ const thisActivityServerState = userProgress[id] ? userProgress[id] : {}
1571
+ const recordInServerLength = Object.keys(thisActivityServerState).length
1572
+
1573
+ const stmt = {
1574
+ id,
1575
+ verb: recordInServerLength ? 'resumed' : 'initialized', //determine verb of the statement to send
1576
+ definition: text,
1577
+ description: text
1578
+ }
1579
+ setTimeout(() => this.$bus.$emit('send-xapi-statement', stmt), 500)
1755
1580
  }
1756
1581
  }
1757
1582
  }
1758
1583
  </script>
1759
1584
  <style lang="scss">
1760
- $heigthNavbar: 55px;
1585
+ .module {
1586
+ width: 100%;
1587
+ height: 100%;
1588
+ min-height: 100vh;
1589
+ position: relative;
1590
+ display: flex;
1591
+ flex-direction: row;
1592
+ align-items: stretch;
1593
+ }
1761
1594
 
1762
- #App-base {
1763
- .skip-link {
1764
- left: -999px;
1765
- position: absolute;
1595
+ .skip-link {
1596
+ position: absolute;
1597
+ left: -999px;
1598
+ top: auto;
1599
+ width: 1px;
1600
+ height: 1px;
1601
+ overflow: hidden;
1602
+ z-index: -999;
1603
+
1604
+ &:focus,
1605
+ &:active {
1606
+ left: auto;
1766
1607
  top: auto;
1767
- width: 1px;
1768
- height: 1px;
1769
- overflow: hidden;
1770
- z-index: -999;
1771
-
1772
- &:focus,
1773
- &:active {
1774
- left: auto;
1775
- top: auto;
1776
- width: 30%;
1777
- height: auto;
1778
- overflow: auto;
1779
- margin: 10px 35%;
1780
- padding: 5px;
1781
- text-align: center;
1782
- font-size: 1.2em;
1783
- z-index: 999;
1784
- }
1785
- }
1786
- /********** FM **********/
1787
- #Loading {
1788
- width: 100%;
1789
- position: fixed;
1790
- z-index: 99999;
1791
- left: 0;
1792
- opacity: 0;
1793
- display: none;
1794
- transition: opacity 0.5s;
1795
- pointer-events: none;
1608
+ width: 30%;
1609
+ height: auto;
1610
+ overflow: auto;
1611
+ margin: 10px 35%;
1612
+ padding: 5px;
1613
+ text-align: center;
1614
+ font-size: 1.2em;
1615
+ z-index: 999;
1796
1616
  }
1797
1617
  }
1798
-
1799
- #btn_mobile_hide {
1800
- width: 48px;
1801
- height: 40px;
1802
- border: none;
1618
+ /********** FM **********/
1619
+ #Loading {
1620
+ width: 100%;
1621
+ position: fixed;
1622
+ z-index: 99999;
1623
+ left: 0;
1624
+ opacity: 0;
1803
1625
  display: none;
1626
+ transition: opacity 0.5s;
1804
1627
  pointer-events: none;
1805
-
1806
- @media screen and (max-width: 1100px) {
1807
- display: block;
1808
- pointer-events: all;
1809
- position: absolute;
1810
- top: 6px;
1811
- right: 10px;
1812
- z-index: 10;
1813
- }
1814
-
1815
- svg {
1816
- transition: transform 0.5s ease-out;
1817
- transform: rotate(180deg);
1818
-
1819
- &[data-hide='true'] {
1820
- transform: rotate(0deg);
1821
- }
1822
- }
1823
1628
  }
1629
+
1630
+ // #Loading {
1631
+ // position: absolute;
1632
+ // width: 100%;
1633
+ // display: flex;
1634
+ // flex-direction: column;
1635
+ // //width: 33%;
1636
+ // height: 40%;
1637
+ // top: 50%;
1638
+ // align-items: center;
1639
+ // justify-content: center;
1640
+ // background-color: rgba($color: #000000, $alpha: 0.7);
1641
+ // border-radius: 10px;
1642
+ // box-shadow: 2px 2px 4px 0px rgba($color: #000000, $alpha: 0.2);
1643
+ // z-index: 99999;
1644
+ // pointer-events: none;
1645
+ // }
1824
1646
  #right-sidebar {
1647
+ -webkit-box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
1648
+ box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
1825
1649
  .b-sidebar-header button .app-icon {
1826
1650
  width: 20px;
1827
1651
  height: 20px;