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