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
@@ -1,520 +1,520 @@
1
- import verbs from './verbs'
2
- import activities from './activitytypes'
3
- import xAPILaunch from './launch'
4
- import { defineUtils } from './utils'
5
- import { xapiwrapper } from './wrapper'
6
- import { xapiStatement } from './xapiStatement'
7
-
8
- //ref: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#partthree
9
-
10
- export default class ADL {
11
- constructor() {
12
- xapiwrapper(this)
13
- defineUtils(this)
14
- xAPILaunch(this)
15
- xapiStatement(this)
16
-
17
- this.verbs = verbs
18
- this.activityTypes = activities
19
-
20
- if (this.XAPIWrapper)
21
- this._prepareStatement = this.XAPIWrapper.prepareStatement
22
- }
23
-
24
- /*
25
- * Helper function to configurate the LRS
26
- * @params: config {Object}
27
- */
28
- _configLRS(config) {
29
- //this object (XAPIWrapper) will be attached to the ADL
30
- if (this.XAPIWrapper) {
31
- this.XAPIWrapper.changeConfig(config)
32
- }
33
- }
34
-
35
- /*
36
- * Helper function to Dispatch statement to the LRS
37
- * @params: Statement {Object}:xapi Statement
38
- */
39
- _sendStatement(statement, cb, withFetch = false) {
40
- //remove the statement Result if it not answer action.
41
- let res = null
42
- const withResult =
43
- statement.verb.id.includes('answered') ||
44
- statement.verb.id.includes('suspended') ||
45
- statement.verb.id.includes('completed')
46
-
47
- if (statement.result && !withResult) {
48
- delete statement.result
49
- }
50
-
51
- //Clear existing id of the statement if any to allow xapi to generate new ID for the Statement.
52
- if (statement.id) statement.id = ''
53
-
54
- // Dispatch the statement to the LRS
55
- if (!this.XAPIWrapper) return
56
-
57
- if (withFetch) this.XAPIWrapper.sendStatementWithFetch(statement)
58
- else {
59
- this.XAPIWrapper.sendStatement(statement, function (response) {
60
- if (cb) cb()
61
- res = response
62
- })
63
- return res
64
- }
65
- }
66
-
67
- /*
68
- * Helper function to Dispatch statement to the LRS
69
- * @params: Statement {Object}:xapi Statement
70
- */
71
- _sendStatements(statements, cb, withFetch = false) {
72
- //remove the statement Result if it not answer action.
73
- let res = null
74
- statements.forEach((statement) => {
75
- const withResult =
76
- statement.verb.id.includes('answered') ||
77
- statement.verb.id.includes('played') ||
78
- statement.verb.id.includes('suspended') ||
79
- statement.verb.id.includes('completed')
80
-
81
- if (statement.result && !withResult) {
82
- delete statement.result
83
- }
84
-
85
- //Clear existing id of the statement if any to allow xapi to generate new ID for the Statement.
86
- if (statement.id) statement.id = ''
87
- })
88
-
89
- // Dispatch the statement to the LRS
90
- if (!this.XAPIWrapper) return
91
-
92
- if (withFetch) this.XAPIWrapper.sendStatementsWithFetch(statements)
93
- else {
94
- this.XAPIWrapper.sendStatements(statements, function (response) {
95
- if (cb) cb()
96
- res = response
97
- })
98
- return res
99
- }
100
- }
101
-
102
- /*
103
- * Helper function to get the agent email
104
- * @Params: {String} email : email of the user
105
- * @Params: {String} activityId: IRI string of the course/activity id
106
- * @Params: {String} verb_id: IRI string of the course/activity id
107
- *
108
- */
109
- _getAgent(email, activityId, verb_id) {
110
- let params, // Array of search parameters
111
- completedStmts // Array of return search result
112
-
113
- if (this.XAPIWrapper) {
114
- params = this.XAPIWrapper.searchParams()
115
-
116
- if (email) params['agent'] = `{"mbox": "mailto:${email}"}`
117
- if (activityId) params['activity'] = activityId
118
- if (verb_id) params['verb'] = verb_id
119
-
120
- completedStmts = this.XAPIWrapper.getStatements(params)
121
- }
122
- //There is a records for the search return the actor
123
- if (
124
- completedStmts &&
125
- completedStmts.statements &&
126
- completedStmts.statements.length
127
- )
128
- return completedStmts.statements[0].actor
129
- else return false // no result
130
- }
131
-
132
- /*@Description: Helper to retrive user progress -
133
- search for a completion status of a activity if there is no completion status
134
- * search where the user stopped in the activity
135
- * return is position in the activity
136
- *
137
- * @Params {String} email: user email
138
- * @Params {String} activityId: the IRI string of the activity
139
- * @Params {String} verb: the verb to search by completed | terminated
140
- */
141
-
142
- async _getProgress(email, activityId, verb) {
143
- let params
144
- const validVerbs = [
145
- 'progressed',
146
- 'completed',
147
- 'suspended',
148
- 'terminated',
149
- 'exited'
150
- ]
151
- if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
152
-
153
- let vb = verb && validVerbs.includes(verb) ? verb : 'terminated'
154
-
155
- params['verb'] = this.verbs[vb].id // completed state
156
- params['activity'] = activityId // this current activity define by this id
157
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
158
- params['limit'] = 200 //limit of record to return by the LRS
159
- params['ascending'] = true //ordered by the oldest statement recorded
160
-
161
- let date = new Date()
162
- let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
163
- params['until'] = dd
164
-
165
- try {
166
- let userData //placeholder for user data
167
- let completedStmts = await this.XAPIWrapper.getStatements(params)
168
-
169
- if (
170
- completedStmts &&
171
- completedStmts.statements &&
172
- completedStmts.statements.length
173
- ) {
174
- let lastRecord = completedStmts.statements.toReversed()[0] // get last recorded statement
175
-
176
- // Verify that the extensions value of the statement as elements
177
- if (
178
- lastRecord.object.definition.extensions &&
179
- Object.keys(lastRecord.object.definition.extensions).length
180
- ) {
181
- const allExtensionsKeys = Object.keys(
182
- lastRecord.object.definition.extensions
183
- )
184
-
185
- //Search for the user-data entry in the extensions
186
- const userDataExtension = allExtensionsKeys.filter((extension) =>
187
- extension.includes('user-data')
188
- )
189
- userData =
190
- lastRecord.object.definition.extensions[userDataExtension[0]]
191
- }
192
- } else {
193
- userData = {}
194
- }
195
- return userData
196
- } catch (err) {
197
- console.log(err)
198
- }
199
- }
200
-
201
- /*@Description: Helper to The Lesson Status (result) -
202
-
203
- * @Params {String} email: user email
204
- * @Params {String} activityId: the IRI string of the activity
205
- */
206
- async _getLessonStatus(email, activityId, verb) {
207
- let params
208
- const validVerbs = ['completed', 'suspended']
209
- if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
210
- let vb = verb && validVerbs.includes(verb) ? verb : 'suspended'
211
- params['verb'] = this.verbs[vb].id // completed state
212
- params['activity'] = activityId // this current activity define by this id
213
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
214
- params['ascending'] = true //ordered by the oldest statement recorded
215
-
216
- let completedStmts = await this.XAPIWrapper.getStatements(params)
217
- let status //placeholder for user data
218
- if (
219
- completedStmts &&
220
- completedStmts.statements &&
221
- completedStmts.statements.length
222
- ) {
223
- let lastRecord = completedStmts.statements.toReversed()[0] // get last recorded statement
224
-
225
- // Verify that result exist
226
- if (lastRecord.result) status = lastRecord.result
227
- } else {
228
- status = {}
229
- }
230
- return status
231
- }
232
-
233
- /* @description: last lesson saved point from the LRS
234
- * search where the user stopped in the activity
235
- * return is position in the activity
236
- * @Params {String} email: user email
237
- * @Params {String} activityId: the IRI string of the activity
238
- */
239
-
240
- async _getLessonPosition(email, activityId) {
241
- let params
242
- let savedPoint = []
243
- if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
244
-
245
- params['verb'] = this.verbs.terminated.id // completed state
246
- params['activity'] = activityId // this current activity define by this id
247
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
248
- params['ascending'] = true //ordered by the oldest statement recorded
249
- params['limit'] = 200 //limit of record to return by the LRS
250
-
251
- let date = new Date()
252
- let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
253
-
254
- params['until'] = dd
255
-
256
- let progressedStmts = await this.XAPIWrapper.getStatements(params)
257
-
258
- if (
259
- progressedStmts &&
260
- progressedStmts.statements &&
261
- progressedStmts.statements.length
262
- ) {
263
- let lastRecord = progressedStmts.statements.toReversed()[0] // get last recorded statement
264
- // Verify that the extensions value of the statement has elements
265
-
266
- if (
267
- lastRecord.object.definition.extensions &&
268
- Object.keys(lastRecord.object.definition.extensions).length
269
- ) {
270
- const allExtensionsKeys = Object.keys(
271
- lastRecord.object.definition.extensions
272
- )
273
-
274
- //Search for the user-data entry in the extensions
275
- savedPoint = allExtensionsKeys.map((extension) => {
276
- if (extension.includes('ending-point'))
277
- return lastRecord.object.definition.extensions[extension]
278
- })
279
- }
280
- }
281
-
282
- return savedPoint && savedPoint.length > 0 ? savedPoint[0] : ''
283
- }
284
-
285
- /* @description: Helper to get the Preferred Settings of the user
286
- * return is position in the activity
287
- * @Params {String} email: user email
288
- * @Params {String} activityId: the IRI string of the activity
289
- */
290
-
291
- async _getPreferredSettings(email, activityId) {
292
- let params
293
- let preferredSettings = []
294
- if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
295
-
296
- params['verb'] = this.verbs.preferred.id // completed state
297
- params['activity'] = activityId // this current activity define by this id
298
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
299
- params['ascending'] = true //ordered by the oldest statement recorded
300
- params['limit'] = 200 //limit of record to return by the LRS
301
-
302
- let date = new Date()
303
- let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
304
-
305
- params['until'] = dd
306
-
307
- let preferredStmts = await this.XAPIWrapper.getStatements(params)
308
-
309
- if (
310
- preferredStmts &&
311
- preferredStmts.statements &&
312
- preferredStmts.statements.length
313
- ) {
314
- let lastRecord = preferredStmts.statements.toReversed()[0] // get last recorded statement
315
-
316
- // Verify that the extensions value of the statement has elements
317
- if (
318
- lastRecord.object.definition.extensions &&
319
- Object.keys(lastRecord.object.definition.extensions).length
320
- ) {
321
- const allExtensionsKeys = Object.keys(
322
- lastRecord.object.definition.extensions
323
- )
324
-
325
- //Search for the user-data entry in the extensions
326
- preferredSettings = allExtensionsKeys.map((extension) => {
327
- if (extension.includes('application-settings'))
328
- return lastRecord.object.definition.extensions[extension]
329
- })
330
- }
331
- }
332
- return preferredSettings[0] ? preferredSettings[0] : {}
333
- }
334
-
335
- /* @description: Helper to get the Playbar values of the user
336
- * @Params {String} email: user email
337
- * @Params {String} activityId: the IRI string of the activity
338
- */
339
-
340
- async _getPlaybarValues(email, activityId) {
341
- let params
342
- let playbarValues = []
343
- if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
344
-
345
- params['verb'] = this.verbs.played.id // completed state
346
- params['activity'] = activityId // this current activity define by this id
347
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
348
- params['ascending'] = true //ordered by the oldest statement recorded
349
- params['limit'] = 200 //limit of record to return by the LRS
350
-
351
- let date = new Date()
352
- let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
353
-
354
- params['until'] = dd
355
-
356
- let playedStmts = await this.XAPIWrapper.getStatements(params)
357
-
358
- if (
359
- playedStmts &&
360
- playedStmts.statements &&
361
- playedStmts.statements.length
362
- ) {
363
- let lastRecord = playedStmts.statements.toReversed()[0] // get last recorded statement
364
- // Verify that the extensions value of the statement has elements
365
- if (
366
- lastRecord.object.definition.extensions &&
367
- Object.keys(lastRecord.object.definition.extensions).length
368
- ) {
369
- const allExtensionsKeys = Object.keys(
370
- lastRecord.object.definition.extensions
371
- )
372
- //Search for the user-data entry in the extensions
373
- playbarValues = allExtensionsKeys.map((extension) => {
374
- if (extension.includes('playbar-values'))
375
- return lastRecord.object.definition.extensions[extension]
376
- })
377
- }
378
- }
379
- return playbarValues[0] ? playbarValues[0] : {}
380
- }
381
- /**
382
- * Method to get a multiple concurent requests using promiseAll to the server
383
- * and return all data at once.
384
- * the following request are made to the server:
385
- * get application-settings: saved value for the user preferred settings
386
- * get user-data: saved values for user progression
387
- * get playbar-value : saved value for the medias play-bar
388
- * get lesson-status : saved value for the lession status
389
- *
390
- * @param {Array} searchParams - Array of Object with key value representing each search paremeter.
391
- * a parameter object definition is expected to have the following signature:
392
- * {
393
- * email{String} agent email,
394
- * verb {String} verb for the request,
395
- * activityId {String} - activity id ,
396
- * }
397
- * @example here is an exemple of the search param:
398
- * {email: 'wizardy0@mymailbox.com', verb: 'completed',activityId:'"http://localhost:5173/vuetify_p4/v3"' }
399
- * @return {object} data with a collection of all the reponses
400
- * from the promiseAll and the status of last request
401
- */
402
- async _getBulkData(searchParams) {
403
- const validVerbs = [
404
- 'progressed',
405
- 'completed',
406
- 'suspended',
407
- 'terminated',
408
- 'preferred',
409
- 'played',
410
- 'exited'
411
- ]
412
-
413
- const paramList = []
414
- // account statement registered today.
415
- let date = new Date()
416
- let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in
417
-
418
- for (let param of searchParams) {
419
- let { email, verb, activityId } = param
420
- let vb = verb && validVerbs.includes(verb) ? verb : 'terminated'
421
- const params = {}
422
-
423
- params['verb'] = this.verbs[vb].id // completed state
424
- params['activity'] = activityId // this current activity define by this id
425
- params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
426
- params['ascending'] = true //ordered by the oldest statement recorded
427
- params['limit'] = 250 //limit of record to return by the LRS
428
-
429
- params['until'] = dd
430
- paramList.push(params)
431
- }
432
- const { response } = await this.XAPIWrapper.getStatements(paramList)
433
-
434
- let userData = {},
435
- playbarValues = {},
436
- preferredSettings = {},
437
- lessonStatus = {},
438
- savedPoint = ''
439
-
440
- if (response && response.length > 0) {
441
- for (const res of response) {
442
- const { statements } = await res.json()
443
- if (!statements || statements.length == 0) continue
444
-
445
- let lastRecord = statements.toReversed()[0] // get last recorded statement
446
- // Verify that the extensions value of the statement has elements
447
-
448
- //Handeling Lession comppletion status
449
- if (lastRecord.verb.id.includes('completed')) {
450
- lessonStatus = lastRecord.result ? lastRecord.result : {}
451
- }
452
-
453
- if (
454
- lastRecord.object.definition.extensions &&
455
- Object.keys(lastRecord.object.definition.extensions).length
456
- ) {
457
- const allExtensionsKeys = Object.keys(
458
- lastRecord.object.definition.extensions
459
- )
460
-
461
- //Search for the user-data entry in the extensions
462
- allExtensionsKeys.forEach((extension) => {
463
- switch (true) {
464
- case extension.includes('user-data'): {
465
- //Search for the user-data entry in the extensions
466
- const userDataExtension = allExtensionsKeys.filter(
467
- (extension) => extension.includes('user-data')
468
- )
469
- userData =
470
- lastRecord.object.definition.extensions[userDataExtension[0]]
471
-
472
- break
473
- }
474
- case extension.includes('ending-point'): {
475
- const userDataExtension = allExtensionsKeys.filter(
476
- (extension) => extension.includes('ending-point')
477
- )
478
- savedPoint =
479
- lastRecord.object.definition.extensions[userDataExtension[0]]
480
-
481
- break
482
- }
483
- case extension.includes('application-settings'): {
484
- const userDataExtension = allExtensionsKeys.filter(
485
- (extension) => extension.includes('application-settings')
486
- )
487
-
488
- const settingsData =
489
- lastRecord.object.definition.extensions[userDataExtension[0]]
490
-
491
- preferredSettings = settingsData ? settingsData : {}
492
-
493
- break
494
- }
495
- case extension.includes('playbar-values'): {
496
- const userDataExtension = allExtensionsKeys.filter(
497
- (extension) => extension.includes('playbar-values')
498
- )
499
-
500
- const playbarData =
501
- lastRecord.object.definition.extensions[userDataExtension[0]]
502
- playbarValues = playbarData ? playbarData : {}
503
-
504
- break
505
- }
506
- }
507
- })
508
- }
509
- }
510
- }
511
-
512
- return {
513
- userData,
514
- savedPoint,
515
- preferredSettings,
516
- playbarValues,
517
- lessonStatus
518
- }
519
- }
520
- }
1
+ import verbs from './verbs'
2
+ import activities from './activitytypes'
3
+ import xAPILaunch from './launch'
4
+ import { defineUtils } from './utils'
5
+ import { xapiwrapper } from './wrapper'
6
+ import { xapiStatement } from './xapiStatement'
7
+
8
+ //ref: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#partthree
9
+
10
+ export default class ADL {
11
+ constructor() {
12
+ xapiwrapper(this)
13
+ defineUtils(this)
14
+ xAPILaunch(this)
15
+ xapiStatement(this)
16
+
17
+ this.verbs = verbs
18
+ this.activityTypes = activities
19
+
20
+ if (this.XAPIWrapper)
21
+ this._prepareStatement = this.XAPIWrapper.prepareStatement
22
+ }
23
+
24
+ /*
25
+ * Helper function to configurate the LRS
26
+ * @params: config {Object}
27
+ */
28
+ _configLRS(config) {
29
+ //this object (XAPIWrapper) will be attached to the ADL
30
+ if (this.XAPIWrapper) {
31
+ this.XAPIWrapper.changeConfig(config)
32
+ }
33
+ }
34
+
35
+ /*
36
+ * Helper function to Dispatch statement to the LRS
37
+ * @params: Statement {Object}:xapi Statement
38
+ */
39
+ _sendStatement(statement, cb, withFetch = false) {
40
+ //remove the statement Result if it not answer action.
41
+ let res = null
42
+ const withResult =
43
+ statement.verb.id.includes('answered') ||
44
+ statement.verb.id.includes('suspended') ||
45
+ statement.verb.id.includes('completed')
46
+
47
+ if (statement.result && !withResult) {
48
+ delete statement.result
49
+ }
50
+
51
+ //Clear existing id of the statement if any to allow xapi to generate new ID for the Statement.
52
+ if (statement.id) statement.id = ''
53
+
54
+ // Dispatch the statement to the LRS
55
+ if (!this.XAPIWrapper) return
56
+
57
+ if (withFetch) this.XAPIWrapper.sendStatementWithFetch(statement)
58
+ else {
59
+ this.XAPIWrapper.sendStatement(statement, function (response) {
60
+ if (cb) cb()
61
+ res = response
62
+ })
63
+ return res
64
+ }
65
+ }
66
+
67
+ /*
68
+ * Helper function to Dispatch statement to the LRS
69
+ * @params: Statement {Object}:xapi Statement
70
+ */
71
+ _sendStatements(statements, cb, withFetch = false) {
72
+ //remove the statement Result if it not answer action.
73
+ let res = null
74
+ statements.forEach((statement) => {
75
+ const withResult =
76
+ statement.verb.id.includes('answered') ||
77
+ statement.verb.id.includes('played') ||
78
+ statement.verb.id.includes('suspended') ||
79
+ statement.verb.id.includes('completed')
80
+
81
+ if (statement.result && !withResult) {
82
+ delete statement.result
83
+ }
84
+
85
+ //Clear existing id of the statement if any to allow xapi to generate new ID for the Statement.
86
+ if (statement.id) statement.id = ''
87
+ })
88
+
89
+ // Dispatch the statement to the LRS
90
+ if (!this.XAPIWrapper) return
91
+
92
+ if (withFetch) this.XAPIWrapper.sendStatementsWithFetch(statements)
93
+ else {
94
+ this.XAPIWrapper.sendStatements(statements, function (response) {
95
+ if (cb) cb()
96
+ res = response
97
+ })
98
+ return res
99
+ }
100
+ }
101
+
102
+ /*
103
+ * Helper function to get the agent email
104
+ * @Params: {String} email : email of the user
105
+ * @Params: {String} activityId: IRI string of the course/activity id
106
+ * @Params: {String} verb_id: IRI string of the course/activity id
107
+ *
108
+ */
109
+ _getAgent(email, activityId, verb_id) {
110
+ let params, // Array of search parameters
111
+ completedStmts // Array of return search result
112
+
113
+ if (this.XAPIWrapper) {
114
+ params = this.XAPIWrapper.searchParams()
115
+
116
+ if (email) params['agent'] = `{"mbox": "mailto:${email}"}`
117
+ if (activityId) params['activity'] = activityId
118
+ if (verb_id) params['verb'] = verb_id
119
+
120
+ completedStmts = this.XAPIWrapper.getStatements(params)
121
+ }
122
+ //There is a records for the search return the actor
123
+ if (
124
+ completedStmts &&
125
+ completedStmts.statements &&
126
+ completedStmts.statements.length
127
+ )
128
+ return completedStmts.statements[0].actor
129
+ else return false // no result
130
+ }
131
+
132
+ /*@Description: Helper to retrive user progress -
133
+ search for a completion status of a activity if there is no completion status
134
+ * search where the user stopped in the activity
135
+ * return is position in the activity
136
+ *
137
+ * @Params {String} email: user email
138
+ * @Params {String} activityId: the IRI string of the activity
139
+ * @Params {String} verb: the verb to search by completed | terminated
140
+ */
141
+
142
+ async _getProgress(email, activityId, verb) {
143
+ let params
144
+ const validVerbs = [
145
+ 'progressed',
146
+ 'completed',
147
+ 'suspended',
148
+ 'terminated',
149
+ 'exited'
150
+ ]
151
+ if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
152
+
153
+ let vb = verb && validVerbs.includes(verb) ? verb : 'terminated'
154
+
155
+ params['verb'] = this.verbs[vb].id // completed state
156
+ params['activity'] = activityId // this current activity define by this id
157
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
158
+ params['limit'] = 200 //limit of record to return by the LRS
159
+ params['ascending'] = true //ordered by the oldest statement recorded
160
+
161
+ let date = new Date()
162
+ let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
163
+ params['until'] = dd
164
+
165
+ try {
166
+ let userData //placeholder for user data
167
+ let completedStmts = await this.XAPIWrapper.getStatements(params)
168
+
169
+ if (
170
+ completedStmts &&
171
+ completedStmts.statements &&
172
+ completedStmts.statements.length
173
+ ) {
174
+ let lastRecord = completedStmts.statements.toReversed()[0] // get last recorded statement
175
+
176
+ // Verify that the extensions value of the statement as elements
177
+ if (
178
+ lastRecord.object.definition.extensions &&
179
+ Object.keys(lastRecord.object.definition.extensions).length
180
+ ) {
181
+ const allExtensionsKeys = Object.keys(
182
+ lastRecord.object.definition.extensions
183
+ )
184
+
185
+ //Search for the user-data entry in the extensions
186
+ const userDataExtension = allExtensionsKeys.filter((extension) =>
187
+ extension.includes('user-data')
188
+ )
189
+ userData =
190
+ lastRecord.object.definition.extensions[userDataExtension[0]]
191
+ }
192
+ } else {
193
+ userData = {}
194
+ }
195
+ return userData
196
+ } catch (err) {
197
+ console.log(err)
198
+ }
199
+ }
200
+
201
+ /*@Description: Helper to The Lesson Status (result) -
202
+
203
+ * @Params {String} email: user email
204
+ * @Params {String} activityId: the IRI string of the activity
205
+ */
206
+ async _getLessonStatus(email, activityId, verb) {
207
+ let params
208
+ const validVerbs = ['completed', 'suspended']
209
+ if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
210
+ let vb = verb && validVerbs.includes(verb) ? verb : 'suspended'
211
+ params['verb'] = this.verbs[vb].id // completed state
212
+ params['activity'] = activityId // this current activity define by this id
213
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
214
+ params['ascending'] = true //ordered by the oldest statement recorded
215
+
216
+ let completedStmts = await this.XAPIWrapper.getStatements(params)
217
+ let status //placeholder for user data
218
+ if (
219
+ completedStmts &&
220
+ completedStmts.statements &&
221
+ completedStmts.statements.length
222
+ ) {
223
+ let lastRecord = completedStmts.statements.toReversed()[0] // get last recorded statement
224
+
225
+ // Verify that result exist
226
+ if (lastRecord.result) status = lastRecord.result
227
+ } else {
228
+ status = {}
229
+ }
230
+ return status
231
+ }
232
+
233
+ /* @description: last lesson saved point from the LRS
234
+ * search where the user stopped in the activity
235
+ * return is position in the activity
236
+ * @Params {String} email: user email
237
+ * @Params {String} activityId: the IRI string of the activity
238
+ */
239
+
240
+ async _getLessonPosition(email, activityId) {
241
+ let params
242
+ let savedPoint = []
243
+ if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
244
+
245
+ params['verb'] = this.verbs.terminated.id // completed state
246
+ params['activity'] = activityId // this current activity define by this id
247
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
248
+ params['ascending'] = true //ordered by the oldest statement recorded
249
+ params['limit'] = 200 //limit of record to return by the LRS
250
+
251
+ let date = new Date()
252
+ let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
253
+
254
+ params['until'] = dd
255
+
256
+ let progressedStmts = await this.XAPIWrapper.getStatements(params)
257
+
258
+ if (
259
+ progressedStmts &&
260
+ progressedStmts.statements &&
261
+ progressedStmts.statements.length
262
+ ) {
263
+ let lastRecord = progressedStmts.statements.toReversed()[0] // get last recorded statement
264
+ // Verify that the extensions value of the statement has elements
265
+
266
+ if (
267
+ lastRecord.object.definition.extensions &&
268
+ Object.keys(lastRecord.object.definition.extensions).length
269
+ ) {
270
+ const allExtensionsKeys = Object.keys(
271
+ lastRecord.object.definition.extensions
272
+ )
273
+
274
+ //Search for the user-data entry in the extensions
275
+ savedPoint = allExtensionsKeys.map((extension) => {
276
+ if (extension.includes('ending-point'))
277
+ return lastRecord.object.definition.extensions[extension]
278
+ })
279
+ }
280
+ }
281
+
282
+ return savedPoint && savedPoint.length > 0 ? savedPoint[0] : ''
283
+ }
284
+
285
+ /* @description: Helper to get the Preferred Settings of the user
286
+ * return is position in the activity
287
+ * @Params {String} email: user email
288
+ * @Params {String} activityId: the IRI string of the activity
289
+ */
290
+
291
+ async _getPreferredSettings(email, activityId) {
292
+ let params
293
+ let preferredSettings = []
294
+ if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
295
+
296
+ params['verb'] = this.verbs.preferred.id // completed state
297
+ params['activity'] = activityId // this current activity define by this id
298
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
299
+ params['ascending'] = true //ordered by the oldest statement recorded
300
+ params['limit'] = 200 //limit of record to return by the LRS
301
+
302
+ let date = new Date()
303
+ let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
304
+
305
+ params['until'] = dd
306
+
307
+ let preferredStmts = await this.XAPIWrapper.getStatements(params)
308
+
309
+ if (
310
+ preferredStmts &&
311
+ preferredStmts.statements &&
312
+ preferredStmts.statements.length
313
+ ) {
314
+ let lastRecord = preferredStmts.statements.toReversed()[0] // get last recorded statement
315
+
316
+ // Verify that the extensions value of the statement has elements
317
+ if (
318
+ lastRecord.object.definition.extensions &&
319
+ Object.keys(lastRecord.object.definition.extensions).length
320
+ ) {
321
+ const allExtensionsKeys = Object.keys(
322
+ lastRecord.object.definition.extensions
323
+ )
324
+
325
+ //Search for the user-data entry in the extensions
326
+ preferredSettings = allExtensionsKeys.map((extension) => {
327
+ if (extension.includes('application-settings'))
328
+ return lastRecord.object.definition.extensions[extension]
329
+ })
330
+ }
331
+ }
332
+ return preferredSettings[0] ? preferredSettings[0] : {}
333
+ }
334
+
335
+ /* @description: Helper to get the Playbar values of the user
336
+ * @Params {String} email: user email
337
+ * @Params {String} activityId: the IRI string of the activity
338
+ */
339
+
340
+ async _getPlaybarValues(email, activityId) {
341
+ let params
342
+ let playbarValues = []
343
+ if (this.XAPIWrapper) params = this.XAPIWrapper.searchParams()
344
+
345
+ params['verb'] = this.verbs.played.id // completed state
346
+ params['activity'] = activityId // this current activity define by this id
347
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
348
+ params['ascending'] = true //ordered by the oldest statement recorded
349
+ params['limit'] = 200 //limit of record to return by the LRS
350
+
351
+ let date = new Date()
352
+ let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in account statement registered today.
353
+
354
+ params['until'] = dd
355
+
356
+ let playedStmts = await this.XAPIWrapper.getStatements(params)
357
+
358
+ if (
359
+ playedStmts &&
360
+ playedStmts.statements &&
361
+ playedStmts.statements.length
362
+ ) {
363
+ let lastRecord = playedStmts.statements.toReversed()[0] // get last recorded statement
364
+ // Verify that the extensions value of the statement has elements
365
+ if (
366
+ lastRecord.object.definition.extensions &&
367
+ Object.keys(lastRecord.object.definition.extensions).length
368
+ ) {
369
+ const allExtensionsKeys = Object.keys(
370
+ lastRecord.object.definition.extensions
371
+ )
372
+ //Search for the user-data entry in the extensions
373
+ playbarValues = allExtensionsKeys.map((extension) => {
374
+ if (extension.includes('playbar-values'))
375
+ return lastRecord.object.definition.extensions[extension]
376
+ })
377
+ }
378
+ }
379
+ return playbarValues[0] ? playbarValues[0] : {}
380
+ }
381
+ /**
382
+ * Method to get a multiple concurent requests using promiseAll to the server
383
+ * and return all data at once.
384
+ * the following request are made to the server:
385
+ * get application-settings: saved value for the user preferred settings
386
+ * get user-data: saved values for user progression
387
+ * get playbar-value : saved value for the medias play-bar
388
+ * get lesson-status : saved value for the lession status
389
+ *
390
+ * @param {Array} searchParams - Array of Object with key value representing each search paremeter.
391
+ * a parameter object definition is expected to have the following signature:
392
+ * {
393
+ * email{String} agent email,
394
+ * verb {String} verb for the request,
395
+ * activityId {String} - activity id ,
396
+ * }
397
+ * @example here is an exemple of the search param:
398
+ * {email: 'wizardy0@mymailbox.com', verb: 'completed',activityId:'"http://localhost:5173/vuetify_p4/v3"' }
399
+ * @return {object} data with a collection of all the reponses
400
+ * from the promiseAll and the status of last request
401
+ */
402
+ async _getBulkData(searchParams) {
403
+ const validVerbs = [
404
+ 'progressed',
405
+ 'completed',
406
+ 'suspended',
407
+ 'terminated',
408
+ 'preferred',
409
+ 'played',
410
+ 'exited'
411
+ ]
412
+
413
+ const paramList = []
414
+ // account statement registered today.
415
+ let date = new Date()
416
+ let dd = new Date(date.setDate(date.getDate() + 1)).toISOString() //Date is set to next day(tomorrow) for Lrs to take in
417
+
418
+ for (let param of searchParams) {
419
+ let { email, verb, activityId } = param
420
+ let vb = verb && validVerbs.includes(verb) ? verb : 'terminated'
421
+ const params = {}
422
+
423
+ params['verb'] = this.verbs[vb].id // completed state
424
+ params['activity'] = activityId // this current activity define by this id
425
+ params['agent'] = `{"mbox": "mailto:${email}"}` // the user identify by this email
426
+ params['ascending'] = true //ordered by the oldest statement recorded
427
+ params['limit'] = 250 //limit of record to return by the LRS
428
+
429
+ params['until'] = dd
430
+ paramList.push(params)
431
+ }
432
+ const { response } = await this.XAPIWrapper.getStatements(paramList)
433
+
434
+ let userData = {},
435
+ playbarValues = {},
436
+ preferredSettings = {},
437
+ lessonStatus = {},
438
+ savedPoint = ''
439
+
440
+ if (response && response.length > 0) {
441
+ for (const res of response) {
442
+ const { statements } = await res.json()
443
+ if (!statements || statements.length == 0) continue
444
+
445
+ let lastRecord = statements.toReversed()[0] // get last recorded statement
446
+ // Verify that the extensions value of the statement has elements
447
+
448
+ //Handeling Lession comppletion status
449
+ if (lastRecord.verb.id.includes('completed')) {
450
+ lessonStatus = lastRecord.result ? lastRecord.result : {}
451
+ }
452
+
453
+ if (
454
+ lastRecord.object.definition.extensions &&
455
+ Object.keys(lastRecord.object.definition.extensions).length
456
+ ) {
457
+ const allExtensionsKeys = Object.keys(
458
+ lastRecord.object.definition.extensions
459
+ )
460
+
461
+ //Search for the user-data entry in the extensions
462
+ allExtensionsKeys.forEach((extension) => {
463
+ switch (true) {
464
+ case extension.includes('user-data'): {
465
+ //Search for the user-data entry in the extensions
466
+ const userDataExtension = allExtensionsKeys.filter(
467
+ (extension) => extension.includes('user-data')
468
+ )
469
+ userData =
470
+ lastRecord.object.definition.extensions[userDataExtension[0]]
471
+
472
+ break
473
+ }
474
+ case extension.includes('ending-point'): {
475
+ const userDataExtension = allExtensionsKeys.filter(
476
+ (extension) => extension.includes('ending-point')
477
+ )
478
+ savedPoint =
479
+ lastRecord.object.definition.extensions[userDataExtension[0]]
480
+
481
+ break
482
+ }
483
+ case extension.includes('application-settings'): {
484
+ const userDataExtension = allExtensionsKeys.filter(
485
+ (extension) => extension.includes('application-settings')
486
+ )
487
+
488
+ const settingsData =
489
+ lastRecord.object.definition.extensions[userDataExtension[0]]
490
+
491
+ preferredSettings = settingsData ? settingsData : {}
492
+
493
+ break
494
+ }
495
+ case extension.includes('playbar-values'): {
496
+ const userDataExtension = allExtensionsKeys.filter(
497
+ (extension) => extension.includes('playbar-values')
498
+ )
499
+
500
+ const playbarData =
501
+ lastRecord.object.definition.extensions[userDataExtension[0]]
502
+ playbarValues = playbarData ? playbarData : {}
503
+
504
+ break
505
+ }
506
+ }
507
+ })
508
+ }
509
+ }
510
+ }
511
+
512
+ return {
513
+ userData,
514
+ savedPoint,
515
+ preferredSettings,
516
+ playbarValues,
517
+ lessonStatus
518
+ }
519
+ }
520
+ }