fcad-core-dragon 2.0.0-beta.3 → 2.0.0-beta.4
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.
- package/.editorconfig +33 -33
- package/.eslintignore +29 -29
- package/.eslintrc.cjs +81 -81
- package/CHANGELOG +373 -364
- package/README.md +71 -71
- package/bk.scss +117 -117
- package/package.json +61 -61
- package/src/$locales/en.json +143 -143
- package/src/$locales/fr.json +105 -105
- package/src/assets/data/onboardingMessages.json +47 -47
- package/src/components/AppBase.vue +1147 -1054
- package/src/components/AppBaseButton.vue +87 -87
- package/src/components/AppBaseErrorDisplay.vue +438 -438
- package/src/components/AppBaseFlipCard.vue +84 -84
- package/src/components/AppBaseModule.vue +1636 -1673
- package/src/components/AppBasePage.vue +779 -779
- package/src/components/AppBasePopover.vue +41 -41
- package/src/components/AppCompAudio.vue +234 -234
- package/src/components/AppCompBranchButtons.vue +552 -552
- package/src/components/AppCompButtonProgress.vue +126 -126
- package/src/components/AppCompCarousel.vue +298 -298
- package/src/components/AppCompInputCheckBoxNext.vue +195 -195
- package/src/components/AppCompInputDropdownNext.vue +159 -159
- package/src/components/AppCompInputRadioNext.vue +152 -152
- package/src/components/AppCompInputTextNext.vue +106 -106
- package/src/components/AppCompInputTextTableNext.vue +141 -141
- package/src/components/AppCompInputTextToFillDropdownNext.vue +230 -230
- package/src/components/AppCompInputTextToFillNext.vue +171 -171
- package/src/components/AppCompJauge.vue +74 -74
- package/src/components/AppCompMenu.vue +423 -413
- package/src/components/AppCompMenuItem.vue +228 -228
- package/src/components/AppCompNavigation.vue +959 -960
- package/src/components/AppCompNoteCall.vue +133 -133
- package/src/components/AppCompNoteCredit.vue +292 -292
- package/src/components/AppCompPlayBar.vue +1218 -1218
- package/src/components/AppCompPlayBarNext.vue +2052 -2052
- package/src/components/AppCompPlayBarProgress.vue +82 -82
- package/src/components/AppCompPopUpNext.vue +503 -503
- package/src/components/AppCompQuizNext.vue +2904 -2904
- package/src/components/AppCompQuizRecall.vue +276 -276
- package/src/components/AppCompSVGNext.vue +347 -347
- package/src/components/AppCompSettingsMenu.vue +172 -172
- package/src/components/AppCompTableOfContent.vue +387 -387
- package/src/components/AppCompTranscript.vue +24 -24
- package/src/components/AppCompVideoPlayer.vue +368 -368
- package/src/components/AppCompViewDisplay.vue +6 -6
- package/src/components/BaseModule.vue +72 -72
- package/src/composables/useQuiz.js +206 -206
- package/src/externalComps/ModuleView.vue +22 -22
- package/src/externalComps/SummaryView.vue +91 -91
- package/src/main.js +272 -272
- package/src/mixins/$mediaMixins.js +819 -819
- package/src/mixins/timerMixin.js +155 -155
- package/src/module/stores/appStore.js +901 -893
- package/src/module/xapi/ADL.js +380 -376
- package/src/module/xapi/Crypto/Hasher.js +241 -241
- package/src/module/xapi/Crypto/WordArray.js +278 -278
- package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
- package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
- package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
- package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
- package/src/module/xapi/Crypto/encoders/Base.js +105 -105
- package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
- package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
- package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
- package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
- package/src/module/xapi/Crypto/index.js +53 -53
- package/src/module/xapi/Statement/activity.js +47 -47
- package/src/module/xapi/Statement/agent.js +55 -55
- package/src/module/xapi/Statement/group.js +26 -26
- package/src/module/xapi/Statement/index.js +259 -259
- package/src/module/xapi/Statement/statement.js +253 -253
- package/src/module/xapi/Statement/statementRef.js +23 -23
- package/src/module/xapi/Statement/substatement.js +22 -22
- package/src/module/xapi/Statement/verb.js +36 -36
- package/src/module/xapi/activitytypes.js +17 -17
- package/src/module/xapi/launch.js +157 -157
- package/src/module/xapi/utils.js +167 -167
- package/src/module/xapi/verbs.js +294 -294
- package/src/module/xapi/wrapper.js +1963 -1963
- package/src/module/xapi/xapiStatement.js +444 -444
- package/src/plugins/bus.js +8 -8
- package/src/plugins/gsap.js +14 -14
- package/src/plugins/helper.js +314 -308
- package/src/plugins/i18n.js +44 -44
- package/src/plugins/idb.js +227 -219
- package/src/plugins/save.js +37 -37
- package/src/plugins/scorm.js +287 -287
- package/src/plugins/xapi.js +11 -11
- package/src/public/index.html +33 -33
- package/src/router/index.js +43 -43
- package/src/router/routes.js +312 -312
- package/src/shared/generalfuncs.js +210 -210
- package/src/shared/validators.js +1069 -1069
- package/vite.config.js +0 -27
|
@@ -1,1673 +1,1636 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
*@ Description: This component is used as main container to display application and containt to display
|
|
3
|
-
*@ What it does: The component fetch the data for the page to display on navigation.
|
|
4
|
-
*
|
|
5
|
-
*@Note :Must be used
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<template>
|
|
9
|
-
<div fluid class="module">
|
|
10
|
-
<div
|
|
11
|
-
id="page_info_section"
|
|
12
|
-
aria-labelledby="page_info"
|
|
13
|
-
aria-live="true"
|
|
14
|
-
></div>
|
|
15
|
-
<a class="skip-link" href="" @click.prevent="skipToMain">
|
|
16
|
-
{{ $t('message.skip_content') }}
|
|
17
|
-
</a>
|
|
18
|
-
|
|
19
|
-
<nav
|
|
20
|
-
v-show="!isMenu"
|
|
21
|
-
id="navTool"
|
|
22
|
-
:key="$route.fullpath"
|
|
23
|
-
class="app-nav"
|
|
24
|
-
:class="{ show: closeDelay }"
|
|
25
|
-
>
|
|
26
|
-
<!------------------------ Nav for drMode ------------------>
|
|
27
|
-
<!-- <app-comp-table-of-content /> -->
|
|
28
|
-
<!------------------------ Nav for FullMode ---------------->
|
|
29
|
-
|
|
30
|
-
<app-comp-navigation
|
|
31
|
-
:app-status="appReady ? true : null"
|
|
32
|
-
:auto-navigate="theNavigationBetweenActivity"
|
|
33
|
-
/>
|
|
34
|
-
</nav>
|
|
35
|
-
<base-module :m-data="$data">
|
|
36
|
-
<v-container
|
|
37
|
-
id="wrapper-content"
|
|
38
|
-
fluid
|
|
39
|
-
:class="{ active: moduleConfig.videoFull }"
|
|
40
|
-
class="scroll-bar"
|
|
41
|
-
>
|
|
42
|
-
<div class="box">
|
|
43
|
-
<router-view ref="main" :key="$route.fullPath" />
|
|
44
|
-
<!-- <router-view v-show="appReady" ref="main" :key="$route.fullPath" /> -->
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div id="primary_nav_wrapper"></div>
|
|
48
|
-
</v-container>
|
|
49
|
-
</base-module>
|
|
50
|
-
|
|
51
|
-
<!------------------POPUP QUICK -------------------------->
|
|
52
|
-
<app-comp-pop-up-next v-show="popupIsOpen">
|
|
53
|
-
<!-- <template #content></template> -->
|
|
54
|
-
</app-comp-pop-up-next>
|
|
55
|
-
<!------------------END USE POP UP-------------------------->
|
|
56
|
-
|
|
57
|
-
<!--------------RIGHT SIDEBAR (for display of extra contents)------------>
|
|
58
|
-
<Transition name="right-sidebar-transition" mode="out-in">
|
|
59
|
-
<div
|
|
60
|
-
v-if="rightSidebarVisible"
|
|
61
|
-
id="right-sidebar"
|
|
62
|
-
ref="right-sidebar"
|
|
63
|
-
:key="dynamicSidebarContent._id"
|
|
64
|
-
:class="{
|
|
65
|
-
'v-media': dynamicSidebarContent._context === 'ctxTranscript'
|
|
66
|
-
// 'right-sidebar-show': rightSidebarVisible
|
|
67
|
-
}"
|
|
68
|
-
:aria-label="dynamicSidebarContent._label"
|
|
69
|
-
>
|
|
70
|
-
<div id="right-sidebar-header">
|
|
71
|
-
<button
|
|
72
|
-
class="btn-reserve-ico embranchement-close"
|
|
73
|
-
@click="
|
|
74
|
-
closeSidebar(
|
|
75
|
-
dynamicSidebarContent._context,
|
|
76
|
-
dynamicSidebarContent._container
|
|
77
|
-
? dynamicSidebarContent._container
|
|
78
|
-
: null
|
|
79
|
-
)
|
|
80
|
-
"
|
|
81
|
-
>
|
|
82
|
-
<svg>
|
|
83
|
-
<use href="#close-square-icon" />
|
|
84
|
-
</svg>
|
|
85
|
-
<span class="sr-only">{{ $t('button.closePopUp') }}</span>
|
|
86
|
-
</button>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
<div id="right-sidebar-body">
|
|
90
|
-
<component
|
|
91
|
-
:is="dynamicSidebarContent._component"
|
|
92
|
-
class="v-media"
|
|
93
|
-
v-bind="{ ...dynamicSidebarContent._comProps }"
|
|
94
|
-
/>
|
|
95
|
-
<!-- </Transition> -->
|
|
96
|
-
</div>
|
|
97
|
-
<div id="right-sidebar-footer"></div>
|
|
98
|
-
</div>
|
|
99
|
-
</Transition>
|
|
100
|
-
<footer></footer>
|
|
101
|
-
<!-------------------------END RIGHT SIDEBAR------------------------------->
|
|
102
|
-
</div>
|
|
103
|
-
</template>
|
|
104
|
-
<script>
|
|
105
|
-
import { mapState, mapActions } from 'pinia'
|
|
106
|
-
import { useAppStore } from '../module/stores/appStore'
|
|
107
|
-
import BaseModule from './BaseModule.vue'
|
|
108
|
-
import { timerMixin } from '../mixins/timerMixin'
|
|
109
|
-
import AppCompTranscript from './AppCompTranscript.vue'
|
|
110
|
-
import { defineAsyncComponent } from 'vue'
|
|
111
|
-
//import { fileAssets } from '../shared/generalfuncs'
|
|
112
|
-
//import
|
|
113
|
-
export default {
|
|
114
|
-
components: {
|
|
115
|
-
BaseModule,
|
|
116
|
-
AppCompTranscript
|
|
117
|
-
},
|
|
118
|
-
mixins: [timerMixin],
|
|
119
|
-
props: {
|
|
120
|
-
moduleConfig: {
|
|
121
|
-
type: Object,
|
|
122
|
-
default: () => {
|
|
123
|
-
return {
|
|
124
|
-
allowNavigationToActivity: false, // set Previous/Next can allow navigation between activities. Set to false if do not want navigation between activities with Previous/Next
|
|
125
|
-
main: ''
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
data() {
|
|
131
|
-
return {
|
|
132
|
-
meta: {},
|
|
133
|
-
videoFull: null,
|
|
134
|
-
routeData: [],
|
|
135
|
-
changePage: false,
|
|
136
|
-
randKey: Math.floor(Math.random() * 10001),
|
|
137
|
-
popupIsOpen: false,
|
|
138
|
-
hidePlayBar: false, // Controle visibility of the play bar. set to true to hide play bar
|
|
139
|
-
stmt: null, // holder for xapi statememt,
|
|
140
|
-
moduleTimer: 0, //tracker for overall time spent the lesson
|
|
141
|
-
activityTimer: 0, // tracker for the time spent on activity,
|
|
142
|
-
routeChangeCounter: 0,
|
|
143
|
-
toolTipTarget: '', //for the tool tip,
|
|
144
|
-
onboardingMessages: {}, //for the onboarding @todo replace with default file
|
|
145
|
-
settingsSelected: {},
|
|
146
|
-
compID: null,
|
|
147
|
-
rightSidebarVisible: false,
|
|
148
|
-
lastInFocus: null,
|
|
149
|
-
closeDelay: false,
|
|
150
|
-
timeOut: null,
|
|
151
|
-
infocusTabIndex: null,
|
|
152
|
-
transcriptVisible: false,
|
|
153
|
-
transcriptContent: null,
|
|
154
|
-
transcriptContainer: null,
|
|
155
|
-
branchingVisible: false,
|
|
156
|
-
lessonCompletionStatus: false,
|
|
157
|
-
checkedDataFromServer: 0
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
computed: {
|
|
161
|
-
...mapState(useAppStore, [
|
|
162
|
-
'getCurrentPage',
|
|
163
|
-
'getCurrentBranchPage',
|
|
164
|
-
'getAppStatus',
|
|
165
|
-
'getUserInteraction',
|
|
166
|
-
'getModuleInfo',
|
|
167
|
-
'getAllActivities',
|
|
168
|
-
'getAllCompleted',
|
|
169
|
-
'getConnectionInfo',
|
|
170
|
-
'hasMediaElOrTimeline',
|
|
171
|
-
'getMenuSettings',
|
|
172
|
-
'getRouteHistory',
|
|
173
|
-
'getOnboardingEnabled',
|
|
174
|
-
'getApplicationSettings',
|
|
175
|
-
'getDataFromServer',
|
|
176
|
-
'getCompStatusTracker'
|
|
177
|
-
]),
|
|
178
|
-
isMenu() {
|
|
179
|
-
return this.$route.name === 'menu'
|
|
180
|
-
},
|
|
181
|
-
appReady() {
|
|
182
|
-
return this.getAppStatus === 'ready' ? true : false
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
hasMedia() {
|
|
186
|
-
return typeof this.hasMediaElOrTimeline === 'object'
|
|
187
|
-
},
|
|
188
|
-
activityHasChanged() {
|
|
189
|
-
const rd = this.routeData.toReversed()
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
rd.length <= 1 ||
|
|
193
|
-
rd[1].activity_ref !== this.$route.meta.activity_ref
|
|
194
|
-
)
|
|
195
|
-
return true
|
|
196
|
-
else return false
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* @description Set the id the module
|
|
201
|
-
*/
|
|
202
|
-
theId() {
|
|
203
|
-
let id = 'mod_001'
|
|
204
|
-
if (this.moduleConfig.id) id = this.moduleConfig.id
|
|
205
|
-
return id
|
|
206
|
-
},
|
|
207
|
-
/**
|
|
208
|
-
* @description Set the title of the lesson
|
|
209
|
-
*/
|
|
210
|
-
theTitle() {
|
|
211
|
-
let title = this.$t(`text.place_holder.for_lesson_title`)
|
|
212
|
-
|
|
213
|
-
if (this.getMenuSettings.lessonTitle)
|
|
214
|
-
title = this.getMenuSettings.lessonTitle
|
|
215
|
-
return title
|
|
216
|
-
},
|
|
217
|
-
/**
|
|
218
|
-
* @description Set desciption for the module
|
|
219
|
-
*
|
|
220
|
-
*/
|
|
221
|
-
theDescription() {
|
|
222
|
-
let description = null
|
|
223
|
-
|
|
224
|
-
if (this.moduleConfig.description)
|
|
225
|
-
description = this.moduleConfig.description
|
|
226
|
-
return description
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return
|
|
238
|
-
},
|
|
239
|
-
/**
|
|
240
|
-
* @description
|
|
241
|
-
*
|
|
242
|
-
*/
|
|
243
|
-
|
|
244
|
-
let
|
|
245
|
-
if (this.moduleConfig.
|
|
246
|
-
|
|
247
|
-
return
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (this.
|
|
256
|
-
|
|
257
|
-
return
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (!
|
|
262
|
-
|
|
263
|
-
let
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (this.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
this.$bus.$off('
|
|
448
|
-
this.$bus.$off('
|
|
449
|
-
this.$bus.$off('
|
|
450
|
-
this.$bus.$off('
|
|
451
|
-
this.$bus.$off('
|
|
452
|
-
this.$bus.$off('
|
|
453
|
-
this.$bus.$off('
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
this.$bus.$on('
|
|
503
|
-
|
|
504
|
-
this.$bus.$on('
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
'
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
this.
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
* @
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
* @
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
* @
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
break
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
this.
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
let
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
let
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
this
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
width:
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
position: fixed;
|
|
1638
|
-
right: 0;
|
|
1639
|
-
top: 0;
|
|
1640
|
-
background-color: #ffffff;
|
|
1641
|
-
-webkit-box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
|
|
1642
|
-
box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
|
|
1643
|
-
|
|
1644
|
-
#right-sidebar-header {
|
|
1645
|
-
display: flex;
|
|
1646
|
-
flex-direction: column;
|
|
1647
|
-
align-items: flex-end;
|
|
1648
|
-
padding: 32px 20px 0 32px;
|
|
1649
|
-
|
|
1650
|
-
.embranchement-close {
|
|
1651
|
-
padding: 11px 13px;
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
#right-sidebar-body {
|
|
1656
|
-
max-height: 90%;
|
|
1657
|
-
overflow-y: auto;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
.right-sidebar-transition-enter-active {
|
|
1662
|
-
transition: transform 0.33s ease-out;
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
.right-sidebar-transition-leave-active {
|
|
1666
|
-
transition: transform 0.16s;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
.right-sidebar-transition-enter-from,
|
|
1670
|
-
.right-sidebar-transition-leave-to {
|
|
1671
|
-
transform: translateX(100%);
|
|
1672
|
-
}
|
|
1673
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
*@ Description: This component is used as main container to display application and containt to display
|
|
3
|
+
*@ What it does: The component fetch the data for the page to display on navigation.
|
|
4
|
+
*
|
|
5
|
+
*@Note :Must be used
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div fluid class="module">
|
|
10
|
+
<div
|
|
11
|
+
id="page_info_section"
|
|
12
|
+
aria-labelledby="page_info"
|
|
13
|
+
aria-live="true"
|
|
14
|
+
></div>
|
|
15
|
+
<a class="skip-link" href="" @click.prevent="skipToMain">
|
|
16
|
+
{{ $t('message.skip_content') }}
|
|
17
|
+
</a>
|
|
18
|
+
|
|
19
|
+
<nav
|
|
20
|
+
v-show="!isMenu"
|
|
21
|
+
id="navTool"
|
|
22
|
+
:key="$route.fullpath"
|
|
23
|
+
class="app-nav"
|
|
24
|
+
:class="{ show: closeDelay }"
|
|
25
|
+
>
|
|
26
|
+
<!------------------------ Nav for drMode ------------------>
|
|
27
|
+
<!-- <app-comp-table-of-content /> -->
|
|
28
|
+
<!------------------------ Nav for FullMode ---------------->
|
|
29
|
+
|
|
30
|
+
<app-comp-navigation
|
|
31
|
+
:app-status="appReady ? true : null"
|
|
32
|
+
:auto-navigate="theNavigationBetweenActivity"
|
|
33
|
+
/>
|
|
34
|
+
</nav>
|
|
35
|
+
<base-module :m-data="$data">
|
|
36
|
+
<v-container
|
|
37
|
+
id="wrapper-content"
|
|
38
|
+
fluid
|
|
39
|
+
:class="{ active: moduleConfig.videoFull }"
|
|
40
|
+
class="scroll-bar"
|
|
41
|
+
>
|
|
42
|
+
<div class="box">
|
|
43
|
+
<router-view ref="main" :key="$route.fullPath" />
|
|
44
|
+
<!-- <router-view v-show="appReady" ref="main" :key="$route.fullPath" /> -->
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div id="primary_nav_wrapper"></div>
|
|
48
|
+
</v-container>
|
|
49
|
+
</base-module>
|
|
50
|
+
|
|
51
|
+
<!------------------POPUP QUICK -------------------------->
|
|
52
|
+
<app-comp-pop-up-next v-show="popupIsOpen">
|
|
53
|
+
<!-- <template #content></template> -->
|
|
54
|
+
</app-comp-pop-up-next>
|
|
55
|
+
<!------------------END USE POP UP-------------------------->
|
|
56
|
+
|
|
57
|
+
<!--------------RIGHT SIDEBAR (for display of extra contents)------------>
|
|
58
|
+
<Transition name="right-sidebar-transition" mode="out-in">
|
|
59
|
+
<div
|
|
60
|
+
v-if="rightSidebarVisible"
|
|
61
|
+
id="right-sidebar"
|
|
62
|
+
ref="right-sidebar"
|
|
63
|
+
:key="dynamicSidebarContent._id"
|
|
64
|
+
:class="{
|
|
65
|
+
'v-media': dynamicSidebarContent._context === 'ctxTranscript'
|
|
66
|
+
// 'right-sidebar-show': rightSidebarVisible
|
|
67
|
+
}"
|
|
68
|
+
:aria-label="dynamicSidebarContent._label"
|
|
69
|
+
>
|
|
70
|
+
<div id="right-sidebar-header">
|
|
71
|
+
<button
|
|
72
|
+
class="btn-reserve-ico embranchement-close"
|
|
73
|
+
@click="
|
|
74
|
+
closeSidebar(
|
|
75
|
+
dynamicSidebarContent._context,
|
|
76
|
+
dynamicSidebarContent._container
|
|
77
|
+
? dynamicSidebarContent._container
|
|
78
|
+
: null
|
|
79
|
+
)
|
|
80
|
+
"
|
|
81
|
+
>
|
|
82
|
+
<svg>
|
|
83
|
+
<use href="#close-square-icon" />
|
|
84
|
+
</svg>
|
|
85
|
+
<span class="sr-only">{{ $t('button.closePopUp') }}</span>
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div id="right-sidebar-body">
|
|
90
|
+
<component
|
|
91
|
+
:is="dynamicSidebarContent._component"
|
|
92
|
+
class="v-media"
|
|
93
|
+
v-bind="{ ...dynamicSidebarContent._comProps }"
|
|
94
|
+
/>
|
|
95
|
+
<!-- </Transition> -->
|
|
96
|
+
</div>
|
|
97
|
+
<div id="right-sidebar-footer"></div>
|
|
98
|
+
</div>
|
|
99
|
+
</Transition>
|
|
100
|
+
<footer></footer>
|
|
101
|
+
<!-------------------------END RIGHT SIDEBAR------------------------------->
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
<script>
|
|
105
|
+
import { mapState, mapActions } from 'pinia'
|
|
106
|
+
import { useAppStore } from '../module/stores/appStore'
|
|
107
|
+
import BaseModule from './BaseModule.vue'
|
|
108
|
+
import { timerMixin } from '../mixins/timerMixin'
|
|
109
|
+
import AppCompTranscript from './AppCompTranscript.vue'
|
|
110
|
+
import { defineAsyncComponent } from 'vue'
|
|
111
|
+
//import { fileAssets } from '../shared/generalfuncs'
|
|
112
|
+
//import
|
|
113
|
+
export default {
|
|
114
|
+
components: {
|
|
115
|
+
BaseModule,
|
|
116
|
+
AppCompTranscript
|
|
117
|
+
},
|
|
118
|
+
mixins: [timerMixin],
|
|
119
|
+
props: {
|
|
120
|
+
moduleConfig: {
|
|
121
|
+
type: Object,
|
|
122
|
+
default: () => {
|
|
123
|
+
return {
|
|
124
|
+
allowNavigationToActivity: false, // set Previous/Next can allow navigation between activities. Set to false if do not want navigation between activities with Previous/Next
|
|
125
|
+
main: ''
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
data() {
|
|
131
|
+
return {
|
|
132
|
+
meta: {},
|
|
133
|
+
videoFull: null,
|
|
134
|
+
routeData: [],
|
|
135
|
+
changePage: false,
|
|
136
|
+
randKey: Math.floor(Math.random() * 10001),
|
|
137
|
+
popupIsOpen: false,
|
|
138
|
+
hidePlayBar: false, // Controle visibility of the play bar. set to true to hide play bar
|
|
139
|
+
stmt: null, // holder for xapi statememt,
|
|
140
|
+
moduleTimer: 0, //tracker for overall time spent the lesson
|
|
141
|
+
activityTimer: 0, // tracker for the time spent on activity,
|
|
142
|
+
routeChangeCounter: 0,
|
|
143
|
+
toolTipTarget: '', //for the tool tip,
|
|
144
|
+
onboardingMessages: {}, //for the onboarding @todo replace with default file
|
|
145
|
+
settingsSelected: {},
|
|
146
|
+
compID: null,
|
|
147
|
+
rightSidebarVisible: false,
|
|
148
|
+
lastInFocus: null,
|
|
149
|
+
closeDelay: false,
|
|
150
|
+
timeOut: null,
|
|
151
|
+
infocusTabIndex: null,
|
|
152
|
+
transcriptVisible: false,
|
|
153
|
+
transcriptContent: null,
|
|
154
|
+
transcriptContainer: null,
|
|
155
|
+
branchingVisible: false,
|
|
156
|
+
lessonCompletionStatus: false,
|
|
157
|
+
checkedDataFromServer: 0
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
computed: {
|
|
161
|
+
...mapState(useAppStore, [
|
|
162
|
+
'getCurrentPage',
|
|
163
|
+
'getCurrentBranchPage',
|
|
164
|
+
'getAppStatus',
|
|
165
|
+
'getUserInteraction',
|
|
166
|
+
'getModuleInfo',
|
|
167
|
+
'getAllActivities',
|
|
168
|
+
'getAllCompleted',
|
|
169
|
+
'getConnectionInfo',
|
|
170
|
+
'hasMediaElOrTimeline',
|
|
171
|
+
'getMenuSettings',
|
|
172
|
+
'getRouteHistory',
|
|
173
|
+
'getOnboardingEnabled',
|
|
174
|
+
'getApplicationSettings',
|
|
175
|
+
'getDataFromServer',
|
|
176
|
+
'getCompStatusTracker'
|
|
177
|
+
]),
|
|
178
|
+
isMenu() {
|
|
179
|
+
return this.$route.name === 'menu'
|
|
180
|
+
},
|
|
181
|
+
appReady() {
|
|
182
|
+
return this.getAppStatus === 'ready' ? true : false
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
hasMedia() {
|
|
186
|
+
return typeof this.hasMediaElOrTimeline === 'object'
|
|
187
|
+
},
|
|
188
|
+
activityHasChanged() {
|
|
189
|
+
const rd = this.routeData.toReversed()
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
rd.length <= 1 ||
|
|
193
|
+
rd[1].activity_ref !== this.$route.meta.activity_ref
|
|
194
|
+
)
|
|
195
|
+
return true
|
|
196
|
+
else return false
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @description Set the id the module
|
|
201
|
+
*/
|
|
202
|
+
theId() {
|
|
203
|
+
let id = 'mod_001'
|
|
204
|
+
if (this.moduleConfig.id) id = this.moduleConfig.id
|
|
205
|
+
return id
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* @description Set the title of the lesson
|
|
209
|
+
*/
|
|
210
|
+
theTitle() {
|
|
211
|
+
let title = this.$t(`text.place_holder.for_lesson_title`)
|
|
212
|
+
|
|
213
|
+
if (this.getMenuSettings.lessonTitle)
|
|
214
|
+
title = this.getMenuSettings.lessonTitle
|
|
215
|
+
return title
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* @description Set desciption for the module
|
|
219
|
+
*
|
|
220
|
+
*/
|
|
221
|
+
theDescription() {
|
|
222
|
+
let description = null
|
|
223
|
+
|
|
224
|
+
if (this.moduleConfig.description)
|
|
225
|
+
description = this.moduleConfig.description
|
|
226
|
+
return description
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @description set Previous/Next can allow navigation between activities.
|
|
231
|
+
*
|
|
232
|
+
*/
|
|
233
|
+
theNavigationBetweenActivity() {
|
|
234
|
+
let navBwteenActivity = false
|
|
235
|
+
if (this.moduleConfig.allowNavigationToActivity)
|
|
236
|
+
navBwteenActivity = this.moduleConfig.allowNavigationToActivity
|
|
237
|
+
return navBwteenActivity
|
|
238
|
+
},
|
|
239
|
+
/**
|
|
240
|
+
* @description Control use INTRODUCTION page in the Lesson.
|
|
241
|
+
* set to false if there is no introduction
|
|
242
|
+
*/
|
|
243
|
+
theIntroIsActivated() {
|
|
244
|
+
let introActive = false
|
|
245
|
+
if (this.moduleConfig.introActive)
|
|
246
|
+
introActive = this.moduleConfig.introActive
|
|
247
|
+
return introActive
|
|
248
|
+
},
|
|
249
|
+
isMain() {
|
|
250
|
+
const { main } = this.moduleConfig
|
|
251
|
+
if (!main || main === ' ') return this.$route.meta.id
|
|
252
|
+
|
|
253
|
+
let mainEl = document.querySelector(`#${main}`)
|
|
254
|
+
|
|
255
|
+
if (!mainEl) return this.$route.meta.id
|
|
256
|
+
|
|
257
|
+
return mainEl
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
dynamicSidebarContent() {
|
|
261
|
+
if (!this.transcriptVisible && !this.branchingVisible) return null
|
|
262
|
+
let sidebarSettings = {}
|
|
263
|
+
let _label = null
|
|
264
|
+
|
|
265
|
+
if (this.transcriptVisible) {
|
|
266
|
+
_label =
|
|
267
|
+
this.$i18n.locale === 'fr'
|
|
268
|
+
? 'Contenu de la transcription'
|
|
269
|
+
: 'Content of the transcript'
|
|
270
|
+
|
|
271
|
+
sidebarSettings = {
|
|
272
|
+
_component: AppCompTranscript,
|
|
273
|
+
_comProps: {
|
|
274
|
+
content: this.transcriptContent
|
|
275
|
+
},
|
|
276
|
+
_context: 'ctxTranscript',
|
|
277
|
+
_container: this.transcriptContainer,
|
|
278
|
+
_label,
|
|
279
|
+
_id: 'transcript'
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
//console.log(sidebarSettings)
|
|
283
|
+
} else if (this.branchingVisible) {
|
|
284
|
+
if (this.$route.meta.type !== 'branching' || !this.compID) return null
|
|
285
|
+
|
|
286
|
+
const componentName = this.compID
|
|
287
|
+
const { activityRef } = this.getCurrentPage //get activity id from current page
|
|
288
|
+
_label =
|
|
289
|
+
this.$i18n.locale === 'fr'
|
|
290
|
+
? "contenu de l'embranchement"
|
|
291
|
+
: 'content of the branching'
|
|
292
|
+
|
|
293
|
+
sidebarSettings = {
|
|
294
|
+
_component: defineAsyncComponent(
|
|
295
|
+
() => import(`@/module/${activityRef}/${componentName}.vue`)
|
|
296
|
+
),
|
|
297
|
+
_comProps: false,
|
|
298
|
+
_context: 'ctxBranching',
|
|
299
|
+
_label,
|
|
300
|
+
_id: componentName
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return sidebarSettings
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
navigationHistory() {
|
|
307
|
+
return this.getRouteHistory
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
watch: {
|
|
311
|
+
navigationHistory: {
|
|
312
|
+
handler() {
|
|
313
|
+
this.routeData = this.navigationHistory
|
|
314
|
+
},
|
|
315
|
+
deep: true,
|
|
316
|
+
immediate: true
|
|
317
|
+
},
|
|
318
|
+
$route: {
|
|
319
|
+
handler() {
|
|
320
|
+
this.getFocusables().then((r) => {
|
|
321
|
+
//Pressing Tab or Shit + tab should make it possible to cycle focus through them
|
|
322
|
+
if (!r) return
|
|
323
|
+
r[0].focus()
|
|
324
|
+
this.infocusTabIndex = r[0]
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
//update the routeChangeCounter when navigation
|
|
328
|
+
if (this.routeChangeCounter < 3) this.routeChangeCounter += 1
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
*Start Timer on New activities
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
const trackedRouteType = ['introduction', 'conclusion', 'normal']
|
|
335
|
+
if (trackedRouteType.includes(this.$route.meta.type)) {
|
|
336
|
+
if (this.$route.name.includes('.')) return
|
|
337
|
+
|
|
338
|
+
//Start the timer every time that there is a new activity
|
|
339
|
+
if (this.timerState === 'started') this.stopTimer('activity') // reset the activity timer
|
|
340
|
+
this.startTimer('activity')
|
|
341
|
+
|
|
342
|
+
//Send statement when activity as changed
|
|
343
|
+
if (this.activityHasChanged)
|
|
344
|
+
this.sendStartStatement({ id: this.$route.meta.activity_ref })
|
|
345
|
+
}
|
|
346
|
+
this.transcriptVisible = false
|
|
347
|
+
},
|
|
348
|
+
deep: true,
|
|
349
|
+
immediate: true
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @description Defined in Store Watch all completed activities to send a lesson completion statement
|
|
354
|
+
*/
|
|
355
|
+
getAllCompleted: {
|
|
356
|
+
handler() {
|
|
357
|
+
// Check once the server to set the completion status of the lesson
|
|
358
|
+
if (this.getDataFromServer && this.checkedDataFromServer < 1) {
|
|
359
|
+
const { completedState } = this.getDataFromServer
|
|
360
|
+
|
|
361
|
+
this.lessonCompletionStatus =
|
|
362
|
+
!completedState || !completedState.completion
|
|
363
|
+
? false
|
|
364
|
+
: completedState.completion
|
|
365
|
+
this.checkedDataFromServer += 1
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!this.lessonCompletionStatus) this.sendCompletionStatus('LESSON')
|
|
369
|
+
},
|
|
370
|
+
deep: true,
|
|
371
|
+
immediate: true
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* @description Defined in timerMixin Watch The epalsed time for autosaving the user reached position
|
|
375
|
+
*/
|
|
376
|
+
elapsedTime: {
|
|
377
|
+
handler() {
|
|
378
|
+
if (this.timerState !== 'started') return
|
|
379
|
+
//send a statement every x time (second)
|
|
380
|
+
if (
|
|
381
|
+
this.timerState === 'started' &&
|
|
382
|
+
this.elapsedTime % 500 === 0 &&
|
|
383
|
+
this.getModuleInfo.packageType === 'xapi' &&
|
|
384
|
+
this.getConnectionInfo &&
|
|
385
|
+
this.getConnectionInfo.actor &&
|
|
386
|
+
this.getConnectionInfo.remote &&
|
|
387
|
+
this.getDataFromServer
|
|
388
|
+
) {
|
|
389
|
+
const { lessonPosition } = this.getDataFromServer
|
|
390
|
+
|
|
391
|
+
const lastReached = lessonPosition.length ? lessonPosition[0] : ''
|
|
392
|
+
|
|
393
|
+
//only Send savepoint statement when 3 navigation happen and the route is different from last saved
|
|
394
|
+
if (
|
|
395
|
+
this.routeChangeCounter >= 3 &&
|
|
396
|
+
lastReached !== this.$route.name
|
|
397
|
+
) {
|
|
398
|
+
let text
|
|
399
|
+
//Defining the text to display for stmt description and definition
|
|
400
|
+
switch (this.$i18n.locale) {
|
|
401
|
+
case 'fr':
|
|
402
|
+
if (this.getModuleInfo.courseID)
|
|
403
|
+
text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
|
|
404
|
+
else text = `Le ${this.getModuleInfo.id}`
|
|
405
|
+
break
|
|
406
|
+
case 'en':
|
|
407
|
+
if (this.getModuleInfo.courseID)
|
|
408
|
+
text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
|
|
409
|
+
else text = `The ${this.getModuleInfo.id}`
|
|
410
|
+
break
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
//Creating custom statment
|
|
414
|
+
const stmt = {
|
|
415
|
+
verb: 'progressed',
|
|
416
|
+
definition: text,
|
|
417
|
+
description: text,
|
|
418
|
+
extensions: [
|
|
419
|
+
{
|
|
420
|
+
id: 'ending-point',
|
|
421
|
+
content: (() =>
|
|
422
|
+
this.$route.name == 'menu'
|
|
423
|
+
? this.$helper.getRoutesFromVueRouter().meta.children[0]
|
|
424
|
+
._namedRoute
|
|
425
|
+
: this.$route.name)()
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
duration: this.lessonDuration
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this.$bus.$emit('send-xapi-statement', stmt)
|
|
432
|
+
this.routeChangeCounter = 0 //reset counter after saving
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
deep: true
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
beforeUnmount() {
|
|
440
|
+
//Communication events
|
|
441
|
+
this.$bus.$off('close-sidebar', this.closeSidebar)
|
|
442
|
+
this.$bus.$off('open-sidebar', this.openSidebar)
|
|
443
|
+
this.$bus.$off('open-popup', this.openPopup)
|
|
444
|
+
this.$bus.$off('close-popup', this.closePopup)
|
|
445
|
+
this.$bus.$off('start-onboarding', this.startOnboarding)
|
|
446
|
+
this.$bus.$off('videoFullScreen', this.onVideoFullScreen)
|
|
447
|
+
this.$bus.$off('save-to-scorm', this.saveToScorm)
|
|
448
|
+
this.$bus.$off('launch-xapi-resource', this.launchResource)
|
|
449
|
+
this.$bus.$off('update-route-history', this.updateRouteHistory)
|
|
450
|
+
this.$bus.$off('update-content', this.updateContent)
|
|
451
|
+
this.$bus.$off('show-transcript', this.openTranscript)
|
|
452
|
+
this.$bus.$off('send-completion-event', this.sendCompletionStatus)
|
|
453
|
+
this.$bus.$off('send-starting-event', this.sendStartStatement)
|
|
454
|
+
//nav mouseleave event
|
|
455
|
+
let nav = document.getElementById('navTool')
|
|
456
|
+
if (nav) {
|
|
457
|
+
nav.removeEventListener('mouseleave', this.onNavMouseleave)
|
|
458
|
+
}
|
|
459
|
+
//sidebar scroll event
|
|
460
|
+
const rightSidebar = this.getRightSidebar()
|
|
461
|
+
if (rightSidebar) {
|
|
462
|
+
rightSidebar.removeEventListener('scroll', this.handleRightSidebarScroll)
|
|
463
|
+
}
|
|
464
|
+
//keayboard listener
|
|
465
|
+
document.removeEventListener('keydown', this.handleKeyboardControls)
|
|
466
|
+
},
|
|
467
|
+
created() {
|
|
468
|
+
let lessonLabel, lessonNumber, lessonTitle, titleString
|
|
469
|
+
lessonLabel = this.$t('text.lesson')
|
|
470
|
+
lessonNumber = this.moduleConfig.id.replace('module_', '')
|
|
471
|
+
lessonTitle = this.theTitle
|
|
472
|
+
titleString = lessonLabel + ' ' + lessonNumber + ' : ' + lessonTitle
|
|
473
|
+
|
|
474
|
+
//Remove prefix for introduction or conclusion, according to isIntroConclu setting in Module.vue
|
|
475
|
+
if (typeof this.moduleConfig.isIntroConclu !== 'undefined') {
|
|
476
|
+
if (this.moduleConfig.isIntroConclu) {
|
|
477
|
+
titleString = lessonTitle
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
document.title = titleString
|
|
481
|
+
|
|
482
|
+
// Tell the store the state of intro and conclusion
|
|
483
|
+
this.updateIntroStatus(this.theIntroIsActivated)
|
|
484
|
+
//Communication events
|
|
485
|
+
this.$bus.$on('open-popup', this.openPopup)
|
|
486
|
+
|
|
487
|
+
this.$bus.$on('close-popup', this.closePopup)
|
|
488
|
+
|
|
489
|
+
this.$bus.$on('start-onboarding', this.startOnboarding)
|
|
490
|
+
|
|
491
|
+
this.$bus.$on('open-sidebar', this.openSidebar)
|
|
492
|
+
|
|
493
|
+
this.$bus.$on('close-sidebar', this.closeSidebar)
|
|
494
|
+
|
|
495
|
+
this.$bus.$on('videoFullScreen', this.onVideoFullScreen)
|
|
496
|
+
|
|
497
|
+
this.$bus.$on('save-to-scorm', this.saveToScorm)
|
|
498
|
+
|
|
499
|
+
this.$bus.$on('launch-xapi-resource', this.launchResource)
|
|
500
|
+
|
|
501
|
+
this.$bus.$on('update-route-history', this.updateRouteHistory)
|
|
502
|
+
this.$bus.$on('update-content', this.updateContent)
|
|
503
|
+
this.$bus.$on('show-transcript', this.openTranscript)
|
|
504
|
+
this.$bus.$on('send-completion-event', this.sendCompletionStatus)
|
|
505
|
+
this.$bus.$on('send-starting-event', this.sendStartStatement)
|
|
506
|
+
|
|
507
|
+
this.initLesson()
|
|
508
|
+
|
|
509
|
+
if (this.navigationHistory.length != 0) {
|
|
510
|
+
this.routeData = this.navigationHistory
|
|
511
|
+
}
|
|
512
|
+
setTimeout(() => {
|
|
513
|
+
this.settingsSelected = { ...this.getApplicationSettings }
|
|
514
|
+
}, 800)
|
|
515
|
+
},
|
|
516
|
+
mounted() {
|
|
517
|
+
//A11Y: Bring back the focus on the body element after each navigation
|
|
518
|
+
document.body.setAttribute('tabindex', -1) //needed to use .focus()
|
|
519
|
+
this.$router.afterEach(() => {
|
|
520
|
+
document.body.focus()
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
let nav = document.getElementById('navTool')
|
|
524
|
+
|
|
525
|
+
if (nav) nav.addEventListener('mouseleave', this.onNavMouseleave)
|
|
526
|
+
|
|
527
|
+
document.addEventListener('keydown', this.handleKeyboardControls)
|
|
528
|
+
},
|
|
529
|
+
methods: {
|
|
530
|
+
...mapActions(useAppStore, [
|
|
531
|
+
'updateIntroStatus',
|
|
532
|
+
'updateCurrentTimeline',
|
|
533
|
+
'updateCurrentMediaElements',
|
|
534
|
+
'updateCurrentPage',
|
|
535
|
+
'updateDataFetchFromServer'
|
|
536
|
+
]),
|
|
537
|
+
onNavMouseleave() {
|
|
538
|
+
let widgetOpen = document.getElementsByClassName('open')
|
|
539
|
+
|
|
540
|
+
if (widgetOpen.length == 0) {
|
|
541
|
+
this.cancelTimeout()
|
|
542
|
+
this.closeDelay = true
|
|
543
|
+
this.startTimeout()
|
|
544
|
+
} else {
|
|
545
|
+
this.closeDelay = true
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
onVideoFullScreen(value) {
|
|
549
|
+
this.videoFull = value
|
|
550
|
+
},
|
|
551
|
+
/* Get All Element related to the media return a list of all DOM element*/
|
|
552
|
+
async getFocusables() {
|
|
553
|
+
return new Promise(function (resolve, reject) {
|
|
554
|
+
setTimeout(function () {
|
|
555
|
+
const getAllFocusables = () => {
|
|
556
|
+
const listItems = document.querySelectorAll('.v-media')
|
|
557
|
+
if (!listItems || !listItems.length) return null
|
|
558
|
+
return Array.from(listItems)
|
|
559
|
+
}
|
|
560
|
+
resolve(getAllFocusables())
|
|
561
|
+
}, 200)
|
|
562
|
+
})
|
|
563
|
+
},
|
|
564
|
+
/**
|
|
565
|
+
* @description Handle Keyboard controls
|
|
566
|
+
* @summary
|
|
567
|
+
* @param {Object} evt - event that fired the action
|
|
568
|
+
*
|
|
569
|
+
*/
|
|
570
|
+
async handleKeyboardControls(evt) {
|
|
571
|
+
let { code } = evt
|
|
572
|
+
|
|
573
|
+
if (code === 'Escape' && this.rightSidebarVisible)
|
|
574
|
+
return (this.rightSidebarVisible = false)
|
|
575
|
+
|
|
576
|
+
//==========================================
|
|
577
|
+
/*const videoRange = await this.getFocusables()
|
|
578
|
+
if (videoRange && videoRange.indexOf(document.activeElement) === -1)
|
|
579
|
+
return
|
|
580
|
+
this.$bus.$emit('play-media', code)*/
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @description open the right sidebar component and show and set it content
|
|
585
|
+
* @summary opens sidebar according to context
|
|
586
|
+
* @param {Object} obj {ctx , e} - context and content to display in the sidebar
|
|
587
|
+
*
|
|
588
|
+
*/
|
|
589
|
+
openSidebar(obj) {
|
|
590
|
+
if (!obj || !obj.ctx || !obj.e) return
|
|
591
|
+
const wrapper = obj.w ? obj.w : null
|
|
592
|
+
this.lastInFocus = document.activeElement
|
|
593
|
+
const { ctx, e: content } = obj
|
|
594
|
+
|
|
595
|
+
switch (ctx) {
|
|
596
|
+
case 'ctxTranscript':
|
|
597
|
+
this.openTranscript(content, wrapper)
|
|
598
|
+
break
|
|
599
|
+
|
|
600
|
+
case 'ctxBranching':
|
|
601
|
+
if (this.compID === content) return this.closeSidebar('ctxBranching')
|
|
602
|
+
|
|
603
|
+
this.openBranchContent(content)
|
|
604
|
+
break
|
|
605
|
+
}
|
|
606
|
+
//delay animation
|
|
607
|
+
this.rightSidebarVisible = true
|
|
608
|
+
setTimeout(() => {
|
|
609
|
+
const rightSidebarContent = this.getRightSidebar() // Emelent displayed in the sidebar-body
|
|
610
|
+
if (!rightSidebarContent) return
|
|
611
|
+
rightSidebarContent.scrollTop = 0
|
|
612
|
+
|
|
613
|
+
const rSidebar = document.querySelector('#right-sidebar') // the sidebar
|
|
614
|
+
|
|
615
|
+
rSidebar.setAttribute('tabindex', -1)
|
|
616
|
+
this.resetFocus(rSidebar) //set focus on the sidebar
|
|
617
|
+
}, 100)
|
|
618
|
+
},
|
|
619
|
+
/**
|
|
620
|
+
* @description close the right sidebar component
|
|
621
|
+
* @summary close sidebar according to context
|
|
622
|
+
* @param {String} ctx - context in which side bar was opened and will now be closed
|
|
623
|
+
*
|
|
624
|
+
*/
|
|
625
|
+
closeSidebar(ctx, wrapper = null) {
|
|
626
|
+
//delay animation
|
|
627
|
+
this.rightSidebarVisible = false // this will allow to run the animation of sidebar closing 1rst
|
|
628
|
+
|
|
629
|
+
this.resetFocus(this.lastInFocus)
|
|
630
|
+
switch (ctx) {
|
|
631
|
+
case 'ctxTranscript':
|
|
632
|
+
this.closeTranscript(wrapper)
|
|
633
|
+
break
|
|
634
|
+
|
|
635
|
+
case 'ctxBranching':
|
|
636
|
+
this.closeBranchContent()
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
default:
|
|
640
|
+
this.branchingVisible = false
|
|
641
|
+
this.transcriptVisible = false
|
|
642
|
+
this.transcriptContent = null
|
|
643
|
+
this.compID = null
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
/**
|
|
647
|
+
* @description to close a pop up (not currently used)
|
|
648
|
+
* @param {Function} [cb]
|
|
649
|
+
* @fires popup-close to AppBasePopup.vue
|
|
650
|
+
*/
|
|
651
|
+
closePopup(cb) {
|
|
652
|
+
this.popupIsOpen = false
|
|
653
|
+
this.$bus.$emit('popup-close', cb)
|
|
654
|
+
},
|
|
655
|
+
/**
|
|
656
|
+
* @description to open a pop up
|
|
657
|
+
* @param {Object} data the content op the popup
|
|
658
|
+
* @fires popup-open to AppBasePopup.vue
|
|
659
|
+
*/
|
|
660
|
+
openPopup(data) {
|
|
661
|
+
this.popupIsOpen = true
|
|
662
|
+
this.$bus.$emit('popup-open', data) // Use to send message to popUp component
|
|
663
|
+
},
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @description to close a popover
|
|
667
|
+
* @fires tooltip-close to AppCompToolTip.vue
|
|
668
|
+
*/
|
|
669
|
+
closeToolTip() {
|
|
670
|
+
this.$bus.$emit('tooltip-close')
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* @description to open a popover
|
|
675
|
+
* @param {Object} data the options of the popover
|
|
676
|
+
* @fires tooltip-open to AppCompToolTip.vue
|
|
677
|
+
*/
|
|
678
|
+
openToolTip(data) {
|
|
679
|
+
this.$bus.$emit('tooltip-open', data) // Use to send message to tooltip component
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* @description Manage opening of sidebar in transcript context
|
|
684
|
+
* @summary set the value of the transcriptContent and transcripVisibility
|
|
685
|
+
* @param {Object} c content to display in the sidebar
|
|
686
|
+
*/
|
|
687
|
+
|
|
688
|
+
openTranscript(c, container) {
|
|
689
|
+
if (!c) return (this.transcriptVisible = false)
|
|
690
|
+
//Change transcript content
|
|
691
|
+
if (this.transcriptContent !== c) this.transcriptContent = c
|
|
692
|
+
|
|
693
|
+
if (this.transcriptContainer && this.transcriptContainer !== container) {
|
|
694
|
+
/*console.log('transcriptContainer est différent de container')
|
|
695
|
+
console.log({ ancien: this.transcriptContainer, nouveau: container })
|
|
696
|
+
console.log('agrandir ancien container et réduire nouveau container')*/
|
|
697
|
+
}
|
|
698
|
+
this.transcriptContainer = container
|
|
699
|
+
this.transcriptVisible = true
|
|
700
|
+
},
|
|
701
|
+
/**
|
|
702
|
+
* @description Manage closing of sidebar in transcript context
|
|
703
|
+
* @summary reset the value of the transcriptContent and transcripVisibility
|
|
704
|
+
* @fires 'transcript-hidden' to AppCompPlaybar
|
|
705
|
+
*/
|
|
706
|
+
closeTranscript(container = this.transcriptContainer) {
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
this.transcriptContent = null
|
|
709
|
+
this.transcriptVisible = false
|
|
710
|
+
}, 300)
|
|
711
|
+
this.$bus.$emit('transcript-hidden')
|
|
712
|
+
this.$bus.$emit('resize-media', 'lg', container)
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* @description Handle the content of the branch page to display in the right sidebar.
|
|
717
|
+
* @summary When call set the value of compID and the branching visibility Attach lister for scroll event in the sidebar
|
|
718
|
+
* @param {Object} branchID - ID OF the Component That to retrieve
|
|
719
|
+
* @fires 'branch-page-viewed' to $PageMixins when scroll in branch page reaches the bottom
|
|
720
|
+
*/
|
|
721
|
+
openBranchContent(branchID) {
|
|
722
|
+
this.branchingVisible = true
|
|
723
|
+
this.compID = branchID //set compenent ID
|
|
724
|
+
|
|
725
|
+
setTimeout(() => {
|
|
726
|
+
const rightSidebar = this.getRightSidebar()
|
|
727
|
+
//Should indicate that page is completed when the Rightsidebar content heigh is less then window height
|
|
728
|
+
if (rightSidebar.scrollHeight <= window.innerHeight) {
|
|
729
|
+
if (!this.getCurrentBranchPage) return
|
|
730
|
+
let { userInteraction } = this.getCurrentBranchPage // Get the current branch page opened in the sidebar
|
|
731
|
+
this.getCurrentBranchPage.state = 'completed' // update the state of this branch
|
|
732
|
+
userInteraction.state = this.getCurrentBranchPage.state //update userInteraction state
|
|
733
|
+
this.$bus.$emit('branch-page-viewed')
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
rightSidebar.addEventListener('scroll', this.handleRightSidebarScroll)
|
|
737
|
+
}, 300)
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* @description Close the branch page in the right sidebar
|
|
742
|
+
* * @summary When call remove listner for scroll event in the sidebar , reset compID and branch visibility
|
|
743
|
+
* * @fires 'branching-hidden' to AppCompButtonProgress
|
|
744
|
+
*/
|
|
745
|
+
closeBranchContent() {
|
|
746
|
+
const rightSidebar = this.getRightSidebar()
|
|
747
|
+
|
|
748
|
+
if (!rightSidebar) return
|
|
749
|
+
|
|
750
|
+
rightSidebar.removeEventListener('scroll', this.handleRightSidebarScroll)
|
|
751
|
+
|
|
752
|
+
setTimeout(() => {
|
|
753
|
+
this.compID = null //reset the comp
|
|
754
|
+
this.branchingVisible = false
|
|
755
|
+
}, 300)
|
|
756
|
+
this.$bus.$emit('branching-hidden')
|
|
757
|
+
},
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @description reset the values of state (currentTimeline,currentMedialement, currentPage, media duration, appStatus')in the store
|
|
761
|
+
*/
|
|
762
|
+
async unload() {
|
|
763
|
+
return new Promise((res) => {
|
|
764
|
+
this.$bus.$emit('set-comp-status', 'appBaseModule', 'loading')
|
|
765
|
+
this.updateCurrentTimeline('')
|
|
766
|
+
this.updateCurrentMediaElements([])
|
|
767
|
+
this.updateCurrentPage({})
|
|
768
|
+
this.$bus.$emit('set-comp-status', 'appBaseModule', 'ready')
|
|
769
|
+
res(this.getCompStatusTracker)
|
|
770
|
+
})
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* @description fetch a page from the store
|
|
775
|
+
*/
|
|
776
|
+
async fetchPage() {
|
|
777
|
+
this.$bus.$emit('set-comp-status', 'appBaseModule', 'loading')
|
|
778
|
+
const fetched = await new Promise((resolve) => {
|
|
779
|
+
setTimeout(() => {
|
|
780
|
+
resolve(this.getCurrentPage)
|
|
781
|
+
this.$bus.$emit('set-comp-status', 'appBaseModule', 'ready')
|
|
782
|
+
}, 200)
|
|
783
|
+
})
|
|
784
|
+
return fetched
|
|
785
|
+
},
|
|
786
|
+
/**
|
|
787
|
+
* @description get User data
|
|
788
|
+
*/
|
|
789
|
+
async fetchUserData() {
|
|
790
|
+
const fetched = await new Promise((resolve) => {
|
|
791
|
+
// get user saved data after 200 milliseconds
|
|
792
|
+
setTimeout(() => {
|
|
793
|
+
resolve(this.getUserInteraction)
|
|
794
|
+
}, 200)
|
|
795
|
+
})
|
|
796
|
+
return fetched
|
|
797
|
+
},
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* @description format data from a page
|
|
801
|
+
* @param {Object} page
|
|
802
|
+
*/
|
|
803
|
+
formateData(page) {
|
|
804
|
+
if (page && page.type === 'pg_media') {
|
|
805
|
+
const {
|
|
806
|
+
id,
|
|
807
|
+
animation,
|
|
808
|
+
type,
|
|
809
|
+
mediaData: { mSources, mType, mSubtitle, mPoster, mTranscript },
|
|
810
|
+
timeline,
|
|
811
|
+
mElement
|
|
812
|
+
} = page
|
|
813
|
+
return {
|
|
814
|
+
id,
|
|
815
|
+
animation,
|
|
816
|
+
type,
|
|
817
|
+
mSources,
|
|
818
|
+
mType,
|
|
819
|
+
mSubtitle,
|
|
820
|
+
timeline,
|
|
821
|
+
mElement,
|
|
822
|
+
mPoster,
|
|
823
|
+
mTranscript
|
|
824
|
+
}
|
|
825
|
+
} else if (page && page.type === 'pg_animation') {
|
|
826
|
+
const { id, animation, type, timeline } = page
|
|
827
|
+
return {
|
|
828
|
+
id,
|
|
829
|
+
animation,
|
|
830
|
+
type,
|
|
831
|
+
timeline
|
|
832
|
+
}
|
|
833
|
+
} else return false
|
|
834
|
+
},
|
|
835
|
+
async initLesson() {
|
|
836
|
+
await this.unload().then(() => {
|
|
837
|
+
const packageType = this.getModuleInfo.packageType
|
|
838
|
+
switch (packageType) {
|
|
839
|
+
case 'scorm': {
|
|
840
|
+
// launching the lesson (if the lesson is not already completed)
|
|
841
|
+
const lessonStatus = this.$scorm.GetValue(
|
|
842
|
+
'cmi.core.lesson_status',
|
|
843
|
+
true
|
|
844
|
+
)
|
|
845
|
+
if (lessonStatus === 'unknown') {
|
|
846
|
+
this.$scorm.setValue('cmi.core.lesson_status', 'incomplete') // set the lesson status to in complete
|
|
847
|
+
this.$scorm.Commit() // persist data
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// check for the bookmark existance in LMS
|
|
851
|
+
const bookmark = this.$scorm.GetValue(
|
|
852
|
+
'cmi.core.lesson_location',
|
|
853
|
+
false
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
// if none stored in the LMS redirect to the 1st page
|
|
857
|
+
if (bookmark === '' || bookmark === undefined) {
|
|
858
|
+
//Redirect to current page or to menu if route is module
|
|
859
|
+
this.$route.name && this.$route.name !== 'module'
|
|
860
|
+
? this.$router.push({ name: this.$route.name })
|
|
861
|
+
: this.$router.push({ name: 'menu' })
|
|
862
|
+
} else if (bookmark) {
|
|
863
|
+
this.$router.push({ name: bookmark })
|
|
864
|
+
}
|
|
865
|
+
// set the current page
|
|
866
|
+
this.fetchPage()
|
|
867
|
+
this.$scorm.getScormAPI()
|
|
868
|
+
break
|
|
869
|
+
}
|
|
870
|
+
case 'xapi':
|
|
871
|
+
{
|
|
872
|
+
this.$route.name && this.$route.name !== 'module'
|
|
873
|
+
? this.$router.push({ name: this.$route.name })
|
|
874
|
+
: this.$router.push({ name: 'menu' })
|
|
875
|
+
|
|
876
|
+
this.fetchPage()
|
|
877
|
+
// }
|
|
878
|
+
}
|
|
879
|
+
break
|
|
880
|
+
}
|
|
881
|
+
})
|
|
882
|
+
},
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* @description save interaction or Module state to scrom suspend_data
|
|
886
|
+
* @param {String} arg [module|userInteraction| null]
|
|
887
|
+
*/
|
|
888
|
+
saveToScorm(arg) {
|
|
889
|
+
arg = arg || null
|
|
890
|
+
let existingRecord // hold record record
|
|
891
|
+
let toBeSaved // hold data to send to scorm
|
|
892
|
+
if (this.$scorm.initialized) {
|
|
893
|
+
if (this.$scorm.GetValue('cmi.suspend_data') !== '')
|
|
894
|
+
existingRecord = JSON.parse(this.$scorm.GetValue('cmi.suspend_data')) // try convert the scorm record to JSON Obiect
|
|
895
|
+
// create entry for user data if there is no record in Scorm
|
|
896
|
+
if (!existingRecord) existingRecord = {}
|
|
897
|
+
// There is a passed arg
|
|
898
|
+
if (arg !== null) {
|
|
899
|
+
// update only the user record
|
|
900
|
+
if (arg === 'userInteraction') {
|
|
901
|
+
// create new entry for user data if does not existe
|
|
902
|
+
if (!existingRecord['userData']) existingRecord['userData'] = {}
|
|
903
|
+
// update value of user data
|
|
904
|
+
if (this.getUserInteraction)
|
|
905
|
+
existingRecord.userData = this.getUserInteraction
|
|
906
|
+
toBeSaved = JSON.stringify(existingRecord) // convert to JSON string format
|
|
907
|
+
}
|
|
908
|
+
// update only the module record
|
|
909
|
+
else if (arg === 'module') {
|
|
910
|
+
// create new entry for module data if does not existe
|
|
911
|
+
if (!existingRecord['moduleData']) existingRecord['moduleData'] = {}
|
|
912
|
+
// update value for module data
|
|
913
|
+
toBeSaved = JSON.stringify(existingRecord) // convert to JSON string format
|
|
914
|
+
}
|
|
915
|
+
// no valid arg
|
|
916
|
+
else return
|
|
917
|
+
}
|
|
918
|
+
// Update module & user record
|
|
919
|
+
else {
|
|
920
|
+
// create entry for module and user data if does not existe
|
|
921
|
+
if (
|
|
922
|
+
!existingRecord['userData'] &&
|
|
923
|
+
!existingRecord['moduleData'] &&
|
|
924
|
+
!existingRecord['routeHistory']
|
|
925
|
+
) {
|
|
926
|
+
existingRecord['moduleData'] = {}
|
|
927
|
+
existingRecord['userData'] = {}
|
|
928
|
+
existingRecord['routeHistory'] = []
|
|
929
|
+
}
|
|
930
|
+
// update values for module and user data
|
|
931
|
+
existingRecord.userData = this.getUserInteraction
|
|
932
|
+
existingRecord.routeHistory = this.routeData
|
|
933
|
+
// update value of user data
|
|
934
|
+
existingRecord.moduleConfig = JSON.stringify({}) // update value of module data
|
|
935
|
+
toBeSaved = JSON.stringify(existingRecord)
|
|
936
|
+
}
|
|
937
|
+
this.$scorm.SetValue('cmi.suspend_data', toBeSaved) // converte to serialized string and save to scorm
|
|
938
|
+
this.$scorm.Commit() // persist data in LMS
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
/**
|
|
942
|
+
* @param {Object} to
|
|
943
|
+
* @param {Object} from unused
|
|
944
|
+
* @param {Function} next
|
|
945
|
+
*/
|
|
946
|
+
updateContent(to, from, next) {
|
|
947
|
+
if (this.getModuleInfo.packageType === 'scorm')
|
|
948
|
+
this.saveToScorm('userInteraction') //save to scorm
|
|
949
|
+
|
|
950
|
+
if (this.popupIsOpen) {
|
|
951
|
+
//close popup and deactivate the popup animation
|
|
952
|
+
this.closePopup({ animationOff: true })
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (this.rightSidebarVisible) this.closeSidebar()
|
|
956
|
+
|
|
957
|
+
this.unload().then((res) => {
|
|
958
|
+
if (res.length) {
|
|
959
|
+
//======== Handle redirect to correct path when user enter incorrect path for existing route ====//
|
|
960
|
+
let toName = null
|
|
961
|
+
if (!to.name) {
|
|
962
|
+
toName = to.fullPath.replace(/\//g, ' ').trim() //clean route name
|
|
963
|
+
|
|
964
|
+
//apply apply correct format for routes that concerne an activity
|
|
965
|
+
if (toName.includes('activite-')) {
|
|
966
|
+
toName = toName.split(' ')[0].replace('-', '_')
|
|
967
|
+
}
|
|
968
|
+
//Go to menu when introactive is false other wise will navigate to introduction
|
|
969
|
+
else if (
|
|
970
|
+
toName.includes('introduction') &&
|
|
971
|
+
!this.theIntroIsActivated
|
|
972
|
+
)
|
|
973
|
+
toName = 'menu'
|
|
974
|
+
}
|
|
975
|
+
//When User inter path to module
|
|
976
|
+
else if (to.name === 'module') {
|
|
977
|
+
toName = 'menu'
|
|
978
|
+
}
|
|
979
|
+
//Default redirection
|
|
980
|
+
else {
|
|
981
|
+
false
|
|
982
|
+
// next()
|
|
983
|
+
}
|
|
984
|
+
//===================== Handeling a page request from Store=========================== ====//
|
|
985
|
+
//get the page from store
|
|
986
|
+
this.fetchPage().then((res) => {
|
|
987
|
+
//Save current page has bookmark in scorm
|
|
988
|
+
if (
|
|
989
|
+
this.getModuleInfo.packageType === 'scorm' &&
|
|
990
|
+
this.$scorm.initialized
|
|
991
|
+
)
|
|
992
|
+
this.$scorm.setBookMark(this.$route.name)
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
})
|
|
996
|
+
},
|
|
997
|
+
|
|
998
|
+
updateRouteHistory(from) {
|
|
999
|
+
//The route is not in the history: should be push at the end of the history
|
|
1000
|
+
const targetIndex = this.routeData.findIndex((r) => {
|
|
1001
|
+
if (r.type === 'pg_menu' && r.id === from.meta.id) return r
|
|
1002
|
+
else if (
|
|
1003
|
+
`${r.activity_ref}_${r.id}` ===
|
|
1004
|
+
`${from.meta.activity_ref}_${from.meta.id}`
|
|
1005
|
+
)
|
|
1006
|
+
return r
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
//Remove route from history if already in
|
|
1010
|
+
if (targetIndex !== -1) this.routeData.splice(targetIndex, 1)
|
|
1011
|
+
|
|
1012
|
+
// Add route route in history
|
|
1013
|
+
this.routeData.push(from.meta)
|
|
1014
|
+
|
|
1015
|
+
if (this.routeData.length >= 4) this.routeData.shift()
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* @description Helper fonction to launch extra ressource from this activity. Extra ressource is another lesson.
|
|
1020
|
+
* @param {Object} res - Data of the ressource to launch
|
|
1021
|
+
* @param {String} res.url
|
|
1022
|
+
* @param {String} res.id
|
|
1023
|
+
*/
|
|
1024
|
+
launchResource(res) {
|
|
1025
|
+
const wrapper = this.$xapi.XAPIWrapper
|
|
1026
|
+
const baseDomain = window.location.origin
|
|
1027
|
+
|
|
1028
|
+
let { endpoint, auth, registration } = wrapper.lrs
|
|
1029
|
+
let { actor, remote } = this.getConnectionInfo
|
|
1030
|
+
|
|
1031
|
+
if (!actor || !remote) return
|
|
1032
|
+
|
|
1033
|
+
actor = encodeURIComponent(JSON.stringify(actor))
|
|
1034
|
+
endpoint = encodeURIComponent(endpoint)
|
|
1035
|
+
registration = encodeURIComponent(registration)
|
|
1036
|
+
auth = encodeURIComponent(auth)
|
|
1037
|
+
|
|
1038
|
+
const activity_id = encodeURIComponent(res.id)
|
|
1039
|
+
|
|
1040
|
+
if (!res.url.includes(baseDomain) && remote)
|
|
1041
|
+
res.url = `${baseDomain}/${res.url}`
|
|
1042
|
+
|
|
1043
|
+
const newUrlToLaunch = `${res.url}?endpoint=${endpoint}&auth=${auth}&actor=${actor}®istration=${registration}&activity_id=${activity_id}`
|
|
1044
|
+
this.routeChangeCounter = 0 //reset counter after saving
|
|
1045
|
+
this.$bus.$emit('fire-exit-event', () => {
|
|
1046
|
+
window.location.replace(newUrlToLaunch)
|
|
1047
|
+
})
|
|
1048
|
+
},
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* @description start the tutorial for first time users
|
|
1052
|
+
* @requires '../assets/data/onboardingMessages.json'
|
|
1053
|
+
* @param {Object} [onboardingMessages] the object for the messages of the tutorial
|
|
1054
|
+
*/
|
|
1055
|
+
startOnboarding(onboardingMessages) {
|
|
1056
|
+
if (onboardingMessages) {
|
|
1057
|
+
if (this.getOnboardingEnabled) {
|
|
1058
|
+
//flags error for OnboardingMessages
|
|
1059
|
+
this.validateOnboardingMessages(onboardingMessages)
|
|
1060
|
+
try {
|
|
1061
|
+
this.onboardingMessages = onboardingMessages
|
|
1062
|
+
this.nextOnboarding('message_1')
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
//fetch default values for popups
|
|
1065
|
+
this.onboardingMessages = import(
|
|
1066
|
+
'../assets/data/onboardingMessages.json'
|
|
1067
|
+
)
|
|
1068
|
+
this.nextOnboarding('message_1')
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
if (this.getOnboardingEnabled) {
|
|
1073
|
+
this.onboardingMessages = import(
|
|
1074
|
+
'../assets/data/onboardingMessages.json'
|
|
1075
|
+
)
|
|
1076
|
+
this.nextOnboarding('message_1')
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* @description start the tutorial for first time users
|
|
1083
|
+
* @param {String} nextMessage name of the following messsage
|
|
1084
|
+
*/
|
|
1085
|
+
nextOnboarding(nextMessage) {
|
|
1086
|
+
let messages = this.onboardingMessages
|
|
1087
|
+
//create message_X variable to put in cb_$confirm
|
|
1088
|
+
let postNextMessage = 'message_' + (parseInt(nextMessage.slice(8)) + 1)
|
|
1089
|
+
|
|
1090
|
+
if (messages[postNextMessage]) {
|
|
1091
|
+
//add something to the cb_$confirm to nextOnboarding
|
|
1092
|
+
messages[nextMessage].value.cb_$confirm = () => {
|
|
1093
|
+
this.nextOnboarding(postNextMessage)
|
|
1094
|
+
}
|
|
1095
|
+
//to apply the target to tooltip before the mounted happens
|
|
1096
|
+
if (messages[postNextMessage].type == 'tooltip') {
|
|
1097
|
+
this.toolTipTarget = messages[postNextMessage].value.target
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
//check if the next message is a popup or other componant
|
|
1101
|
+
if (messages[nextMessage].type == 'popup-avert') {
|
|
1102
|
+
this.openPopup(messages[nextMessage])
|
|
1103
|
+
} else if (messages[nextMessage].type == 'tooltip') {
|
|
1104
|
+
this.openToolTip(messages[nextMessage].value)
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
/**
|
|
1108
|
+
* @description- Skip directly to the main containt*
|
|
1109
|
+
* main content is Node with page ID by default
|
|
1110
|
+
* Main Content can be defined by front-end using prop "main" in Module.vue of project
|
|
1111
|
+
* @fire {event} 'move-to-target' to AppBase
|
|
1112
|
+
*/
|
|
1113
|
+
|
|
1114
|
+
skipToMain() {
|
|
1115
|
+
const { main } = this.moduleConfig // check for main input
|
|
1116
|
+
|
|
1117
|
+
let skipTo = main ? main : 'wrapper-content' // search for node element specified as main
|
|
1118
|
+
|
|
1119
|
+
//fires event
|
|
1120
|
+
this.$bus.$emit('move-to-target', skipTo, {
|
|
1121
|
+
top: 100, // offset target top to 100 pixel
|
|
1122
|
+
left: 0,
|
|
1123
|
+
behavior: 'auto'
|
|
1124
|
+
})
|
|
1125
|
+
},
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* @description validate the OnboardingMessages
|
|
1129
|
+
* @param {Object} onboardingMessages
|
|
1130
|
+
* @returns {Boolean} false if valid onboardingMessages || true if invalid onboardingMessages
|
|
1131
|
+
*/
|
|
1132
|
+
validateOnboardingMessages(onboardingMessages) {
|
|
1133
|
+
let err = false
|
|
1134
|
+
let errorList = [] //array for errors dectected
|
|
1135
|
+
if (onboardingMessages && typeof onboardingMessages == 'object') {
|
|
1136
|
+
//continue
|
|
1137
|
+
//check the list of attributes is message_X
|
|
1138
|
+
let listMessage = Object.keys(onboardingMessages)
|
|
1139
|
+
for (let index = 1; index <= listMessage.length; index++) {
|
|
1140
|
+
const element = listMessage[index - 1]
|
|
1141
|
+
if (element == 'message_' + index) {
|
|
1142
|
+
//continu
|
|
1143
|
+
if (
|
|
1144
|
+
onboardingMessages[element] &&
|
|
1145
|
+
typeof onboardingMessages[element] == 'object'
|
|
1146
|
+
) {
|
|
1147
|
+
if (
|
|
1148
|
+
onboardingMessages[element].type &&
|
|
1149
|
+
typeof onboardingMessages[element].type == 'string'
|
|
1150
|
+
) {
|
|
1151
|
+
//checks if is tooltip
|
|
1152
|
+
if (onboardingMessages[element].type == 'tooltip') {
|
|
1153
|
+
let tooltip = onboardingMessages[element]
|
|
1154
|
+
if (tooltip.value && typeof tooltip.value == 'object') {
|
|
1155
|
+
//check title
|
|
1156
|
+
if (
|
|
1157
|
+
!tooltip.value.title ||
|
|
1158
|
+
typeof tooltip.value.title !== 'string'
|
|
1159
|
+
) {
|
|
1160
|
+
//flags error
|
|
1161
|
+
errorList.push('message_' + index + ' type value title')
|
|
1162
|
+
console.warn(
|
|
1163
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1164
|
+
index +
|
|
1165
|
+
' type value title is incorrect',
|
|
1166
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1167
|
+
)
|
|
1168
|
+
}
|
|
1169
|
+
//check content
|
|
1170
|
+
if (
|
|
1171
|
+
!tooltip.value.content ||
|
|
1172
|
+
typeof tooltip.value.content !== 'string'
|
|
1173
|
+
) {
|
|
1174
|
+
//flags error
|
|
1175
|
+
errorList.push('message_' + index + ' type value content')
|
|
1176
|
+
console.warn(
|
|
1177
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1178
|
+
index +
|
|
1179
|
+
' type value content is incorrect',
|
|
1180
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1181
|
+
)
|
|
1182
|
+
}
|
|
1183
|
+
//check target
|
|
1184
|
+
if (
|
|
1185
|
+
tooltip.value.target &&
|
|
1186
|
+
typeof tooltip.value.target == 'string'
|
|
1187
|
+
) {
|
|
1188
|
+
//check if target is exist
|
|
1189
|
+
let target = tooltip.value.target
|
|
1190
|
+
//@todo
|
|
1191
|
+
if (!document.getElementById(target)) {
|
|
1192
|
+
//flags error
|
|
1193
|
+
errorList.push(
|
|
1194
|
+
'message_' + index + ' type value target'
|
|
1195
|
+
)
|
|
1196
|
+
console.warn(
|
|
1197
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1198
|
+
index +
|
|
1199
|
+
' type value target is not found in page',
|
|
1200
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1201
|
+
)
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
//flags error
|
|
1205
|
+
errorList.push('message_' + index + ' type value target')
|
|
1206
|
+
console.warn(
|
|
1207
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1208
|
+
index +
|
|
1209
|
+
' type value target is incorrect',
|
|
1210
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1211
|
+
)
|
|
1212
|
+
}
|
|
1213
|
+
} else {
|
|
1214
|
+
//flags error
|
|
1215
|
+
errorList.push('message_' + index + ' type value')
|
|
1216
|
+
console.warn(
|
|
1217
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1218
|
+
index +
|
|
1219
|
+
' type value is incorrect',
|
|
1220
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1221
|
+
)
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
//flags error
|
|
1226
|
+
errorList.push('message_' + index + ' type')
|
|
1227
|
+
console.warn(
|
|
1228
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1229
|
+
index +
|
|
1230
|
+
' type is missing',
|
|
1231
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1232
|
+
)
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
//flags error
|
|
1236
|
+
errorList.push('message_' + index + ' typeof')
|
|
1237
|
+
console.warn(
|
|
1238
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages message_' +
|
|
1239
|
+
index +
|
|
1240
|
+
' is not an object',
|
|
1241
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1242
|
+
)
|
|
1243
|
+
}
|
|
1244
|
+
} else {
|
|
1245
|
+
//flags error
|
|
1246
|
+
errorList.push('onboardingMessages keys')
|
|
1247
|
+
console.warn(
|
|
1248
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages keys does not follow "message_[number]" naming',
|
|
1249
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1250
|
+
)
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
} else {
|
|
1254
|
+
//flags error
|
|
1255
|
+
errorList.push('onboardingMessages')
|
|
1256
|
+
console.warn(
|
|
1257
|
+
'%c WARNING!>>> appBaseModule: Your onboardingMessages is not an Object',
|
|
1258
|
+
'background: orange; color: white; display: block; margin:5px;'
|
|
1259
|
+
)
|
|
1260
|
+
}
|
|
1261
|
+
// all item have required field. There is no error
|
|
1262
|
+
if (!errorList.length) err = false
|
|
1263
|
+
//return list of error
|
|
1264
|
+
else err = errorList
|
|
1265
|
+
|
|
1266
|
+
return err
|
|
1267
|
+
},
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* @description Reset the focus the element
|
|
1271
|
+
* @param {HTMLElement} e - element that will get focus
|
|
1272
|
+
*/
|
|
1273
|
+
resetFocus(e) {
|
|
1274
|
+
if (e) e.focus()
|
|
1275
|
+
},
|
|
1276
|
+
|
|
1277
|
+
handleRightSidebarScroll(event) {
|
|
1278
|
+
let scrollHeight = null
|
|
1279
|
+
let clientHeight = null
|
|
1280
|
+
let scrollTop = null
|
|
1281
|
+
|
|
1282
|
+
scrollHeight = event.target.scrollHeight
|
|
1283
|
+
clientHeight = event.target.clientHeight
|
|
1284
|
+
scrollTop = event.target.scrollTop
|
|
1285
|
+
|
|
1286
|
+
// //Set scroll limit reached at 150px above the document height.
|
|
1287
|
+
let scrollLimit = scrollHeight - 150
|
|
1288
|
+
let fullyScrolled = Math.round(clientHeight + scrollTop)
|
|
1289
|
+
|
|
1290
|
+
//consider page completed when scrolled value has reached or passed set limit
|
|
1291
|
+
if (fullyScrolled >= scrollLimit) {
|
|
1292
|
+
event.target.removeEventListener(
|
|
1293
|
+
'scroll',
|
|
1294
|
+
this.handleRightSidebarScroll
|
|
1295
|
+
)
|
|
1296
|
+
let { userInteraction } = this.getCurrentBranchPage // Get the current branch page opened in the sidebar
|
|
1297
|
+
|
|
1298
|
+
this.getCurrentBranchPage.state = 'completed' // update the state of this branch
|
|
1299
|
+
userInteraction.state = this.getCurrentBranchPage.state //update userInteraction state
|
|
1300
|
+
|
|
1301
|
+
setTimeout(() => this.$bus.$emit('branch-page-viewed'), 20)
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
getRightSidebar() {
|
|
1305
|
+
let rSidebar = null
|
|
1306
|
+
rSidebar = document.getElementById('right-sidebar')
|
|
1307
|
+
if (!rSidebar) return
|
|
1308
|
+
const rSidebarContent = rSidebar.querySelector(`#right-sidebar-body`)
|
|
1309
|
+
return rSidebarContent
|
|
1310
|
+
},
|
|
1311
|
+
|
|
1312
|
+
startTimeout() {
|
|
1313
|
+
this.timeOut = setTimeout(() => {
|
|
1314
|
+
this.cancelTimeout()
|
|
1315
|
+
}, 3000)
|
|
1316
|
+
},
|
|
1317
|
+
cancelTimeout() {
|
|
1318
|
+
if (this.timeOut) {
|
|
1319
|
+
this.closeDelay = false
|
|
1320
|
+
clearTimeout(this.timeOut)
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* @description Method to handle to sanding of completion status to LRS
|
|
1326
|
+
* determine wether to send a completion for an activity or the Lesson
|
|
1327
|
+
* Completion is sent once
|
|
1328
|
+
* @param {String} context - 'ACTIVITY | LESSON'
|
|
1329
|
+
*/
|
|
1330
|
+
|
|
1331
|
+
sendCompletionStatus(context = null) {
|
|
1332
|
+
if (
|
|
1333
|
+
!this.getModuleInfo.packageType === 'xapi' ||
|
|
1334
|
+
!this.getConnectionInfo ||
|
|
1335
|
+
!this.getConnectionInfo.actor ||
|
|
1336
|
+
!this.getConnectionInfo.remote ||
|
|
1337
|
+
!this.getDataFromServer ||
|
|
1338
|
+
!context
|
|
1339
|
+
)
|
|
1340
|
+
return
|
|
1341
|
+
|
|
1342
|
+
let stmt = null
|
|
1343
|
+
|
|
1344
|
+
switch (context) {
|
|
1345
|
+
case 'ACTIVITY': {
|
|
1346
|
+
let text
|
|
1347
|
+
let completedSize = 0
|
|
1348
|
+
Object.entries(this.getAllCompleted).forEach((value) => {
|
|
1349
|
+
completedSize = completedSize + value[1].length
|
|
1350
|
+
})
|
|
1351
|
+
let recordInServerLength,
|
|
1352
|
+
thisActivityProgressLength,
|
|
1353
|
+
thisActivityLength
|
|
1354
|
+
|
|
1355
|
+
const { userProgress } = this.getDataFromServer
|
|
1356
|
+
const thisActivityServerState =
|
|
1357
|
+
userProgress[this.$route.meta.activity_ref]
|
|
1358
|
+
|
|
1359
|
+
if (thisActivityServerState)
|
|
1360
|
+
recordInServerLength = Object.keys(thisActivityServerState).length
|
|
1361
|
+
|
|
1362
|
+
thisActivityProgressLength = this.getAllCompleted[
|
|
1363
|
+
this.$route.meta.activity_ref
|
|
1364
|
+
]
|
|
1365
|
+
? this.getAllCompleted[this.$route.meta.activity_ref].length
|
|
1366
|
+
: 0
|
|
1367
|
+
|
|
1368
|
+
//get The total length of the current activity
|
|
1369
|
+
thisActivityLength = this.getAllActivities(
|
|
1370
|
+
this.getCurrentPage.activityRef
|
|
1371
|
+
).pageSize
|
|
1372
|
+
|
|
1373
|
+
if (thisActivityProgressLength !== thisActivityLength) return
|
|
1374
|
+
if (thisActivityProgressLength == recordInServerLength) return
|
|
1375
|
+
|
|
1376
|
+
//Defining the text to display for stmt description and definition
|
|
1377
|
+
const id = this.getCurrentPage.activityRef
|
|
1378
|
+
let aName = ''
|
|
1379
|
+
switch (true) {
|
|
1380
|
+
case id == 'A00':
|
|
1381
|
+
aName = 'Introduction'
|
|
1382
|
+
break
|
|
1383
|
+
case id == 'A99':
|
|
1384
|
+
aName = 'Conclusion'
|
|
1385
|
+
break
|
|
1386
|
+
|
|
1387
|
+
default: {
|
|
1388
|
+
let d = id.replace('A', '').trim()
|
|
1389
|
+
d = parseInt(d)
|
|
1390
|
+
aName = `${this.$t('text.activity')} ${d}`
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
switch (this.$i18n.locale) {
|
|
1395
|
+
case 'fr':
|
|
1396
|
+
if (this.getModuleInfo.courseID)
|
|
1397
|
+
text = `${aName} de ${this.getModuleInfo.id} `
|
|
1398
|
+
else text = `Le ${this.getModuleInfo.id}`
|
|
1399
|
+
break
|
|
1400
|
+
case 'en':
|
|
1401
|
+
if (this.getModuleInfo.courseID)
|
|
1402
|
+
text = `${aName} of ${this.getModuleInfo.id}`
|
|
1403
|
+
else text = `The ${this.getModuleInfo.id}`
|
|
1404
|
+
break
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Retrive only user data in lessons front its interaction that we want to send to the LRS.
|
|
1408
|
+
// Note: User Settings are sent on a different URI and statement
|
|
1409
|
+
const { isFistTime, userSettings, ...lessonsData } =
|
|
1410
|
+
this.getUserInteraction
|
|
1411
|
+
|
|
1412
|
+
stmt = {
|
|
1413
|
+
id: this.getCurrentPage.activityRef,
|
|
1414
|
+
verb: 'completed',
|
|
1415
|
+
definition: text,
|
|
1416
|
+
description: text,
|
|
1417
|
+
extensions: [
|
|
1418
|
+
{
|
|
1419
|
+
id: 'user-data',
|
|
1420
|
+
content: {
|
|
1421
|
+
routeHistory: this.getRouteHistory,
|
|
1422
|
+
...lessonsData
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
],
|
|
1426
|
+
duration: this.activityDuration,
|
|
1427
|
+
completion: true
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
break
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
case 'LESSON': {
|
|
1434
|
+
//======================================================
|
|
1435
|
+
let completedSize = 0
|
|
1436
|
+
Object.entries(this.getAllCompleted).forEach((value) => {
|
|
1437
|
+
completedSize = completedSize + value[1].length
|
|
1438
|
+
})
|
|
1439
|
+
|
|
1440
|
+
if (completedSize === this.getAllActivities().pageSize) {
|
|
1441
|
+
let text
|
|
1442
|
+
//Defining the text to display for stmt description and definition
|
|
1443
|
+
switch (this.$i18n.locale) {
|
|
1444
|
+
case 'fr':
|
|
1445
|
+
if (this.getModuleInfo.courseID)
|
|
1446
|
+
text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
|
|
1447
|
+
else text = `Le ${this.getModuleInfo.id}`
|
|
1448
|
+
break
|
|
1449
|
+
case 'en':
|
|
1450
|
+
if (this.getModuleInfo.courseID)
|
|
1451
|
+
text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
|
|
1452
|
+
else text = `The ${this.getModuleInfo.id}`
|
|
1453
|
+
break
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Retrive only user data in lessons front its interaction that we want to send to the LRS.
|
|
1457
|
+
// Note: User Settings are sent on a different URI and statement
|
|
1458
|
+
const { isFistTime, userSettings, ...lessonsData } =
|
|
1459
|
+
this.getUserInteraction
|
|
1460
|
+
|
|
1461
|
+
stmt = {
|
|
1462
|
+
verb: 'completed',
|
|
1463
|
+
definition: text,
|
|
1464
|
+
description: text,
|
|
1465
|
+
extensions: [
|
|
1466
|
+
{
|
|
1467
|
+
id: 'user-data',
|
|
1468
|
+
content: {
|
|
1469
|
+
routeHistory: this.getRouteHistory,
|
|
1470
|
+
...lessonsData
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
],
|
|
1474
|
+
duration: this.lessonDuration,
|
|
1475
|
+
completion: true
|
|
1476
|
+
}
|
|
1477
|
+
this.lessonCompletionStatus = true // set the status of this lesson as completed
|
|
1478
|
+
let completedState = {
|
|
1479
|
+
duration: this.lessonDuration,
|
|
1480
|
+
completion: this.lessonCompletionStatus
|
|
1481
|
+
}
|
|
1482
|
+
this.updateDataFetchFromServer({
|
|
1483
|
+
completedState
|
|
1484
|
+
})
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
break
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (!stmt) return
|
|
1491
|
+
|
|
1492
|
+
this.$bus.$emit('send-xapi-statement', stmt)
|
|
1493
|
+
},
|
|
1494
|
+
/**
|
|
1495
|
+
* @description Method handle start event of activity to LRS
|
|
1496
|
+
* Check if the activity is already initiated in The LRS record to determine wether it a start or a resume
|
|
1497
|
+
* @param {Object} a - Data of the activity
|
|
1498
|
+
* @fires 'send-xapi-statement' to APPBASE
|
|
1499
|
+
*/
|
|
1500
|
+
|
|
1501
|
+
sendStartStatement(a) {
|
|
1502
|
+
if (!a || !this.getDataFromServer) return
|
|
1503
|
+
const { id } = a
|
|
1504
|
+
let aName = null
|
|
1505
|
+
|
|
1506
|
+
switch (true) {
|
|
1507
|
+
case id == 'A00':
|
|
1508
|
+
aName = 'Introduction'
|
|
1509
|
+
break
|
|
1510
|
+
case id == 'A99':
|
|
1511
|
+
aName = 'Conclusion'
|
|
1512
|
+
break
|
|
1513
|
+
|
|
1514
|
+
default: {
|
|
1515
|
+
let d = id.replace('A', '').trim()
|
|
1516
|
+
d = parseInt(d)
|
|
1517
|
+
aName = `${this.$t('text.activity')} ${d}`
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
let text =
|
|
1522
|
+
this.$i18n.locale == 'fr'
|
|
1523
|
+
? `${aName} de ${this.getModuleInfo.id}`
|
|
1524
|
+
: `${aName} of ${this.getModuleInfo.id}`
|
|
1525
|
+
|
|
1526
|
+
/*
|
|
1527
|
+
*Determine if activity as been initialized. Activity is initialized when it is in the serverRecords
|
|
1528
|
+
*/
|
|
1529
|
+
const { userProgress } = this.getDataFromServer
|
|
1530
|
+
const thisActivityServerState = userProgress[id] ? userProgress[id] : {}
|
|
1531
|
+
const recordInServerLength = Object.keys(thisActivityServerState).length
|
|
1532
|
+
|
|
1533
|
+
const stmt = {
|
|
1534
|
+
id,
|
|
1535
|
+
verb: recordInServerLength ? 'resumed' : 'initialized', //determine verb of the statement to send
|
|
1536
|
+
definition: text,
|
|
1537
|
+
description: text
|
|
1538
|
+
}
|
|
1539
|
+
setTimeout(() => this.$bus.$emit('send-xapi-statement', stmt), 500)
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
</script>
|
|
1544
|
+
<style lang="scss">
|
|
1545
|
+
.module {
|
|
1546
|
+
width: 100%;
|
|
1547
|
+
height: 100%;
|
|
1548
|
+
min-height: 100vh;
|
|
1549
|
+
position: relative;
|
|
1550
|
+
display: flex;
|
|
1551
|
+
flex-direction: row;
|
|
1552
|
+
align-items: stretch;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
.skip-link {
|
|
1556
|
+
position: absolute;
|
|
1557
|
+
left: -999px;
|
|
1558
|
+
top: auto;
|
|
1559
|
+
width: 1px;
|
|
1560
|
+
height: 1px;
|
|
1561
|
+
overflow: hidden;
|
|
1562
|
+
z-index: -999;
|
|
1563
|
+
|
|
1564
|
+
&:focus,
|
|
1565
|
+
&:active {
|
|
1566
|
+
left: auto;
|
|
1567
|
+
top: auto;
|
|
1568
|
+
width: 30%;
|
|
1569
|
+
height: auto;
|
|
1570
|
+
overflow: auto;
|
|
1571
|
+
margin: 10px 35%;
|
|
1572
|
+
padding: 5px;
|
|
1573
|
+
text-align: center;
|
|
1574
|
+
font-size: 1.2em;
|
|
1575
|
+
z-index: 999;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/********** FM **********/
|
|
1579
|
+
#Loading {
|
|
1580
|
+
width: 100%;
|
|
1581
|
+
position: fixed;
|
|
1582
|
+
z-index: 99999;
|
|
1583
|
+
left: 0;
|
|
1584
|
+
opacity: 0;
|
|
1585
|
+
display: none;
|
|
1586
|
+
transition: opacity 0.5s;
|
|
1587
|
+
pointer-events: none;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
#right-sidebar {
|
|
1591
|
+
z-index: 10;
|
|
1592
|
+
display: flex;
|
|
1593
|
+
flex-direction: column;
|
|
1594
|
+
flex-wrap: nowrap;
|
|
1595
|
+
justify-content: flex-start;
|
|
1596
|
+
max-width: 780px;
|
|
1597
|
+
width: 30%;
|
|
1598
|
+
height: 100%;
|
|
1599
|
+
overflow: hidden;
|
|
1600
|
+
position: fixed;
|
|
1601
|
+
right: 0;
|
|
1602
|
+
top: 0;
|
|
1603
|
+
background-color: #ffffff;
|
|
1604
|
+
-webkit-box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
|
|
1605
|
+
box-shadow: -2px 1px 6px -1px rgb(0 0 0 / 40%);
|
|
1606
|
+
|
|
1607
|
+
#right-sidebar-header {
|
|
1608
|
+
display: flex;
|
|
1609
|
+
flex-direction: column;
|
|
1610
|
+
align-items: flex-end;
|
|
1611
|
+
padding: 32px 20px 0 32px;
|
|
1612
|
+
|
|
1613
|
+
.embranchement-close {
|
|
1614
|
+
padding: 11px 13px;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
#right-sidebar-body {
|
|
1619
|
+
max-height: 90%;
|
|
1620
|
+
overflow-y: auto;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.right-sidebar-transition-enter-active {
|
|
1625
|
+
transition: transform 0.33s ease-out;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.right-sidebar-transition-leave-active {
|
|
1629
|
+
transition: transform 0.16s;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.right-sidebar-transition-enter-from,
|
|
1633
|
+
.right-sidebar-transition-leave-to {
|
|
1634
|
+
transform: translateX(100%);
|
|
1635
|
+
}
|
|
1636
|
+
</style>
|