fcad-core-dragon 2.1.0 → 2.1.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 (160) hide show
  1. package/.editorconfig +7 -7
  2. package/.gitlab-ci.yml +124 -0
  3. package/.prettierrc +11 -11
  4. package/.vscode/extensions.json +8 -8
  5. package/.vscode/settings.json +46 -16
  6. package/CHANGELOG +520 -520
  7. package/README.md +57 -57
  8. package/documentation/.vitepress/config.js +114 -114
  9. package/documentation/api-examples.md +49 -49
  10. package/documentation/composants/app-base-button.md +58 -58
  11. package/documentation/composants/app-base-error-display.md +59 -59
  12. package/documentation/composants/app-base-popover.md +68 -68
  13. package/documentation/composants/app-comp-audio.md +75 -75
  14. package/documentation/composants/app-comp-branch-buttons.md +111 -111
  15. package/documentation/composants/app-comp-button-progress.md +53 -53
  16. package/documentation/composants/app-comp-carousel.md +53 -53
  17. package/documentation/composants/app-comp-container.md +53 -53
  18. package/documentation/composants/app-comp-input-checkbox-next.md +42 -42
  19. package/documentation/composants/app-comp-input-dropdown-next.md +34 -34
  20. package/documentation/composants/app-comp-input-radio-next.md +39 -39
  21. package/documentation/composants/app-comp-input-text-next.md +35 -35
  22. package/documentation/composants/app-comp-input-text-table-next.md +34 -34
  23. package/documentation/composants/app-comp-input-text-to-fill-dropdown-next.md +53 -53
  24. package/documentation/composants/app-comp-input-text-to-fill-next.md +31 -31
  25. package/documentation/composants/app-comp-jauge.md +31 -31
  26. package/documentation/composants/app-comp-menu-item.md +55 -55
  27. package/documentation/composants/app-comp-menu.md +29 -29
  28. package/documentation/composants/app-comp-navigation.md +41 -41
  29. package/documentation/composants/app-comp-note-call.md +53 -53
  30. package/documentation/composants/app-comp-note-credit.md +53 -53
  31. package/documentation/composants/app-comp-play-bar-next.md +53 -53
  32. package/documentation/composants/app-comp-pop-up-next.md +93 -93
  33. package/documentation/composants/app-comp-quiz-next.md +235 -235
  34. package/documentation/composants/app-comp-quiz-recall.md +53 -53
  35. package/documentation/composants/app-comp-svg-next.md +53 -53
  36. package/documentation/composants/app-comp-table-of-content.md +50 -50
  37. package/documentation/composants/app-comp-video-player.md +82 -82
  38. package/documentation/composants.md +46 -46
  39. package/documentation/composants_critiques/ModelPageComposant.md +53 -53
  40. package/documentation/composants_critiques/app-base-module.md +43 -43
  41. package/documentation/composants_critiques/app-base-page.md +48 -48
  42. package/documentation/composants_critiques/app-base.md +311 -311
  43. package/documentation/composants_critiques/main.md +15 -15
  44. package/documentation/demarrage.md +50 -50
  45. package/documentation/deploiement.md +57 -57
  46. package/documentation/index.md +33 -33
  47. package/documentation/markdown-examples.md +85 -85
  48. package/documentation/public/vite.svg +14 -14
  49. package/documentation/public/vuejs.svg +1 -1
  50. package/documentation/public/vuetify.svg +5 -5
  51. package/eslint.config.js +60 -60
  52. package/junit-report.xml +182 -0
  53. package/package.json +66 -59
  54. package/playwright/index.html +12 -0
  55. package/playwright/index.js +21 -0
  56. package/playwright-ct.config.js +95 -0
  57. package/src/$locales/en.json +157 -157
  58. package/src/$locales/fr.json +120 -120
  59. package/src/assets/data/onboardingMessages.json +47 -47
  60. package/src/components/AppBase.vue +1171 -1169
  61. package/src/components/AppBaseButton.vue +90 -95
  62. package/src/components/AppBaseErrorDisplay.vue +438 -438
  63. package/src/components/AppBaseFlipCard.vue +84 -84
  64. package/src/components/AppBaseModule.vue +1639 -1634
  65. package/src/components/AppBasePage.vue +3 -2
  66. package/src/components/AppBasePopover.vue +41 -41
  67. package/src/components/AppBaseSkeleton.vue +66 -66
  68. package/src/components/AppCompAudio.vue +261 -256
  69. package/src/components/AppCompBranchButtons.vue +508 -508
  70. package/src/components/AppCompButtonProgress.vue +137 -132
  71. package/src/components/AppCompCarousel.vue +342 -336
  72. package/src/components/AppCompContainer.vue +29 -29
  73. package/src/components/AppCompInputCheckBoxNx.vue +325 -323
  74. package/src/components/AppCompInputDropdownNx.vue +302 -299
  75. package/src/components/AppCompInputRadioNx.vue +287 -284
  76. package/src/components/AppCompInputTextNx.vue +156 -153
  77. package/src/components/AppCompInputTextTableNx.vue +205 -202
  78. package/src/components/AppCompInputTextToFillDropdownNx.vue +343 -340
  79. package/src/components/AppCompInputTextToFillNx.vue +316 -313
  80. package/src/components/AppCompJauge.vue +81 -81
  81. package/src/components/AppCompMenu.vue +6 -2
  82. package/src/components/AppCompMenuItem.vue +246 -240
  83. package/src/components/AppCompNavigation.vue +977 -972
  84. package/src/components/AppCompNoteCall.vue +167 -161
  85. package/src/components/AppCompNoteCredit.vue +496 -491
  86. package/src/components/AppCompPlayBarNext.vue +2290 -2288
  87. package/src/components/AppCompPopUpNext.vue +508 -504
  88. package/src/components/AppCompQuizNext.vue +515 -510
  89. package/src/components/AppCompQuizRecall.vue +355 -350
  90. package/src/components/AppCompSVGNext.vue +346 -346
  91. package/src/components/AppCompSettingsMenu.vue +177 -172
  92. package/src/components/AppCompTableOfContent.vue +433 -427
  93. package/src/components/AppCompVideoPlayer.vue +377 -377
  94. package/src/components/AppCompViewDisplay.vue +6 -6
  95. package/src/components/BaseModule.vue +55 -55
  96. package/src/composables/useIdleDetector.js +56 -56
  97. package/src/composables/useQuiz.js +89 -89
  98. package/src/composables/useTimer.js +172 -172
  99. package/src/directives/nvdaFix.js +53 -53
  100. package/src/externalComps/ModuleView.vue +22 -22
  101. package/src/externalComps/SummaryView.vue +91 -91
  102. package/src/main.js +493 -476
  103. package/src/module/stores/appStore.js +960 -947
  104. package/src/module/xapi/ADL.js +520 -520
  105. package/src/module/xapi/Crypto/Hasher.js +241 -241
  106. package/src/module/xapi/Crypto/WordArray.js +278 -278
  107. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  108. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  109. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  110. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  111. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  112. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  113. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  114. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  115. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  116. package/src/module/xapi/Crypto/index.js +53 -53
  117. package/src/module/xapi/Statement/activity.js +47 -47
  118. package/src/module/xapi/Statement/agent.js +55 -55
  119. package/src/module/xapi/Statement/group.js +26 -26
  120. package/src/module/xapi/Statement/index.js +259 -259
  121. package/src/module/xapi/Statement/statement.js +253 -253
  122. package/src/module/xapi/Statement/statementRef.js +23 -23
  123. package/src/module/xapi/Statement/substatement.js +22 -22
  124. package/src/module/xapi/Statement/verb.js +36 -36
  125. package/src/module/xapi/activitytypes.js +17 -17
  126. package/src/module/xapi/launch.js +157 -157
  127. package/src/module/xapi/utils.js +167 -167
  128. package/src/module/xapi/verbs.js +294 -294
  129. package/src/module/xapi/wrapper.js +1895 -1895
  130. package/src/module/xapi/xapiStatement.js +444 -444
  131. package/src/plugins/analytics.js +34 -34
  132. package/src/plugins/bus.js +12 -8
  133. package/src/plugins/gsap.js +17 -15
  134. package/src/plugins/helper.js +355 -358
  135. package/src/plugins/i18n.js +27 -26
  136. package/src/plugins/idb.js +227 -227
  137. package/src/plugins/save.js +37 -37
  138. package/src/plugins/scorm.js +287 -287
  139. package/src/plugins/xapi.js +11 -11
  140. package/src/public/index.html +33 -33
  141. package/src/router/index.js +57 -57
  142. package/src/router/routes.js +312 -312
  143. package/src/shared/generalfuncs.js +344 -344
  144. package/src/shared/validators.js +1018 -1018
  145. package/tests/component/AppBaseButton.spec.js +53 -0
  146. package/tests/component/pinia.spec.js +24 -0
  147. package/{src/components/tests__ → tests/unit}/AppBaseButton.spec.js +53 -53
  148. package/tests/unit/AppCompInputCheckBoxNx.spec.js +59 -0
  149. package/tests/unit/AppCompInputDropdownNx.spec.js +51 -0
  150. package/tests/unit/AppCompInputRadioNx.spec.js +59 -0
  151. package/tests/unit/AppCompInputTextNx.spec.js +44 -0
  152. package/tests/unit/AppCompInputTextTableNx.spec.js +77 -0
  153. package/tests/unit/AppCompInputTextToFillDropdownNx.spec.js +60 -0
  154. package/tests/unit/AppCompInputTextToFillNx.spec.js +45 -0
  155. package/tests/unit/AppCompQuizNext.spec.js +114 -0
  156. package/tests/unit/AppCompVideoPlayer.spec.js +177 -0
  157. package/{src/components/tests__ → tests/unit}/useTimer.spec.js +91 -91
  158. package/vitest.config.js +28 -19
  159. package/vitest.setup.js +28 -0
  160. package/src/components/AppBaseButton.test.js +0 -21
