fcad-core-dragon 2.1.0-beta.4 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.editorconfig +8 -33
  2. package/.prettierrc +11 -0
  3. package/.vscode/extensions.json +8 -0
  4. package/.vscode/settings.json +16 -0
  5. package/CHANGELOG +20 -0
  6. package/eslint.config.js +60 -0
  7. package/package.json +9 -9
  8. package/src/$locales/en.json +3 -3
  9. package/src/$locales/fr.json +3 -3
  10. package/src/assets/data/onboardingMessages.json +47 -47
  11. package/src/components/AppBase.vue +5 -6
  12. package/src/components/AppBaseErrorDisplay.vue +438 -438
  13. package/src/components/AppBaseFlipCard.vue +84 -84
  14. package/src/components/AppBaseModule.vue +15 -17
  15. package/src/components/AppBasePage.vue +91 -8
  16. package/src/components/AppBasePopover.vue +41 -41
  17. package/src/components/AppBaseSkeleton.vue +24 -3
  18. package/src/components/AppCompAudio.vue +12 -2
  19. package/src/components/AppCompInputCheckBoxNx.vue +1 -2
  20. package/src/components/AppCompInputRadioNx.vue +8 -2
  21. package/src/components/AppCompInputTextToFillDropdownNx.vue +45 -0
  22. package/src/components/AppCompMenu.vue +423 -423
  23. package/src/components/AppCompNavigation.vue +2 -2
  24. package/src/components/AppCompPlayBarNext.vue +123 -94
  25. package/src/components/AppCompPopUpNext.vue +2 -2
  26. package/src/components/AppCompQuizNext.vue +12 -2
  27. package/src/components/AppCompQuizRecall.vue +10 -4
  28. package/src/components/AppCompSettingsMenu.vue +172 -172
  29. package/src/components/AppCompTableOfContent.vue +1 -4
  30. package/src/components/AppCompVideoPlayer.vue +7 -0
  31. package/src/components/AppCompViewDisplay.vue +6 -6
  32. package/src/composables/useTimer.js +17 -20
  33. package/src/externalComps/ModuleView.vue +22 -22
  34. package/src/externalComps/SummaryView.vue +91 -91
  35. package/src/main.js +5 -5
  36. package/src/module/stores/appStore.js +0 -1
  37. package/src/module/xapi/Crypto/Hasher.js +241 -241
  38. package/src/module/xapi/Crypto/WordArray.js +278 -278
  39. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  40. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  41. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  42. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  43. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  44. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  45. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  46. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  47. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  48. package/src/module/xapi/Crypto/index.js +53 -53
  49. package/src/module/xapi/Statement/activity.js +47 -47
  50. package/src/module/xapi/Statement/agent.js +55 -55
  51. package/src/module/xapi/Statement/group.js +26 -26
  52. package/src/module/xapi/Statement/index.js +259 -259
  53. package/src/module/xapi/Statement/statement.js +253 -253
  54. package/src/module/xapi/Statement/statementRef.js +23 -23
  55. package/src/module/xapi/Statement/substatement.js +22 -22
  56. package/src/module/xapi/Statement/verb.js +36 -36
  57. package/src/module/xapi/activitytypes.js +17 -17
  58. package/src/module/xapi/utils.js +167 -167
  59. package/src/module/xapi/verbs.js +294 -294
  60. package/src/module/xapi/xapiStatement.js +444 -444
  61. package/src/plugins/analytics.js +4 -4
  62. package/src/plugins/bus.js +8 -8
  63. package/src/plugins/gsap.js +4 -2
  64. package/src/plugins/helper.js +8 -4
  65. package/src/plugins/save.js +37 -37
  66. package/src/plugins/scorm.js +287 -287
  67. package/src/plugins/xapi.js +11 -11
  68. package/src/public/index.html +33 -33
  69. package/src/router/index.js +1 -1
  70. package/src/shared/validators.js +22 -6
  71. package/.eslintignore +0 -29
  72. package/.eslintrc.cjs +0 -81
  73. package/bk.scss +0 -117
