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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -9
  3. package/src/$locales/en.json +39 -15
  4. package/src/$locales/fr.json +48 -23
  5. package/src/components/AppBase.vue +245 -266
  6. package/src/components/AppBaseErrorDisplay.vue +29 -0
  7. package/src/components/AppBaseModule.vue +485 -232
  8. package/src/components/AppBasePage.vue +28 -54
  9. package/src/components/AppCompBif.vue +120 -0
  10. package/src/components/AppCompBranchButtons.vue +31 -30
  11. package/src/components/AppCompButtonProgress.vue +35 -46
  12. package/src/components/AppCompCarousel.vue +154 -247
  13. package/src/components/AppCompJauge.vue +1 -2
  14. package/src/components/AppCompMediaPlayer.vue +106 -74
  15. package/src/components/AppCompMenu.vue +87 -81
  16. package/src/components/AppCompMenuItem.vue +48 -90
  17. package/src/components/AppCompNavigation.vue +949 -0
  18. package/src/components/AppCompNoteCall.vue +126 -0
  19. package/src/components/AppCompNoteCredit.vue +164 -0
  20. package/src/components/AppCompPlayBar.vue +864 -1085
  21. package/src/components/AppCompPopUp.vue +26 -27
  22. package/src/components/AppCompPopover.vue +27 -0
  23. package/src/components/AppCompQuiz.vue +27 -36
  24. package/src/components/AppCompQuizRecall.vue +250 -0
  25. package/src/components/AppCompSVG.vue +309 -0
  26. package/src/components/AppCompSettingsMenu.vue +1 -0
  27. package/src/components/AppCompTableOfContent.vue +140 -85
  28. package/src/components/AppCompTranscript.vue +19 -0
  29. package/src/components/AppCompVideoPlayer.vue +336 -0
  30. package/src/components/BaseModule.vue +24 -105
  31. package/src/main.js +18 -9
  32. package/src/mixins/$pageMixins.js +106 -28
  33. package/src/mixins/timerMixin.js +31 -7
  34. package/src/module/store.js +33 -12
  35. package/src/module/xapi/Crypto/encoders/Hex.js +2 -1
  36. package/src/module/xapi/wrapper.js +4 -4
  37. package/src/plugins/gsap.js +4 -1
  38. package/src/plugins/helper.js +53 -18
  39. package/src/plugins/idb.js +1 -0
  40. package/src/public/index.html +1 -1
  41. package/src/router/index.js +41 -0
  42. package/src/router/routes.js +337 -0
  43. package/src/routes_bckp.js +313 -0
  44. package/src/routes_static.js +344 -0
  45. package/src/shared/generalfuncs.js +79 -4
  46. package/src/shared/validators.js +249 -0
  47. package/src/components/AppCompNavigationFull.vue +0 -1791
  48. package/src/components/AppCompToolTip.vue +0 -94
  49. package/src/routes.js +0 -734
@@ -4,49 +4,32 @@
4
4
  @ Must be used.
5
5
  -->
6
6
  <template>
7
- <div
8
- id="App-base"
9
- fluid
10
- :class="[{ DR: appConfig.isDr }, { iPad: resizeiPad }]"
11
- >
12
- <router-view :key="$route.fullpath" class="box" />
7
+ <div id="App-base" fluid :class="{ iPad: resizeiPad }">
13
8
  <transition name="bounce" mode="in-out">
14
9
  <div
15
- v-show="wrongSide"
16
- class="macWarn macWarn-overlay"
17
- :class="{ warningPortait: wrongSide }"
10
+ v-if="showBuildInfo && !buildInfoClicked"
11
+ id="build-info"
12
+ @click="buildInfoClicked = true"
18
13
  >
19
- <div class="macWarn-box">
20
- <h1>{{ $t('message.bad_orientation_title') }}</h1>
21
- <p>{{ $t('message.bad_orientation_msg') }}</p>
22
- <app-base-button
23
- id="btn-warning-ok"
24
- class="btn"
25
- @click="
26
- () => {
27
- if (wrongSide) wrongSide = !wrongSide
28
- }
29
- "
30
- >
31
- ok
32
- </app-base-button>
33
- </div>
14
+ <span>{{ getModuleInfo.courseID.toUpperCase() }}</span>
15
+ <span>FCAD {{ this.$helper.getFcadVersion() }}</span>
16
+ <span>{{ this.$helper.getBuildTime() }}</span>
34
17
  </div>