package/src/main.js CHANGED
@@ -1,476 +1,493 @@
1
- import AppBase from './components/AppBase.vue'
2
- import AppBaseButton from './components/AppBaseButton.vue'
3
- import AppBaseModule from './components/AppBaseModule.vue'
4
- import AppBasePage from './components/AppBasePage.vue'
5
- import AppBaseFlipCard from './components/AppBaseFlipCard.vue'
6
- import AppBasePopover from './components/AppBasePopover.vue'
7
- import AppCompJauge from './components/AppCompJauge.vue'
8
- import AppBaseErrorDisplay from './components/AppBaseErrorDisplay.vue'
9
- //import AppCompBif from './components/AppCompBif.vue'
10
- import AppCompAudio from './components/AppCompAudio.vue'
11
- import AppCompBranchButtons from './components/AppCompBranchButtons.vue'
12
- import AppCompCarousel from './components/AppCompCarousel.vue'
13
- import AppCompViewDisplay from './components/AppCompViewDisplay.vue'
14
- import AppCompTableOfContent from './components/AppCompTableOfContent.vue'
15
- import AppCompMenu from './components/AppCompMenu.vue'
16
- import AppCompMenuItem from './components/AppCompMenuItem.vue'
17
- import AppCompPopUpNext from './components/AppCompPopUpNext.vue'
18
- import AppCompNavigation from './components/AppCompNavigation.vue'
19
- import AppCompNoteCall from './components/AppCompNoteCall.vue'
20
- import AppCompNoteCredit from './components/AppCompNoteCredit.vue'
21
- import AppCompVideoPlayer from './components/AppCompVideoPlayer.vue'
22
- import AppCompPlayBarNext from './components/AppCompPlayBarNext.vue'
23
- import AppCompQuizNext from './components/AppCompQuizNext.vue'
24
- import AppCompQuizRecall from './components/AppCompQuizRecall.vue'
25
- import AppBaseSkeleton from './components/AppBaseSkeleton.vue'
26
-
27
- import GsapPlugin from './plugins/gsap'
28
- import eventBus from './plugins/bus'
29
- import helper from './plugins/helper'
30
- import analytics from './plugins/analytics'
31
- import mergeLocales from './plugins/i18n'
32
- import { scormPlugin } from './plugins/scorm'
33
- import { xapiPlugin } from './plugins/xapi'
34
- import { $idb } from './plugins/idb'
35
- import { validatefileContent } from './shared/validators.js'
36
- import { createPinia } from 'pinia'
37
- import { useAppStore } from './module/stores/appStore.js'
38
- import axios from 'axios'
39
- import AppCompSVGNext from './components/AppCompSVGNext.vue'
40
- import VueSafeTeleport from 'vue-safe-teleport'
41
- import { FocusTrap } from 'focus-trap-vue'
42
- import nvdaFix from './directives/nvdaFix.js'
43
-
44
- const pinia = createPinia()
45
-
46
- export default {
47
- install(app, options) {
48
- app.use(pinia)
49
- app.use(scormPlugin, '$scorm')
50
- app.use(GsapPlugin, '$gsap')
51
- app.use(xapiPlugin, '$xapi')
52
- app.use(VueSafeTeleport)
53
- app.component('FocusTrap', FocusTrap)
54
-
55
- app.config.globalProperties.$bus = eventBus
56
- app.use($idb)
57
-
58
- app.component('AppBase', AppBase)
59
- app.component('AppBaseButton', AppBaseButton)
60
- app.component('AppBaseModule', AppBaseModule)
61
- app.component('AppBasePage', AppBasePage)
62
- app.component('AppBaseFlipCard', AppBaseFlipCard)
63
- app.component('AppBaseErrorDisplay', AppBaseErrorDisplay)
64
- app.component('AppCompMenu', AppCompMenu)
65
- //app.component('app-comp-bif', AppCompBif)
66
- app.component('AppCompAudioPlayer', AppCompAudio)
67
- app.component('AppCompBranchButtons', AppCompBranchButtons)
68
- app.component('AppCompCarousel', AppCompCarousel)
69
- app.component('AppCompMenuItem', AppCompMenuItem)
70
- app.component('AppCompJauge', AppCompJauge)
71
- app.component('AppCompTableOfContent', AppCompTableOfContent)
72
- app.component('AppCompPopUpNext', AppCompPopUpNext)
73
- app.component('AppCompNavigation', AppCompNavigation)
74
- app.component('AppCompNoteCredit', AppCompNoteCredit)
75
- app.component('AppCompNoteCall', AppCompNoteCall)
76
- app.component('AppBasePopover', AppBasePopover)
77
- app.component('AppCompVideoPlayer', AppCompVideoPlayer)
78
- app.component('AppCompPlayBarNext', AppCompPlayBarNext)
79
- app.component('AppCompQuizNext', AppCompQuizNext)
80
- app.component('AppIconsNext', AppCompSVGNext)
81
- app.component('AppCompQuizRecall', AppCompQuizRecall)
82
- app.component('AppBaseSkeleton', AppBaseSkeleton)
83
- app.component(AppCompViewDisplay)
84
-
85
- if (!options)
86
- throw new Error(
87
- '💥You did not provide i18n and vuex store. Please install and provide them.'
88
- )
89
-
90
- const requiredOptions = ['i18n', 'menuSettings']
91
- const errorMissing = {
92
- i18n: { a: 'Internationalisation', b: 'vue-I18n' },
93
- menuSettings: { a: 'Menu Settings file', b: 'menuSettings' }
94
- }
95
-
96
- /*Watch for appBase data received in the store
97
- *To set connection Info
98
- *To set the connection with indexDB
99
- *To fetch data from servers /storage
100
- *To set document lang
101
- */
102
- const appStore = useAppStore()
103
-
104
- const unsubscribeToSetConfig = appStore.$onAction(
105
- ({
106
- name, // name of the action
107
- store, // store instance, same as `someStore`
108
- args, // array of parameters passed to the action
109
- after // hook after the action returns or resolves
110
- }) => {
111
- after(async (result) => {
112
- if (name !== 'initializeApp') return
113
- let { appConfigs } = appStore.$state
114
- let { remote = false, specification } = appConfigs
115
- const c = await configConnection(appConfigs)
116
- store.lrsConfig = c
117
- const { $scorm, $xapi, $idb } = app.config.globalProperties
118
- if (specification == 'xapi') {
119
- const {
120
- auth,
121
- endpoint,
122
- registration = $xapi.ruuid()
123
- } = store.lrsConfig
124
-
125
- const config = {
126
- auth,
127
- endpoint,
128
- registration,
129
- activity_platform: `SIPI_organizationId`
130
- }
131
-
132
- $xapi._configLRS(config) // configure and launch LRS
133
- }
134
-
135
- //Configure connection with the Data from appBase
136
- //===================================================
137
-
138
- const activity_id = c ? c.activity_id : null
139
-
140
- //Redefine actor with value from connection informtion
141
- if (c && c.actor) appConfigs.actor = c.actor
142
-
143
- appConfigs.remote = c && c.remote ? c.remote : false
144
-
145
- //========================FETCHING EXISTING DATA AND SETTING STORE STATE===========================
146
- /*Fetch user existing data from the server
147
- and set/update initial state of the store
148
- */
149
- const res = await fetchDatasFromServer({
150
- $scorm,
151
- $xapi,
152
- $idb,
153
- activity_id,
154
- ...appConfigs
155
- })
156
- //Update the store state
157
-
158
- if (res) {
159
- const {
160
- routeHistory,
161
- userSettings = {},
162
- progress,
163
- playbarValues = null,
164
- lessonPosition = null,
165
- completedState = null
166
- } = res
167
-
168
- appStore.setUserMetaData(progress)
169
- appStore.setRouteHistory(routeHistory)
170
- appStore.setApplicationSettings(userSettings)
171
-
172
- if (playbarValues)
173
- appStore.$state.mediaPlaybarValues = playbarValues
174
-
175
- if (lessonPosition) appStore.setLessonPosition(lessonPosition)
176
- if (completedState) appStore.setCompletionState(completedState)
177
- }
178
- //Open Connection to IDB and Save the store state to localDB
179
- let dbStoreActive = window.location.hostname == 'localhost' && !remote
180
-
181
- if (dbStoreActive) {
182
- console.log(`🏬 IDB Store active!`)
183
- await $idb.openDB()
184
- await $idb.saveState(appStore, appConfigs.idb_id)
185
- }
186
- //Update Loaging state
187
- appStore.userDataLoaded = true
188
-
189
- //Set document lang attribute (wcag request)
190
- document.documentElement.setAttribute('lang', appConfigs.lang)
191
- })
192
- }
193
- )
194
- app.directive('nvda-fix', nvdaFix)
195
- app.provide('unsubscribeToSetConfig', unsubscribeToSetConfig)
196
-
197
- /* Check that required options are provided */
198
- requiredOptions.forEach((key) => {
199
- // required key is missing in $data that was passed for the page
200
- if (!Object.keys(options).includes(key)) {
201
- throw new Error(
202
- `👉${errorMissing[key].a}👈 is not provided. Please install and provide 👉${errorMissing[key].b}👈.`
203
- )
204
- }
205
-
206
- if (key === 'menuSettings') {
207
- const mapAct = appStore.$state.thisModule.activities
208
- if (!mapAct) return
209
- const keys4Lesson = ['lessonTitle', 'lessonNumber', ...mapAct.keys()]
210
- const keys4ActivityOpt = ['title', 'subTitle']
211
- const keys4ActivityMandatory = ['time', 'anchors']
212
- const keys4Anchors = ['anchorName', 'pageRef', 'page']
213
-
214
- const errChecked = validatefileContent(
215
- 'menuSettings',
216
- options.menuSettings,
217
- {
218
- keys4Lesson,
219
- keys4ActivityMandatory,
220
- keys4ActivityOpt,
221
- keys4Anchors
222
- }
223
- )
224
- if (errChecked) {
225
- appStore.errMenuSetting = errChecked
226
- } else {
227
- appStore.errMenuSetting = false
228
- }
229
- }
230
- })
231
-
232
- window.App_DEBUGMODE = false
233
-
234
- /* Local Method to configurate the connection info */
235
- const configConnection = async (data) => {
236
- let connectionInfo
237
-
238
- switch (true) {
239
- case window.location.origin.includes('cegepadistance.ca'): {
240
- if (data.specification == 'scorm') {
241
- connectionInfo = {
242
- remote: true,
243
- endpoint: 'scorm'
244
- }
245
- } else {
246
- try {
247
- // Accessing User && Activity Info from Url parametters
248
- const queryString = window.location.search
249
- const urlParams = new URLSearchParams(queryString)
250
- let c // Initialize connection object
251
- //Get User && Activity info
252
- if (urlParams.get('actor') && urlParams.get('activity_id')) {
253
- c = {
254
- actor: JSON.parse(urlParams.get('actor')),
255
- activity_id: urlParams.get('activity_id'),
256
- endpoint: urlParams.get('endpoint'),
257
- registration: urlParams.get('registration'),
258
- remote: true
259
- }
260
- }
261
- // Get LRS autorisation info from serveur
262
- const request = await axios.get('../../configs-xapi/xapi.json')
263
-
264
- const { basicauth } = request.data
265
-
266
- c.auth = basicauth
267
- connectionInfo = c
268
- } catch (err) {
269
- console.error('DOWNLOAD ERROR: 💥', err)
270
- }
271
- }
272
-
273
- break
274
- }
275
- case window.location.origin.includes('localhost'): {
276
- if (!data || data.specification == 'scorm') return
277
- let {
278
- id,
279
- crs_id = null,
280
- actor = {
281
- mbox: 'mailto:totoescargot@email.com',
282
- name: 'Toto Escargot',
283
- objectType: 'Agent'
284
- },
285
- endpointConfig = null,
286
- remote = null
287
- } = data
288
-
289
- const origin = window.location.origin
290
- const activity_id = crs_id
291
- ? `${origin}/${crs_id}/${id}`
292
- : `${origin}/${id}`
293
-
294
- if (remote == null) remote = false
295
-
296
- if (endpointConfig == null)
297
- endpointConfig = {
298
- endpoint: 'https://learninglocker.cegepadistance.ca/data/xAPI/',
299
- auth: 'Basic MmU4ZGQ3NTY1NDRiZWUxNmUxZWYzZDZiOThjNWVjY2YxNjVhZjIyNzpkY2Q5Zjk4OWZlYjU3MmZlZjBhNDkxZDIxNmYyNmQyY2M1YTQ4Nzlh'
300
- }
301
-
302
- connectionInfo = {
303
- actor,
304
- activity_id,
305
- remote,
306
- ...endpointConfig
307
- }
308
- break
309
- }
310
- }
311
-
312
- return connectionInfo
313
- }
314
- /* Local Method for data fetching */
315
- const fetchDatasFromServer = async (config) => {
316
- const {
317
- $scorm,
318
- $xapi,
319
- $idb,
320
- actor,
321
- activity_id,
322
- crs_id,
323
- idb_id,
324
- specification,
325
- remote
326
- } = config
327
-
328
- let server = remote ? specification : 'local'
329
- //falback to idb when scorm and origin is localhost
330
- if (specification == 'scorm' && window.location.hostname == 'localhost')
331
- server = 'local'
332
-
333
- let data = null
334
-
335
- switch (server) {
336
- case 'scorm': {
337
- if ($scorm.initialized) {
338
- const lessonStatus = $scorm.GetValue('cmi.core.lesson_status', true)
339
- if (lessonStatus === 'unknown') {
340
- $scorm.setValue('cmi.core.lesson_status', 'incomplete')
341
- $scorm.Commit()
342
- }
343
-
344
- const suspendData = $scorm.GetValue('cmi.suspend_data')
345
- if (suspendData !== '') {
346
- try {
347
- const parsed = JSON.parse(suspendData.replace(/\\/g, ''))
348
- const {
349
- userData: progress,
350
- routeHistory,
351
- userSettings
352
- } = parsed
353
-
354
- const lessonPosition = this.$scorm.GetValue(
355
- 'cmi.core.lesson_location',
356
- false
357
- )
358
-
359
- data = {
360
- progress,
361
- routeHistory,
362
- lessonPosition,
363
- userSettings
364
- }
365
- } catch (err) {
366
- console.error('❌ Failed to parse suspend_data:', err)
367
- }
368
- }
369
- }
370
- break
371
- }
372
-
373
- case 'xapi': {
374
- try {
375
- const actorMbox = actor.mbox.replace('mailto:', '')
376
- const activityId = activity_id
377
- const _url = new URL(activityId)
378
- const parentID = `${_url.origin}/${crs_id}` // redefining activity id for statement
379
-
380
- const lessonProgressParam = { email: actorMbox, activityId }
381
- const lessonStateParam = {
382
- email: actorMbox,
383
- activityId,
384
- verb: 'completed'
385
- }
386
- const lessonPosionParam = {
387
- email: actorMbox,
388
- activityId
389
- }
390
- const playbarParam = {
391
- email: actorMbox,
392
- activityId,
393
- verb: 'played'
394
- }
395
- const preferencesParam = {
396
- email: actorMbox,
397
- activityId: parentID,
398
- verb: 'preferred'
399
- }
400
-
401
- const fetchParams = [
402
- lessonProgressParam,
403
- lessonStateParam,
404
- lessonPosionParam,
405
- playbarParam,
406
- preferencesParam
407
- ]
408
-
409
- const {
410
- userData,
411
- savedPoint,
412
- preferredSettings,
413
- playbarValues,
414
- lessonStatus
415
- } = await $xapi._getBulkData(fetchParams)
416
-
417
- const { routeHistory = [], ...progress } = userData
418
-
419
- const completedState = lessonStatus || {}
420
- const lessonPosition = savedPoint || ''
421
- const userSettings = preferredSettings || {}
422
-
423
- data = {
424
- progress,
425
- routeHistory,
426
- lessonPosition,
427
- completedState,
428
- playbarValues,
429
- userSettings
430
- }
431
- } catch (err) {
432
- throw new Error(err)
433
- }
434
- break
435
- }
436
- default: {
437
- try {
438
- await $idb.openDB() //open the new instance in indexDB
439
- const res = await $idb.getFromDB(idb_id) // get existing Data from db
440
-
441
- if (res && res.$record) data = res.$record
442
- } catch (err) {
443
- console.error('❌ Failed get Data from IndexDB:', err)
444
- }
445
- }
446
- }
447
-
448
- return data
449
- }
450
- //=================================END SET LRS INFO ====================================
451
- window.setDebugMode = (mode) => {
452
- appStore.appDebugMode = mode
453
- const msg = mode ? 'DEBUG MODE ON' : 'DEBUG MODE OFF'
454
- console.log(`🚩 ${msg}`)
455
- }
456
-
457
- appStore.menuSetting = options.menuSettings
458
-
459
- //=================================SETTING PREFERENCES ====================================
460
-
461
- //define the settings option for user preference according to the mode of App
462
- let settingsOptions = {
463
- autoplay: null
464
- // onboarding: null,// Uncomment when ready to add onboarding in settings
465
- // subtitles: null,// Uncomment when ready to add subtitle in settings
466
- }
467
-
468
- appStore.applicationSettings = settingsOptions
469
- //=================================END SETTING PREFERENCES ====================================//
470
-
471
- //mergeLocales
472
- mergeLocales(options.i18n.global)
473
- app.use(helper, '$helper')
474
- app.use(analytics, '$analytics')
475
- }
476
- }
1
+ import AppBase from './components/AppBase.vue'
2
+ import AppBaseButton from './components/AppBaseButton.vue'
3
+ import AppBaseModule from './components/AppBaseModule.vue'
4
+ import AppBasePage from './components/AppBasePage.vue'
5
+ import AppBaseFlipCard from './components/AppBaseFlipCard.vue'
6
+ import AppBasePopover from './components/AppBasePopover.vue'
7
+ import AppCompJauge from './components/AppCompJauge.vue'
8
+ import AppBaseErrorDisplay from './components/AppBaseErrorDisplay.vue'
9
+ //import AppCompBif from './components/AppCompBif.vue'
10
+ import AppCompAudio from './components/AppCompAudio.vue'
11
+ import AppCompBranchButtons from './components/AppCompBranchButtons.vue'
12
+ import AppCompCarousel from './components/AppCompCarousel.vue'
13
+ import AppCompViewDisplay from './components/AppCompViewDisplay.vue'
14
+ import AppCompTableOfContent from './components/AppCompTableOfContent.vue'
15
+ import AppCompMenu from './components/AppCompMenu.vue'
16
+ import AppCompMenuItem from './components/AppCompMenuItem.vue'
17
+ import AppCompPopUpNext from './components/AppCompPopUpNext.vue'
18
+ import AppCompNavigation from './components/AppCompNavigation.vue'
19
+ import AppCompNoteCall from './components/AppCompNoteCall.vue'
20
+ import AppCompNoteCredit from './components/AppCompNoteCredit.vue'
21
+ import AppCompVideoPlayer from './components/AppCompVideoPlayer.vue'
22
+ import AppCompPlayBarNext from './components/AppCompPlayBarNext.vue'
23
+ import AppCompQuizNext from './components/AppCompQuizNext.vue'
24
+ import AppCompQuizRecall from './components/AppCompQuizRecall.vue'
25
+ import AppBaseSkeleton from './components/AppBaseSkeleton.vue'
26
+
27
+ import GsapPlugin from './plugins/gsap'
28
+ import eventBus from './plugins/bus'
29
+ import helper from './plugins/helper'
30
+ import analytics from './plugins/analytics'
31
+ import mergeLocales from './plugins/i18n'
32
+ import { scormPlugin } from './plugins/scorm'
33
+ import { xapiPlugin } from './plugins/xapi'
34
+ import { $idb } from './plugins/idb'
35
+ import { validatefileContent } from './shared/validators.js'
36
+ import { createPinia } from 'pinia'
37
+ import { useAppStore } from './module/stores/appStore.js'
38
+ import axios from 'axios'
39
+ import AppCompSVGNext from './components/AppCompSVGNext.vue'
40
+ import VueSafeTeleport from 'vue-safe-teleport'
41
+ import { FocusTrap } from 'focus-trap-vue'
42
+ import nvdaFix from './directives/nvdaFix.js'
43
+ import pckg from '../package.json'
44
+ const pinia = createPinia()
45
+
46
+ export default {
47
+ install(app, options) {
48
+ app.use(pinia)
49
+ app.use(scormPlugin)
50
+ app.use(GsapPlugin)
51
+ app.use(xapiPlugin)
52
+ app.use(VueSafeTeleport)
53
+ app.component('FocusTrap', FocusTrap)
54
+
55
+ app.use(eventBus)
56
+ app.use($idb)
57
+
58
+ app.component('AppBase', AppBase)
59
+ app.component('AppBaseButton', AppBaseButton)
60
+ app.component('AppBaseModule', AppBaseModule)
61
+ app.component('AppBasePage', AppBasePage)
62
+ app.component('AppBaseFlipCard', AppBaseFlipCard)
63
+ app.component('AppBaseErrorDisplay', AppBaseErrorDisplay)
64
+ app.component('AppCompMenu', AppCompMenu)
65
+ //app.component('app-comp-bif', AppCompBif)
66
+ app.component('AppCompAudioPlayer', AppCompAudio)
67
+ app.component('AppCompBranchButtons', AppCompBranchButtons)
68
+ app.component('AppCompCarousel', AppCompCarousel)
69
+ app.component('AppCompMenuItem', AppCompMenuItem)
70
+ app.component('AppCompJauge', AppCompJauge)
71
+ app.component('AppCompTableOfContent', AppCompTableOfContent)
72
+ app.component('AppCompPopUpNext', AppCompPopUpNext)
73
+ app.component('AppCompNavigation', AppCompNavigation)
74
+ app.component('AppCompNoteCredit', AppCompNoteCredit)
75
+ app.component('AppCompNoteCall', AppCompNoteCall)
76
+ app.component('AppBasePopover', AppBasePopover)
77
+ app.component('AppCompVideoPlayer', AppCompVideoPlayer)
78
+ app.component('AppCompPlayBarNext', AppCompPlayBarNext)
79
+ app.component('AppCompQuizNext', AppCompQuizNext)
80
+ app.component('AppIconsNext', AppCompSVGNext)
81
+ app.component('AppCompQuizRecall', AppCompQuizRecall)
82
+ app.component('AppBaseSkeleton', AppBaseSkeleton)
83
+ app.component(AppCompViewDisplay)
84
+
85
+ if (!options)
86
+ throw new Error(
87
+ '💥You did not provide i18n and vuex store. Please install and provide them.'
88
+ )
89
+
90
+ const requiredOptions = ['i18n', 'menuSettings']
91
+ const errorMissing = {
92
+ i18n: { a: 'Internationalisation', b: 'vue-I18n' },
93
+ menuSettings: { a: 'Menu Settings file', b: 'menuSettings' }
94
+ }
95
+
96
+ /*Watch for appBase data received in the store
97
+ *To set connection Info
98
+ *To set the connection with indexDB
99
+ *To fetch data from servers /storage
100
+ *To set document lang
101
+ */
102
+ const appStore = useAppStore()
103
+ const { name, version } = pckg
104
+
105
+ appStore.setAppPackageInfo({
106
+ packageVersion: `v${version}`,
107
+ packageName: name
108
+ })
109
+
110
+ const unsubscribeToSetConfig = appStore.$onAction(
111
+ ({
112
+ name, // name of the action
113
+ store, // store instance, same as `someStore`
114
+ args, // array of parameters passed to the action
115
+ after // hook after the action returns or resolves
116
+ }) => {
117
+ after(async (result) => {
118
+ if (name !== 'initializeApp') return
119
+ let { appConfigs } = appStore.$state
120
+ let { remote = false, specification } = appConfigs
121
+ const c = await configConnection(appConfigs)
122
+ store.lrsConfig = c
123
+ const { $scorm, $xapi, $idb } = app.config.globalProperties
124
+ if (specification == 'xapi') {
125
+ const {
126
+ auth,
127
+ endpoint,
128
+ registration = $xapi.ruuid()
129
+ } = store.lrsConfig
130
+
131
+ const config = {
132
+ auth,
133
+ endpoint,
134
+ registration,
135
+ activity_platform: `SIPI_organizationId`
136
+ }
137
+
138
+ $xapi._configLRS(config) // configure and launch LRS
139
+ }
140
+
141
+ //Configure connection with the Data from appBase
142
+ //===================================================
143
+
144
+ const activity_id = c ? c.activity_id : null
145
+
146
+ //Redefine actor with value from connection informtion
147
+ if (c && c.actor) appConfigs.actor = c.actor
148
+
149
+ //========================FETCHING EXISTING DATA AND SETTING STORE STATE===========================
150
+ /*Fetch user existing data from the server
151
+ and set/update initial state of the store
152
+ */
153
+ const res = await fetchDatasFromServer({
154
+ $scorm,
155
+ $xapi,
156
+ $idb,
157
+ activity_id,
158
+ ...appConfigs
159
+ })
160
+ //Update the store state
161
+
162
+ if (res) {
163
+ const {
164
+ routeHistory,
165
+ userSettings = {},
166
+ progress,
167
+ playbarValues = null,
168
+ lessonPosition = null,
169
+ completedState = null
170
+ } = res
171
+
172
+ appStore.setUserMetaData(progress)
173
+ appStore.setRouteHistory(routeHistory)
174
+ appStore.setApplicationSettings(userSettings)
175
+
176
+ if (playbarValues)
177
+ appStore.$state.mediaPlaybarValues = playbarValues
178
+
179
+ if (lessonPosition) appStore.setLessonPosition(lessonPosition)
180
+ if (completedState) appStore.setCompletionState(completedState)
181
+ }
182
+ //Open Connection to IDB and Save the store state to localDB
183
+ let dbStoreActive =
184
+ !window.location.hostname.includes('cegepadistance.ca') && !remote
185
+
186
+ if (dbStoreActive) {
187
+ console.log(`🏬 IDB Store active!`)
188
+ await $idb.openDB()
189
+ await $idb.saveState(appStore, appConfigs.idb_id)
190
+ }
191
+ //Update Loaging state
192
+ appStore.userDataLoaded = true
193
+
194
+ //Set document lang attribute (wcag request)
195
+ document.documentElement.setAttribute('lang', appConfigs.lang)
196
+ })
197
+ }
198
+ )
199
+ app.directive('nvda-fix', nvdaFix)
200
+ app.provide('unsubscribeToSetConfig', unsubscribeToSetConfig)
201
+
202
+ /* Check that required options are provided */
203
+ requiredOptions.forEach((key) => {
204
+ // required key is missing in $data that was passed for the page
205
+ if (!Object.keys(options).includes(key)) {
206
+ throw new Error(
207
+ `👉${errorMissing[key].a}👈 is not provided. Please install and provide 👉${errorMissing[key].b}👈.`
208
+ )
209
+ }
210
+
211
+ if (key === 'menuSettings') {
212
+ const mapAct = appStore.$state.thisModule.activities
213
+ if (!mapAct) return
214
+ const keys4Lesson = ['lessonTitle', 'lessonNumber', ...mapAct.keys()]
215
+ const keys4ActivityOpt = ['title', 'subTitle']
216
+ const keys4ActivityMandatory = ['time', 'anchors']
217
+ const keys4Anchors = ['anchorName', 'pageRef', 'page']
218
+
219
+ const errChecked = validatefileContent(
220
+ 'menuSettings',
221
+ options.menuSettings,
222
+ {
223
+ keys4Lesson,
224
+ keys4ActivityMandatory,
225
+ keys4ActivityOpt,
226
+ keys4Anchors
227
+ }
228
+ )
229
+ if (errChecked) {
230
+ appStore.errMenuSetting = errChecked
231
+ } else {
232
+ appStore.errMenuSetting = false
233
+ }
234
+ }
235
+ })
236
+
237
+ window.App_DEBUGMODE = false
238
+
239
+ /* Local Method to configurate the connection info */
240
+ const configConnection = async (data) => {
241
+ let connectionInfo
242
+
243
+ switch (true) {
244
+ case window.location.origin.includes('cegepadistance.ca'): {
245
+ if (data.specification == 'scorm') {
246
+ connectionInfo = {
247
+ remote: true,
248
+ endpoint: 'scorm'
249
+ }
250
+ } else {
251
+ try {
252
+ // Accessing User && Activity Info from Url parametters
253
+ const queryString = window.location.search
254
+ const urlParams = new URLSearchParams(queryString)
255
+ let c // Initialize connection object
256
+ //Get User && Activity info
257
+ if (urlParams.get('actor') && urlParams.get('activity_id')) {
258
+ c = {
259
+ actor: JSON.parse(urlParams.get('actor')),
260
+ activity_id: urlParams.get('activity_id'),
261
+ endpoint: urlParams.get('endpoint'),
262
+ registration: urlParams.get('registration'),
263
+ remote: true
264
+ }
265
+ }
266
+ // Get LRS autorisation info from serveur
267
+ const request = await axios.get('../../configs-xapi/xapi.json')
268
+
269
+ const { basicauth } = request.data
270
+
271
+ c.auth = basicauth
272
+ connectionInfo = c
273
+ } catch (err) {
274
+ console.error('DOWNLOAD ERROR: 💥', err)
275
+ }
276
+ }
277
+
278
+ break
279
+ }
280
+ case window.location.origin.includes('localhost'): {
281
+ if (!data || data.specification == 'scorm') return
282
+ let {
283
+ id,
284
+ crs_id = null,
285
+ actor = {
286
+ mbox: 'mailto:totoescargot@email.com',
287
+ name: 'Toto Escargot',
288
+ objectType: 'Agent'
289
+ },
290
+ endpointConfig = null,
291
+ remote = null
292
+ } = data
293
+
294
+ const origin = window.location.origin
295
+ const activity_id = crs_id
296
+ ? `${origin}/${crs_id}/${id}`
297
+ : `${origin}/${id}`
298
+
299
+ if (remote == null) remote = false
300
+
301
+ if (endpointConfig == null)
302
+ endpointConfig = {
303
+ endpoint: 'https://learninglocker.cegepadistance.ca/data/xAPI/',
304
+ auth: 'Basic MmU4ZGQ3NTY1NDRiZWUxNmUxZWYzZDZiOThjNWVjY2YxNjVhZjIyNzpkY2Q5Zjk4OWZlYjU3MmZlZjBhNDkxZDIxNmYyNmQyY2M1YTQ4Nzlh'
305
+ }
306
+
307
+ connectionInfo = {
308
+ actor,
309
+ activity_id,
310
+ remote,
311
+ ...endpointConfig
312
+ }
313
+ break
314
+ }
315
+ }
316
+
317
+ return connectionInfo
318
+ }
319
+ /* Local Method for data fetching */
320
+ const fetchDatasFromServer = async (config) => {
321
+ const {
322
+ $scorm,
323
+ $xapi,
324
+ $idb,
325
+ actor,
326
+ activity_id,
327
+ crs_id,
328
+ idb_id,
329
+ specification,
330
+ remote
331
+ } = config
332
+
333
+ let server = remote ? specification : 'local'
334
+ //falback to idb when scorm and origin is localhost
335
+ //falback to idb when scorm and origin is localhost
336
+ if (
337
+ (specification == 'scorm' && window.location.hostname == 'localhost') ||
338
+ !window.location.hostname.includes('cegepadistance.ca')
339
+ )
340
+ server = 'local'
341
+
342
+ let data = null
343
+
344
+ switch (server) {
345
+ case 'scorm': {
346
+ if ($scorm.initialized) {
347
+ const lessonStatus = $scorm.GetValue('cmi.core.lesson_status', true)
348
+ if (lessonStatus === 'unknown') {
349
+ $scorm.setValue('cmi.core.lesson_status', 'incomplete')
350
+ $scorm.Commit()
351
+ }
352
+
353
+ const suspendData = $scorm.GetValue('cmi.suspend_data')
354
+ if (suspendData !== '') {
355
+ try {
356
+ const parsed = JSON.parse(suspendData.replace(/\\/g, ''))
357
+ const {
358
+ userData: progress,
359
+ routeHistory,
360
+ userSettings
361
+ } = parsed
362
+
363
+ const lessonPosition = this.$scorm.GetValue(
364
+ 'cmi.core.lesson_location',
365
+ false
366
+ )
367
+
368
+ data = {
369
+ progress,
370
+ routeHistory,
371
+ lessonPosition,
372
+ userSettings
373
+ }
374
+ } catch (err) {
375
+ console.error('❌ Failed to parse suspend_data:', err)
376
+ }
377
+ }
378
+ }
379
+ break
380
+ }
381
+
382
+ case 'xapi': {
383
+ try {
384
+ const actorMbox = actor.mbox.replace('mailto:', '')
385
+ const activityId = activity_id
386
+ const _url = new URL(activityId)
387
+ const parentID = `${_url.origin}/${crs_id}` // redefining activity id for statement
388
+
389
+ const lessonProgressParam = { email: actorMbox, activityId }
390
+ const lessonStateParam = {
391
+ email: actorMbox,
392
+ activityId,
393
+ verb: 'completed'
394
+ }
395
+ const lessonPosionParam = {
396
+ email: actorMbox,
397
+ activityId
398
+ }
399
+ const playbarParam = {
400
+ email: actorMbox,
401
+ activityId,
402
+ verb: 'played'
403
+ }
404
+ const preferencesParam = {
405
+ email: actorMbox,
406
+ activityId: parentID,
407
+ verb: 'preferred'
408
+ }
409
+
410
+ const fetchParams = [
411
+ lessonProgressParam,
412
+ lessonStateParam,
413
+ lessonPosionParam,
414
+ playbarParam,
415
+ preferencesParam
416
+ ]
417
+
418
+ const {
419
+ userData,
420
+ savedPoint,
421
+ preferredSettings,
422
+ playbarValues,
423
+ lessonStatus
424
+ } = await $xapi._getBulkData(fetchParams)
425
+
426
+ const { routeHistory = [], ...progress } = userData
427
+
428
+ const completedState = lessonStatus || {}
429
+ const lessonPosition = savedPoint || ''
430
+ const userSettings = preferredSettings || {}
431
+
432
+ data = {
433
+ progress,
434
+ routeHistory,
435
+ lessonPosition,
436
+ completedState,
437
+ playbarValues,
438
+ userSettings
439
+ }
440
+ } catch (err) {
441
+ throw new Error(err)
442
+ }
443
+ break
444
+ }
445
+ default: {
446
+ try {
447
+ await $idb.openDB() //open the new instance in indexDB
448
+ const res = await $idb.getFromDB(idb_id) // get existing Data from db
449
+
450
+ if (res && res.$record) data = res.$record
451
+ } catch (err) {
452
+ console.error('❌ Failed get Data from IndexDB:', err)
453
+ }
454
+ }
455
+ }
456
+
457
+ return data
458
+ }
459
+ //=================================END SET LRS INFO ====================================
460
+ window.setDebugMode = (mode) => {
461
+ appStore.appDebugMode = mode
462
+ const msg = mode ? 'DEBUG MODE ON' : 'DEBUG MODE OFF'
463
+ console.log(`🚩 ${msg}`)
464
+ }
465
+
466
+ appStore.menuSetting = options.menuSettings
467
+
468
+ //=================================SETTING PREFERENCES ====================================
469
+
470
+ //define the settings option for user preference according to the mode of App
471
+ let settingsOptions = {
472
+ autoplay: null
473
+ // onboarding: null,// Uncomment when ready to add onboarding in settings
474
+ // subtitles: null,// Uncomment when ready to add subtitle in settings
475
+ }
476
+
477
+ appStore.applicationSettings = settingsOptions
478
+ //=================================END SETTING PREFERENCES ====================================//
479
+
480
+ //mergeLocales
481
+ mergeLocales(options.i18n.global)
482
+ app.use(helper)
483
+ app.use(analytics)
484
+
485
+ //=======================================
486
+ const { packageName, packageVersion } = appStore.getAppPackageInfo()
487
+ console.log(
488
+ `%c 📦${packageName} %c${packageVersion}`,
489
+ 'color: white; background: #35495e; padding: 4px 8px; border-radius: 4px 0 0 4px;',
490
+ 'color: white; background: #41b883; padding: 4px 8px; border-radius: 0 4px 4px 0;'
491
+ )
492
+ }
493
+ }