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