35
18
  </transition>
19
+
20
+ <router-view class="box" />
21
+ <app-icons />
36
22
  </div>
37
23
  </template>
38
24
  <script>
39
25
  import { mapGetters } from 'vuex'
40
- import AppBaseButton from './AppBaseButton.vue'
41
26
 
42
27
  export default {
43
- components: { AppBaseButton },
44
28
  props: {
45
29
  appConfig: {
46
30
  type: Object,
47
31
  default: () => {
48
32
  return {
49
- isDr: false, // set Mode of App: true if DR mode false if is Full app mode
50
33
  lang: 'fr' // set language of App: default French
51
34
  }
52
35
  }
@@ -60,7 +43,7 @@ export default {
60
43
  initialDeviceOrentation: null,
61
44
  appIsFullScreen: false,
62
45
  resizeiPad: false,
63
- wrongSide: null
46
+ buildInfoClicked: false
64
47
  }
65
48
  },
66
49
  computed: {
@@ -77,6 +60,12 @@ export default {
77
60
  },
78
61
  getheight() {
79
62
  return window.innerHeight
63
+ },
64
+ showBuildInfo() {
65
+ return (
66
+ process.env.NODE_ENV === 'production' &&
67
+ window.location.host === 'projets.cegepadistance.ca'
68
+ )
80
69
  }
81
70
  },
82
71
  watch: {
@@ -112,22 +101,8 @@ export default {
112
101
  this.getConnectionInfo.actor &&
113
102
  this.getConnectionInfo.remote
114
103
  ) {
115
- /*
116
- * NOTE: At this date, beforeunload event does not give consitent behavior
117
- * to excecute the methode to securely save data to the LMS. To bypass this anomally
118
- * We force the use of message window to give time for the statement to be send to LMS.
119
- * The caviat of this is that if the user decide to stay on the page, inconsistent information of termination is
120
- * recorded in the LMS as user didn't actually leave the page. We cannot controle the sucessfull delivery of
121
- * As of this date, Browser do not give option to execute method base on the user response from the msg window.
122
- *
123
- */
124
-
125
104
  window.addEventListener('beforeunload', (e) => {
126
- // e.preventDefault()
127
-
128
105
  this.executeCloseEventTriggered()
129
-
130
- // return (e.returnValue = '')
131
106
  })
132
107
  } else
133
108
  window.addEventListener(
@@ -157,18 +132,6 @@ export default {
157
132
  }
158
133
 
159
134
  this.$xapi._configLRS(config) // configure and launch LRS
160
-
161
- /* NOTE : Handleling of crossed setting preferrences
162
- * This is an exemple of how to check and handle possible existing preferrences set by the user
163
- * This logic should properly be handle by the component that will controle the user preferrences
164
- */
165
- // const activity_id = this.getModuleInfo.courseID
166
- // ? this.getConnectionInfo.activity_id.replace(
167
- // `/${this.getModuleInfo.id}`,
168
- // ''
169
- // )
170
- // : this.getConnectionInfo.activity_id
171
- // this.$bus.$emit('launch-xapi-resource', activity_id)
172
135
  this.fetchSettingsFromServer()
173
136
  }
174
137
  }
@@ -176,6 +139,8 @@ export default {
176
139
  },
177
140
 
178
141
  created() {
142
+ this.setProgress()
143
+ window.versionFCAD = this.$helper.getFcadVersionString()
179
144
  //check if this is running in a mobile environment and register state in the store
180
145
  const mobileDetect = require('mobile-detect')
181
146
 
@@ -190,46 +155,19 @@ export default {
190
155
 
191
156
  // register device type running the App in the store (ios, Android or Deskop)
192
157
  this.$store.dispatch('setDeviceType', this.detectDevice())
193
-
194
- this.initialDeviceOrentation = window.matchMedia('(orientation:portrait)')
195
- /**
196
- * Note: So far we have noticed an inconsistent beheavor when app is running in Mooddle. on Android Devices, when *
197
- * showing the warning msg for user to turn their device, the listners for unknown reason are not responding. To *
198
- * solve that we have add a button that will close the warning msg when the user click ok. This behavior is
199
- * applayed for all handheld devices, untill suitable solition is found.
200
- */
201
-
202
- //Attaching listener for the MatchMedia
203
- if (this.detectDevice() === 'iOSDevice') {
204
- this.initialDeviceOrentation.addListener(this.toggleChanges)
205
- } else if (this.detectDevice() === 'AndroidDevice') {
206
- this.initialDeviceOrentation.addEventListener('change', (e) => {
207
- this.toggleChanges(e.target)
208
- })
209
- }
210
- //Attaching a listener when app load the 1st time to detect orientation
211
- window.addEventListener('load', () => {
212
- this.toggleChanges(this.initialDeviceOrentation)
213
- })
214
-
215
158
  this.fetchSettingsFromServer()
216
159
  },
217
160
  mounted() {
218
- // put app in DR or Full mode
219
- this.$store.commit('SET_DR', this.appConfig.isDr)
220
161
  // set the language of the app
221
162
  this.setLocale(this.appConfig.lang)
163
+ this.$bus.$on('set-comp-status', (name, status) =>
164
+ this.updateTracker(name, status)
165
+ )
222
166
 
223
- // Listen to the size of the window add to the wrapper
224
- if (!this.$store.state.$appStore.isDr) {
225
- window.addEventListener('resize', function() {
226
- if (this.document.getElementById('wrapper-content')) {
227
- this.document.getElementById(
228
- 'wrapper-content'
229
- ).style.height = `${window.innerHeight - 55}px`
230
- }
231
- })
232
- }
167
+ this.$bus.$on('set-user-progress', this.setProgress())
168
+ },
169
+ beforeDestroy() {
170
+ this.$bus.$off('set-comp-status')
233
171
  },
234
172
  methods: {
235
173
  /**
@@ -243,7 +181,7 @@ export default {
243
181
  if (lang === 'français' || lang === 'francais' || lang === 'french')
244
182
  lang = 'fr'
245
183
  else if (lang === 'english' || lang === 'anglais') lang = 'en'
246
- else lang = lang.substr(0, 2).toLowerCase()
184
+ else lang = lang.substring(0, 2).toLowerCase()
247
185
  this.$i18n.locale = lang
248
186
  }
249
187
  },
@@ -302,21 +240,7 @@ export default {
302
240
 
303
241
  return device
304
242
  },
305
- /**
306
- * @param {MediaQueryList} query
307
- */
308
- toggleChanges(query) {
309
- // Proceed if the app is in full mode
310
- if (!this.getIsMobile || this.isDr) return
311
- //run the query check
312
- else {
313
- if (query.matches) {
314
- this.wrongSide = true
315
- } else {
316
- this.wrongSide = false
317
- }
318
- }
319
- },
243
+
320
244
  fetchSettingsFromServer() {
321
245
  if (this.getModuleInfo.packageType !== 'xapi') return
322
246
  if (!this.getConnectionInfo || !this.getConnectionInfo.remote) return
@@ -373,6 +297,107 @@ export default {
373
297
  this.$bus.$emit('fire-exit-event', null, true)
374
298
  this.routeChangeCounter = 0 //reset counter after saving
375
299
  }
300
+ },
301
+ setProgress() {
302
+ this.updateTracker('appBase', 'loading')
303
+ const packageType = this.getModuleInfo.packageType
304
+
305
+ switch (packageType) {
306
+ case 'scorm':
307
+ // Get saved data from suspend_data or from localStorage to update the store:
308
+ // LMS is connected
309
+ if (this.$scorm.initialized) {
310
+ // chacked if there is a existing record in the LMS
311
+ if (this.$scorm.GetValue('cmi.suspend_data') !== '') {
312
+ const scormRecord = this.$scorm
313
+ .GetValue('cmi.suspend_data')
314
+ .replace(/\\/g, '')
315
+ const userProgress = JSON.parse(scormRecord).userData
316
+ const routeHistory = JSON.parse(scormRecord).routeHistory
317
+
318
+ this.$store.dispatch('setUserMetaData', userProgress)
319
+ this.$store.dispatch('setRouteHistory', routeHistory) // update store recored with existing record
320
+ this.updateTracker('appBase', 'ready')
321
+ }
322
+
323
+ // No LMS use LocalStorage record
324
+ } else {
325
+ this.$idb.openDB().then(() => {
326
+ this.$idb.getFromDB(this.getModuleInfo.idbID).then((res) => {
327
+ if (res && res.$record) {
328
+ const {
329
+ routeHistory,
330
+ userSettings,
331
+ ...userData
332
+ } = res.$record
333
+ this.$store.dispatch('setUserMetaData', userData) // update store record with existing record
334
+ this.$store.dispatch('setRouteHistory', routeHistory) // update store record with existing route info
335
+ if (userSettings)
336
+ this.$store.dispatch('setApplicationSettings', userSettings) // update store record with existing user settings
337
+ this.updateTracker('appBase', 'ready')
338
+ }
339
+ })
340
+ })
341
+ }
342
+
343
+ break
344
+
345
+ case 'xapi':
346
+ {
347
+ if (
348
+ this.getConnectionInfo &&
349
+ this.getConnectionInfo.actor &&
350
+ this.getConnectionInfo.remote
351
+ ) {
352
+ //Try to get the user progress from LRS
353
+ const userProgress = this.$xapi._getProgress(
354
+ this.getConnectionInfo.actor.mbox.replace('mailto:', ''),
355
+ this.getConnectionInfo.activity_id
356
+ )
357
+ if (userProgress) {
358
+ const { routeHistory, ...userData } = userProgress
359
+ // console.log('Progress from Server: ', {
360
+ // userData,
361
+ // routeHistory
362
+ // })
363
+ this.$store.dispatch('setUserMetaData', userData) // update store record with existing record
364
+ this.$store.dispatch('setRouteHistory', routeHistory) // update store record with existing record
365
+ }
366
+ this.updateTracker('appBase', 'ready')
367
+ } else {
368
+ //Get existing records for user data in local store
369
+ this.$idb.openDB().then(() => {
370
+ this.$idb
371
+ .getFromDB(this.getModuleInfo.idbID)
372
+ .then((res) => {
373
+ if (res && res.$record) {
374
+ const {
375
+ routeHistory,
376
+ userSettings,
377
+ ...userData
378
+ } = res.$record
379
+
380
+ this.$store.dispatch('setUserMetaData', userData) // update store record with existing record
381
+ this.$store.dispatch('setRouteHistory', routeHistory) // update store record with existing route info
382
+
383
+ if (userSettings) {
384
+ this.$store.dispatch(
385
+ 'setApplicationSettings',
386
+ userSettings
387
+ ) // update store record with existing user setting
388
+ }
389
+ }
390
+ })
391
+ .then(() => this.updateTracker('appBase', 'ready'))
392
+ })
393
+ }
394
+ }
395
+
396
+ break
397
+ }
398
+ },
399
+ updateTracker(name, status) {
400
+ this.$store.dispatch('updateCompStatusTracker', { name, status })
376
401
  }
377
402
  }
378
403
  }
@@ -382,64 +407,99 @@ export default {
382
407
  width: 100%;
383
408
  height: 100vh;
384
409
 
385
- /********** FM **********/
386
- &:not(.DR) {
387
- .module {
388
- width: 100%;
410
+ #build-info {
411
+ opacity: 0.9;
412
+ position: fixed;
413
+ right: 0;
414
+ bottom: 0;
415
+ color: #fff;
416
+ text-shadow: -1px 1px 1px rgba(0, 0, 0, 0.4);
417
+ background-color: hotpink;
418
+ font-family: serif, Impact, Arial;
419
+ font-size: 11px;
420
+ padding: 3px;
421
+ cursor: pointer;
422
+ z-index: 999;
423
+ span {
424
+ text-align: center;
425
+ display: block;
426
+ }
427
+ }
428
+
429
+ .module {
430
+ width: 100%;
431
+ height: 100%;
432
+ position: relative;
433
+ display: flex;
434
+ flex-direction: row;
435
+ align-items: stretch;
436
+
437
+ .navbar {
438
+ position: absolute;
439
+ top: 0;
440
+ left: 0;
441
+ z-index: 10;
442
+ display: flex;
443
+ flex-direction: column;
444
+ flex-wrap: wrap;
445
+ align-content: start;
446
+ width: 67px;
389
447
  height: 100%;
390
- // overflow: hidden;
448
+ }
449
+
450
+ .module-wrapper {
451
+ width: 100%;
391
452
  position: relative;
392
- overscroll-behavior-y: none;
393
- -webkit-overflow-scrolling: touch;
394
453
 
395
- .module-wrapper {
454
+ #wrapper-content {
396
455
  width: 100%;
397
456
  position: relative;
457
+ padding-right: 0 !important;
458
+ padding-left: 0 !important;
398
459
 
399
- #wrapper-content {
400
- display: -webkit-flex;
401
- display: flex;
402
- min-width: 100%;
403
- overflow: auto;
404
- position: relative;
405
- padding-right: 0 !important;
406
- padding-left: 0 !important;
460
+ &.active {
461
+ overflow: hidden;
462
+ }
407
463
 
408
- &.active {
409
- overflow: hidden;
410
- }
464
+ .box {
465
+ width: 100%;
466
+ height: 95%;
411
467
 
412
- .box {
468
+ .app-page {
413
469
  width: 100%;
414
- height: 95%;
470
+ margin-top: 60px;
415
471
 
416
- .app-page {
472
+ .row {
417
473
  width: 100%;
418
- min-width: 100%;
419
- margin-top: 60px;
420
-
421
- .row {
422
- margin-right: 0;
423
- margin-left: 0;
424
- }
474
+ margin-right: 0;
475
+ margin-left: 0;
425
476
  }
426
477
  }
427
478
  }
479
+ }
428
480
 
429
- .app-comp-table-of-content {
430
- display: -webkit-flex;
431
- display: flex;
432
- flex-direction: column;
433
- flex-wrap: wrap;
434
- position: absolute;
435
- top: 0px;
436
- min-height: 100%;
437
- z-index: 1;
438
- }
481
+ .app-comp-table-of-content {
482
+ display: -webkit-flex;
483
+ display: flex;
484
+ flex-direction: column;
485
+ flex-wrap: wrap;
486
+ position: absolute;
487
+ top: 0px;
488
+ min-height: 100%;
489
+ z-index: 1;
439
490
  }
491
+ }
440
492
 
441
- .app-nav {
442
- .md-controller {
493
+ .app-nav {
494
+ .md-controller {
495
+ display: -webkit-flex;
496
+ display: flex;
497
+ flex-direction: row;
498
+ align-items: baseline;
499
+ //width: $widthPlayer;
500
+ width: 50%;
501
+
502
+ .ctrl-play {
443
503
  display: -webkit-flex;
444
504
  display: flex;
445
505
  flex-direction: row;
@@ -447,121 +507,45 @@ export default {
447
507
  //width: $widthPlayer;
448
508
  width: 50%;
449
509
 
450
- .ctrl-play {
451
- display: -webkit-flex;
452
- display: flex;
453
- flex-direction: row;
454
- align-items: baseline;
455
- //width: $widthPlayer;
456
- width: 50%;
510
+ #progress-bar {
511
+ display: block;
512
+ //width: $widthProgressBar;
513
+ width: 150px;
514
+ //height: $heigthProgressBar;
515
+ height: 10px;
516
+ position: relative;
517
+ overflow: hidden;
457
518
 
458
- #progress-bar {
519
+ #progress {
459
520
  display: block;
460
- //width: $widthProgressBar;
461
- width: 150px;
462
- //height: $heigthProgressBar;
463
- height: 10px;
464
- position: relative;
465
- overflow: hidden;
466
-
467
- #progress {
468
- display: block;
469
- height: 100%;
470
- }
471
-
472
- #progress-shaddow {
473
- display: block;
474
- height: 100%;
475
- position: absolute;
476
- top: 0px;
477
- left: 0px;
478
- z-index: -1;
479
- }
521
+ height: 100%;
522
+ background-color: red;
480
523
  }
481
- }
482
-
483
- .ctrl-subtitle {
484
- //width: $widthCtrSubtitle;
485
- width: 10%;
486
- }
487
524
 
488
- .ctrl-sound {
489
- display: -webkit-flex;
490
- display: flex;
491
- flex-direction: row;
492
- align-items: baseline;
493
- justify-content: space-between;
494
- //width: $widthCtrlSound;
495
- width: 40%;
525
+ #progress-shaddow {
526
+ display: block;
527
+ height: 100%;
528
+ position: absolute;
529
+ top: 0px;
530
+ left: 0px;
531
+ z-index: -1;
532
+ }
496
533
  }
497
534
  }
498
- }
499
- }
500
- }
501
-
502
- /********** DR **********/
503
- &.DR {
504
- .module {
505
- position: relative;
506
-
507
- .module-wrapper {
508
- width: 100%;
509
- margin-top: 65px;
510
- /// METRE VH
511
- position: relative;
512
535
 
513
- #wrapper-content {
514
- width: 100%;
515
- height: 100%;
516
-
517
- .app-page {
518
- margin-top: 100px;
519
- }
536
+ .ctrl-subtitle {
537
+ //width: $widthCtrSubtitle;
538
+ width: 10%;
520
539
  }
521
- }
522
- }
523
- }
524
- }
525
-
526
- html {
527
- &:fullscreen,
528
- &:-moz-full-screen {
529
- #App {
530
- width: 100%;
531
- height: 100vh;
532
540
 
533
- .box {
534
- height: 100%;
535
-
536
- .module {
537
- height: 100%;
538
-
539
- .app-nav {
540
- bottom: 0px;
541
- z-index: 9999;
542
- }
543
-
544
- .module-wrapper {
545
- height: 100%;
546
-
547
- #wrapper-content {
548
- height: 100%;
549
- display: -webkit-flex;
550
- display: flex;
551
- align-items: center;
552
-
553
- .box {
554
- display: -webkit-flex;
555
- display: flex;
556
- align-items: center;
557
-
558
- .app-page {
559
- align-content: center;
560
- align-items: center;
561
- }
562
- }
563
- }
564
- }
541
+ .ctrl-sound {
542
+ display: -webkit-flex;
543
+ display: flex;
544
+ flex-direction: row;
545
+ align-items: baseline;
546
+ justify-content: space-between;
547
+ //width: $widthCtrlSound;
548
+ width: 40%;
565
549
  }
566
550
  }
567
551
  }
@@ -615,21 +599,16 @@ html {
615
599
  /* .component-fade-leave-active below version 2.1.8 */ {
616
600
  opacity: 0;
617
601
  }
618
- .bounce-enter-active {
619
- animation: bounce-in 0.5s;
620
- }
621
- .bounce-leave-active {
622
- animation: bounce-in 0.5s reverse;
602
+
603
+ .app-icons-svg {
604
+ width: 30px;
605
+ height: 30px;
606
+ fill: #fff;
607
+ stroke: #fff;
608
+ cursor: pointer;
623
609
  }
624
- @keyframes bounce-in {
625
- 0% {
626
- transform: scale(0);
627
- }
628
- 50% {
629
- transform: scale(1.5);
630
- }
631
- 100% {
632
- transform: scale(1);
633
- }
610
+
611
+ .svg-hidden {
612
+ display: none;
634
613
  }
635
614
  </style>
@@ -127,6 +127,35 @@
127
127
  </p>
128
128
  </div>
129
129
  </div>
130
+ <div v-if="errorMessage === 'errorMenuBranching'" class="box-error">
131
+ <div class="red-box">
132
+ <div class="box">
133
+ <h2>
134
+ <b-icon icon="exclamation-triangle" />
135
+ Erreur d'embranchement.
136
+ </h2>
137
+
138
+ <p>
139
+ Vous ne pouvez pas avoir de pages d'embranchement(s) dans le dossier
140
+ A00
141
+ </p>
142
+ </div>
143
+ </div>
144
+ <div class="box">
145
+ <p class="doc">
146
+ Visitez
147
+ <a
148
+ :href="
149
+ `https://fcaddocumentation.netlify.app/guide/structure.html#pages-d-embranchements-dans-un-dossier`
150
+ "
151
+ target="blank"
152
+ >
153
+ la documentation
154
+ </a>
155
+ pour plus de details ...
156
+ </p>
157
+ </div>
158
+ </div>
130
159
  <div v-if="errorMessage === '404'" class="box-error">
131
160
  <div class="red-box">
132
161
  <div class="box">