@@ -531,8 +531,8 @@ export default {
531
531
  const endPopup = {
532
532
  type: 'popup-endActivity',
533
533
  value: {
534
- template: `<p>${this.$t('popup.text_what_to_do')}</p>`,
535
- title: ''
534
+ template: ``,
535
+ title: `${this.$t('popup.text_what_to_do')}`
536
536
  }
537
537
  }
538
538
 
@@ -432,7 +432,8 @@ mediaMixins is used for all the methods/data shared between audio and video. In
432
432
  </div>
433
433
  <div v-if="appDebugMode" class="timer">
434
434
  <!-- <div class="timer"> -->
435
- {{ timer.getElapsedTime() }}
435
+ <p></p>
436
+ <p>Timer : {{ timer.getElapsedTime() }}</p>
436
437
  </div>
437
438
  </div>
438
439
  </div>
@@ -712,6 +713,12 @@ export default {
712
713
  }
713
714
  },
714
715
  watch: {
716
+ viewedThresholdReached: {
717
+ handler() {
718
+ //send a "viewed" event for this media
719
+ this.trackEvent('end')
720
+ }
721
+ },
715
722
  getUserInteraction: {
716
723
  handler() {
717
724
  const activityID = this.$route.meta.activity_ref
@@ -791,6 +798,96 @@ export default {
791
798
  'setMediaMuted',
792
799
  'setMediaPlaybarValues'
793
800
  ]),
801
+ trackEvent(action) {
802
+ const expectedActions = ['play', 'viewed']
803
+ if (!action || !expectedActions.includes(action)) return
804
+ const media = this.mediaRawData
805
+ if (!media || !media.mSources || !media.mSources.length) return
806
+ const { mTitle, mSources } = media
807
+ const { mType } = this.mediaToPlay
808
+ const URIBase = `media/${mType}/`
809
+
810
+ //send statement
811
+ const stmt = {
812
+ id: (() => `${URIBase}${mSources[0].src.split('/').toReversed()[0]}`)(),
813
+ verb: null,
814
+ definition: mTitle || mSources[0].src,
815
+ description: null,
816
+ type: `https://w3id.org/xapi/${mType}/activity-type/${mType}`,
817
+ context: {
818
+ contextActivities: {
819
+ category: [{ id: `https://w3id.org/xapi/${mType}` }]
820
+ },
821
+ extensions: {
822
+ 'https://w3id.org/xapi/video/extensions/session-id':
823
+ this.getModuleInfo.id
824
+ }
825
+ },
826
+ result: null
827
+ }
828
+ switch (action) {
829
+ case 'play': {
830
+ stmt.verb = 'played'
831
+ stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
832
+ stmt.result = {
833
+ extensions: {
834
+ 'https://w3id.org/xapi/video/extensions/time': Math.round(
835
+ this.startedTime
836
+ ),
837
+ 'https://w3id.org/xapi/video/extensions/progress': Math.round(
838
+ (this.startedTime / this.mediaDuration) * 100
839
+ )
840
+ }
841
+ }
842
+ break
843
+ }
844
+ case 'viewed': {
845
+ const expectedViewTime = Number(
846
+ this.timer.getElapsedTime() / Math.trunc(this.mediaDuration)
847
+ )
848
+ stmt.verb = 'completed'
849
+ stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
850
+ stmt.result = {
851
+ extensions: {
852
+ 'https://w3id.org/xapi/video/extensions/progress':
853
+ expectedViewTime * 100,
854
+ 'https://w3id.org/xapi/video/extensions/time-from': Math.round(
855
+ this.startedTime
856
+ ),
857
+ 'https://w3id.org/xapi/video/extensions/time-to': this.currentTime
858
+ },
859
+ completion:
860
+ expectedViewTime >= this.viewedThreshold &&
861
+ this.viewedThresholdReached
862
+ }
863
+ stmt.duration = this.timer.ISOTimeParser(
864
+ expectedViewTime * this.mediaDuration
865
+ )
866
+ break
867
+ }
868
+ }
869
+ this.$bus.$emit('send-xapi-statement', stmt)
870
+ //send Analytics Event
871
+ const analyticsEventName = `fcad_${mType}_${action}`
872
+ this.trackWithGA(analyticsEventName)
873
+ },
874
+ trackWithGA(eventName) {
875
+ if (
876
+ !eventName ||
877
+ !this.mediaRawData ||
878
+ !this.mediaRawData.mSources ||
879
+ !this.mediaRawData.mSources.length
880
+ )
881
+ return
882
+ const { mTitle, mSources } = this.mediaRawData
883
+
884
+ const eventParams = {
885
+ title: mTitle || mSources[0].src,
886
+ url: mSources[0].src
887
+ }
888
+
889
+ this.$analytics.sendEvent(eventName, eventParams)
890
+ },
794
891
 
795
892
  //======================================================================
796
893
  /**
@@ -882,7 +979,8 @@ export default {
882
979
  !this.isPlaying ? this.timer.start() : this.timer.pause()
883
980
 
884
981
  if (!this.playStmtSent) {
885
- this.sendMediaStatement('play', this.startedTime, null)
982
+ //this.sendMediaStatement("play", this.startedTime, null);
983
+ this.trackEvent('play')
886
984
  this.playStmtSent = true
887
985
  }
888
986
  //Data
@@ -918,14 +1016,10 @@ export default {
918
1016
  if (this.mediaElement && this.isPlaying) {
919
1017
  this.mediaElement.pause()
920
1018
  this.isPlaying = false
921
-
922
- const expectedViewTime = Number(
923
- this.timer.getElapsedTime() / Math.trunc(this.mediaDuration)
924
- ) //relation view time to media duration
925
-
926
1019
  this.timer.stop()
927
1020
 
928
- this.sendMediaStatement('end', this.startedTime, expectedViewTime)
1021
+ //this.sendMediaStatement("end", this.startedTime, expectedViewTime);
1022
+ this.trackEvent('viewed')
929
1023
  this.playStmtSent = false //reset the play statement for next play
930
1024
  }
931
1025
  this.$bus.$emit('media-viewed', this.mediaToPlay.id)
@@ -1174,7 +1268,10 @@ export default {
1174
1268
  this.currentTime = e.target.currentTime
1175
1269
 
1176
1270
  //Set viewedThresholdReached to true when the threshold is reached
1177
- if (Math.trunc(this.progressBarPercentage) == this.viewedThreshold * 100)
1271
+ if (
1272
+ this.timer.getElapsedTime() / Math.trunc(this.mediaDuration) >=
1273
+ this.viewedThreshold
1274
+ )
1178
1275
  this.viewedThresholdReached = true
1179
1276
 
1180
1277
  //Update strings
@@ -1529,6 +1626,7 @@ export default {
1529
1626
  */
1530
1627
  toggleFullScreen() {
1531
1628
  const fullscreenElement = this.mediaContainer
1629
+ console.log(fullscreenElement)
1532
1630
 
1533
1631
  if (document.fullscreenElement) {
1534
1632
  // exitFullscreen is only available on the Document object.
@@ -1682,7 +1780,7 @@ export default {
1682
1780
  this.hideTimer = setTimeout(() => {
1683
1781
  this.showControlsValue = false
1684
1782
  }, this.delayUntilHide)
1685
- },
1783
+ }
1686
1784
 
1687
1785
  /**
1688
1786
  * @description - Send xAPI statement for media play and end
@@ -1690,87 +1788,6 @@ export default {
1690
1788
  * @param {Number} startTime - time in seconds when the media start to be played
1691
1789
  * @param {Number} endTime - time in seconds when the media stop to be played
1692
1790
  */
1693
- async sendMediaStatement(action, startTime = null, endTime = null) {
1694
- const expectedActions = ['play', 'end']
1695
- if (!action || !expectedActions.includes(action)) return
1696
-
1697
- // const { audiosData, videosData } = this.getCurrentPage
1698
- const media = this.mediaRawData
1699
- // this.mediaToPlay.mType == 'video'
1700
- // ? videosData.find((el) => el.id == this.mediaToPlay.id)
1701
- // : audiosData.find((el) => el.id == this.mediaToPlay.id)
1702
-
1703
- if (!media || !media.mSources || !media.mSources.length) return
1704
-
1705
- const { mTitle, mSources } = media
1706
- const { mType } = this.mediaToPlay
1707
-
1708
- const URIBase = `media/${mType}/`
1709
-
1710
- const stmt = {
1711
- id: (() => `${URIBase}${mSources[0].src.split('/').toReversed()[0]}`)(),
1712
- verb: null,
1713
- definition: mTitle || mSources[0].src,
1714
- description: null,
1715
- type: `https://w3id.org/xapi/${mType}/activity-type/${mType}`,
1716
- context: {
1717
- contextActivities: {
1718
- category: [{ id: `https://w3id.org/xapi/${mType}` }]
1719
- },
1720
- extensions: {
1721
- 'https://w3id.org/xapi/video/extensions/session-id':
1722
- this.getModuleInfo.id
1723
- }
1724
- },
1725
- result: null
1726
- }
1727
-
1728
- switch (action) {
1729
- case 'play': {
1730
- stmt.verb = 'played'
1731
- stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
1732
- stmt.result = {
1733
- extensions: {
1734
- 'https://w3id.org/xapi/video/extensions/time':
1735
- Math.round(startTime),
1736
- 'https://w3id.org/xapi/video/extensions/progress': Math.round(
1737
- (startTime / this.mediaDuration) * 100
1738
- )
1739
- }
1740
- }
1741
- break
1742
- }
1743
-
1744
- case 'end': {
1745
- stmt.verb = 'completed'
1746
- stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
1747
- stmt.result = {
1748
- extensions: {
1749
- 'https://w3id.org/xapi/video/extensions/progress': endTime * 100,
1750
- 'https://w3id.org/xapi/video/extensions/time-from':
1751
- Math.round(startTime),
1752
- 'https://w3id.org/xapi/video/extensions/time-to': this.currentTime
1753
- },
1754
- completion:
1755
- endTime >= this.viewedThreshold && this.viewedThresholdReached
1756
- }
1757
- stmt.duration = this.timer.ISOTimeParser(endTime * this.mediaDuration)
1758
- break
1759
- }
1760
- }
1761
-
1762
- /* send play/end events to Google Analytics */
1763
- const analyticsEventName = `fcad_${mType}_${action}`
1764
- const analyticsEventParams = {
1765
- title: stmt.definition,
1766
- url: mSources[0].src
1767
- }
1768
- if (action === 'end') {
1769
- analyticsEventParams.viewed = this.viewedThresholdReached
1770
- }
1771
- this.$analytics.sendEvent(analyticsEventName, analyticsEventParams)
1772
- this.$bus.$emit('send-xapi-statement', stmt)
1773
- }
1774
1791
  }
1775
1792
  }
1776
1793
  </script>
@@ -1781,19 +1798,21 @@ export default {
1781
1798
  width: 100%;
1782
1799
  justify-content: center;
1783
1800
  display: flex;
1801
+ margin-bottom: 78px;
1784
1802
  }
1785
1803
 
1786
1804
  .playback-button {
1787
1805
  position: absolute;
1788
- top: 0;
1806
+ bottom: 0;
1789
1807
  left: 0;
1790
1808
  width: 100%;
1791
- height: 100%;
1809
+ height: 94%;
1792
1810
  display: flex;
1793
1811
  flex-flow: column wrap;
1794
1812
  justify-content: center;
1795
1813
  align-items: center;
1796
1814
  background-color: transparent;
1815
+
1797
1816
  .playback-wrapper {
1798
1817
  height: 64px;
1799
1818
  width: 64px;
@@ -1817,9 +1836,10 @@ export default {
1817
1836
  display: flex;
1818
1837
  flex-direction: column;
1819
1838
  position: absolute;
1820
- bottom: 0;
1839
+ bottom: -78px;
1821
1840
  opacity: 0;
1822
1841
  z-index: 2;
1842
+
1823
1843
  &.show-controls {
1824
1844
  opacity: 1;
1825
1845
  transition: opacity 0.35s ease-in-out;
@@ -2158,6 +2178,7 @@ export default {
2158
2178
  min-width: 50px;
2159
2179
  margin: 0 auto;
2160
2180
  }
2181
+
2161
2182
  .progress-area {
2162
2183
  justify-content: center;
2163
2184
  }
@@ -2235,6 +2256,14 @@ export default {
2235
2256
  }
2236
2257
  }
2237
2258
 
2259
+ .FS {
2260
+ &:fullscreen {
2261
+ .pb-wrapper {
2262
+ bottom: -45px;
2263
+ }
2264
+ }
2265
+ }
2266
+
2238
2267
  @keyframes handlePlayBack {
2239
2268
  0% {
2240
2269
  opacity: 0;
@@ -61,7 +61,7 @@
61
61
  <div class="pop-containt">
62
62
  <div class="pop-box">
63
63
  <div id="popHeader">
64
- <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
64
+ <div id="dialogTitle" class="p-title" v-html="pTitle"></div>
65
65
  </div>
66
66
  <div class="box-content-popUp"></div>
67
67
  <div
@@ -103,7 +103,7 @@
103
103
  <div class="pop-containt">
104
104
  <div class="pop-box">
105
105
  <div id="popHeader">
106
- <h4 id="dialogTitle" class="p-title" v-html="pTitle"></h4>
106
+ <p id="dialogTitle" class="p-title" v-html="pTitle"></p>
107
107
  </div>
108
108
  <div class="box-content-popUp">
109
109
  <template v-if="contentLength > 0">
@@ -22,7 +22,11 @@
22
22
  >
23
23
  <!--Show skeleton while quiz data is not ready-->
24
24
  <template v-if="!isReady">
25
- <app-base-skeleton :skeleton-text="''" :skeleton-lines="5" />
25
+ <app-base-skeleton
26
+ :skeleton-text="''"
27
+ :skeleton-type="skeletonType(quizData.type_question)"
28
+ />
29
+ {{ quizData.type_question }}
26
30
  </template>
27
31
  <!--Show when quiz data is ready-->
28
32
  <template v-else>
@@ -185,7 +189,6 @@ export default {
185
189
 
186
190
  return quiz
187
191
  },
188
-
189
192
  retroAriaLabel() {
190
193
  let label = ''
191
194
  switch (this.retroType) {
@@ -489,6 +492,13 @@ export default {
489
492
  if (el.id !== this.quizData.id) return
490
493
 
491
494
  this.showRetro = value
495
+ },
496
+ skeletonType(type) {
497
+ if (type == 'reponse_ouverte') {
498
+ return `quiz-texte`
499
+ } else {
500
+ return `quiz-choix`
501
+ }
492
502
  }
493
503
  }
494
504
  }
@@ -10,7 +10,7 @@
10
10
  <!--Optionnal title, out of quiz-answer-conditionning, default tag H4, but can be change by user-->
11
11
  <!--Show skeleton while app data is not ready-->
12
12
  <template v-if="!isReady">
13
- <app-base-skeleton :skeleton-lines="2" />
13
+ <app-base-skeleton :skeleton-text="''" :skeleton-type="`quiz-recall`" />
14
14
  </template>
15
15
  <!--Show app data when ready-->
16
16
  <template v-else>
@@ -140,10 +140,17 @@ export default {
140
140
  for (let property in searchObject) {
141
141
  //Should only check for not null poperties
142
142
  if (!searchObject[property]) continue
143
+
143
144
  // Should only check for Object or Array property
144
145
  if (
145
- searchObject[property].constructor !== Array &&
146
- searchObject[property].constructor !== Object
146
+ searchObject[property].constructor !== Object &&
147
+ searchObject[property].constructor !== Array
148
+ )
149
+ continue
150
+
151
+ if (
152
+ searchObject[property].constructor == Array &&
153
+ searchObject[property][0].constructor !== Object
147
154
  )
148
155
  continue
149
156
 
@@ -152,7 +159,6 @@ export default {
152
159
  searchObject[property].constructor === Object
153
160
  ? [searchObject[property]]
154
161
  : searchObject[property]
155
-
156
162
  //Only Search for quiz:
157
163
  //Search Array is a list of quiz if one of its element has at least the property 'type_question'
158
164
  return searchArray[0]['type_question']