fcad-core-dragon 2.0.0-beta.1 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +33 -33
- package/.eslintignore +29 -29
- package/{.eslintrc.js → .eslintrc.cjs} +81 -86
- package/CHANGELOG +364 -364
- package/README.md +71 -71
- package/bk.scss +117 -0
- package/package.json +61 -63
- package/src/$locales/en.json +143 -179
- package/src/$locales/fr.json +105 -181
- package/src/assets/data/onboardingMessages.json +47 -47
- package/src/components/AppBase.vue +1054 -614
- package/src/components/AppBaseButton.vue +87 -63
- package/src/components/AppBaseErrorDisplay.vue +438 -420
- package/src/components/AppBaseFlipCard.vue +84 -83
- package/src/components/AppBaseModule.vue +1673 -1842
- package/src/components/AppBasePage.vue +779 -312
- package/src/components/AppBasePopover.vue +41 -0
- package/src/components/AppCompAudio.vue +234 -0
- package/src/components/AppCompBranchButtons.vue +552 -582
- package/src/components/AppCompButtonProgress.vue +126 -147
- package/src/components/AppCompCarousel.vue +298 -192
- package/src/components/AppCompInputCheckBoxNext.vue +195 -0
- package/src/components/AppCompInputDropdownNext.vue +159 -0
- package/src/components/AppCompInputRadioNext.vue +152 -0
- package/src/components/{AppCompInputTextBox.vue → AppCompInputTextNext.vue} +106 -91
- package/src/components/AppCompInputTextTableNext.vue +141 -0
- package/src/components/AppCompInputTextToFillDropdownNext.vue +230 -0
- package/src/components/{AppCompInputTextToFillText.vue → AppCompInputTextToFillNext.vue} +171 -164
- package/src/components/AppCompJauge.vue +74 -55
- package/src/components/AppCompMenu.vue +413 -209
- package/src/components/AppCompMenuItem.vue +228 -174
- package/src/components/AppCompNavigation.vue +960 -949
- package/src/components/AppCompNoteCall.vue +133 -126
- package/src/components/AppCompNoteCredit.vue +292 -164
- package/src/components/AppCompPlayBar.vue +1218 -1319
- package/src/components/AppCompPlayBarNext.vue +2052 -0
- package/src/components/AppCompPlayBarProgress.vue +82 -0
- package/src/components/AppCompPopUpNext.vue +503 -0
- package/src/components/{AppCompQuiz.vue → AppCompQuizNext.vue} +2904 -2989
- package/src/components/AppCompQuizRecall.vue +276 -250
- package/src/components/AppCompSVGNext.vue +347 -0
- package/src/components/AppCompSettingsMenu.vue +172 -171
- package/src/components/AppCompTableOfContent.vue +387 -264
- package/src/components/AppCompTranscript.vue +24 -19
- package/src/components/AppCompVideoPlayer.vue +368 -336
- package/src/components/AppCompViewDisplay.vue +6 -6
- package/src/components/BaseModule.vue +72 -67
- package/src/composables/useQuiz.js +206 -0
- package/src/externalComps/ModuleView.vue +22 -0
- package/src/externalComps/SummaryView.vue +91 -0
- package/src/main.js +272 -227
- package/src/mixins/$mediaMixins.js +819 -0
- package/src/mixins/timerMixin.js +155 -156
- package/src/module/stores/appStore.js +893 -0
- package/src/module/xapi/ADL.js +376 -339
- package/src/module/xapi/Crypto/Hasher.js +241 -241
- package/src/module/xapi/Crypto/WordArray.js +278 -278
- package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
- package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -319
- package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
- package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
- package/src/module/xapi/Crypto/encoders/Base.js +105 -105
- package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
- package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
- package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
- package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
- package/src/module/xapi/Crypto/index.js +53 -53
- package/src/module/xapi/Statement/activity.js +47 -47
- package/src/module/xapi/Statement/agent.js +55 -55
- package/src/module/xapi/Statement/group.js +26 -26
- package/src/module/xapi/Statement/index.js +259 -259
- package/src/module/xapi/Statement/statement.js +253 -253
- package/src/module/xapi/Statement/statementRef.js +23 -23
- package/src/module/xapi/Statement/substatement.js +22 -22
- package/src/module/xapi/Statement/verb.js +36 -36
- package/src/module/xapi/activitytypes.js +17 -17
- package/src/module/xapi/launch.js +157 -157
- package/src/module/xapi/utils.js +167 -167
- package/src/module/xapi/verbs.js +294 -294
- package/src/module/xapi/wrapper.js +1963 -1890
- package/src/module/xapi/xapiStatement.js +444 -444
- package/src/plugins/bus.js +8 -3
- package/src/plugins/gsap.js +14 -17
- package/src/plugins/helper.js +308 -295
- package/src/plugins/i18n.js +44 -31
- package/src/plugins/idb.js +219 -212
- package/src/plugins/save.js +37 -37
- package/src/plugins/scorm.js +287 -287
- package/src/plugins/xapi.js +11 -11
- package/src/public/index.html +33 -21
- package/src/router/index.js +43 -41
- package/src/router/routes.js +312 -337
- package/src/shared/generalfuncs.js +210 -188
- package/src/shared/validators.js +1069 -249
- package/vite.config.js +27 -0
- 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/AppCompBif.vue +0 -120
- 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/AppCompInputTextTable.vue +0 -155
- package/src/components/AppCompInputTextToFillDropdown.vue +0 -255
- package/src/components/AppCompMediaPlayer.vue +0 -397
- package/src/components/AppCompPopUp.vue +0 -522
- package/src/components/AppCompPopover.vue +0 -27
- package/src/components/AppCompSVG.vue +0 -309
- package/src/mixins/$pageMixins.js +0 -459
- package/src/mixins/$quizMixins.js +0 -456
- package/src/module/store.js +0 -895
- package/src/plugins/timeManager.js +0 -77
- package/src/routes_bckp.js +0 -313
- package/src/routes_static.js +0 -344
- package/vue.config.js +0 -83
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<div
|
|
4
|
-
v-for="singleDropdown in theInputData"
|
|
5
|
-
:key="singleDropdown.id"
|
|
6
|
-
class="texteatrou"
|
|
7
|
-
>
|
|
8
|
-
<span v-if="!singleDropdown.type" v-html="singleDropdown.content"></span>
|
|
9
|
-
<label
|
|
10
|
-
:for="`${inputDataId}_${singleDropdown.id}-champ`"
|
|
11
|
-
style="display:none;"
|
|
12
|
-
>
|
|
13
|
-
{{ $t('text.quiz') }}
|
|
14
|
-
</label>
|
|
15
|
-
<b-form-select
|
|
16
|
-
v-if="singleDropdown.type == 'dropdown'"
|
|
17
|
-
:id="`${inputDataId}_${singleDropdown.id}-champ`"
|
|
18
|
-
v-model="quizSelectedValue[singleDropdown.id]"
|
|
19
|
-
:class="classInput(singleDropdown.id)"
|
|
20
|
-
:options="singleDropdown.option"
|
|
21
|
-
:disabled="quizCompleted"
|
|
22
|
-
:aria-describedby="`${inputDataId}_${singleDropdown.id}-msg-erreur`"
|
|
23
|
-
></b-form-select>
|
|
24
|
-
<span
|
|
25
|
-
:id="`${inputDataId}_${singleDropdown.id}-msg-erreur`"
|
|
26
|
-
class="sr-only"
|
|
27
|
-
>
|
|
28
|
-
{{ messageAccessibility(singleDropdown.id) }}
|
|
29
|
-
</span>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</template>
|
|
33
|
-
<script>
|
|
34
|
-
import $extendsQuiz from '../mixins/$quizMixins'
|
|
35
|
-
export default {
|
|
36
|
-
name: 'AppCompInputTextToFillDropdown',
|
|
37
|
-
mixins: [$extendsQuiz],
|
|
38
|
-
|
|
39
|
-
props: {
|
|
40
|
-
inputType: {
|
|
41
|
-
type: String,
|
|
42
|
-
default: ''
|
|
43
|
-
},
|
|
44
|
-
inputDataId: {
|
|
45
|
-
type: String,
|
|
46
|
-
default: ''
|
|
47
|
-
},
|
|
48
|
-
inputData: {
|
|
49
|
-
type: Array,
|
|
50
|
-
default: () => []
|
|
51
|
-
},
|
|
52
|
-
textBase: {
|
|
53
|
-
type: String,
|
|
54
|
-
default: ''
|
|
55
|
-
},
|
|
56
|
-
quizCompleted: {
|
|
57
|
-
type: Boolean,
|
|
58
|
-
default: false
|
|
59
|
-
},
|
|
60
|
-
solution: {
|
|
61
|
-
type: Array,
|
|
62
|
-
default: () => []
|
|
63
|
-
},
|
|
64
|
-
showSolution: {
|
|
65
|
-
type: Boolean,
|
|
66
|
-
default: false
|
|
67
|
-
},
|
|
68
|
-
shuffleAnswers: {
|
|
69
|
-
type: Boolean,
|
|
70
|
-
default: false
|
|
71
|
-
},
|
|
72
|
-
quizSelected: {
|
|
73
|
-
type: Array,
|
|
74
|
-
default: () => []
|
|
75
|
-
}, //use to pass the value of the input
|
|
76
|
-
quizSubmit: {
|
|
77
|
-
type: Boolean,
|
|
78
|
-
default: false
|
|
79
|
-
} //use to call a submit
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
data() {
|
|
83
|
-
return {
|
|
84
|
-
quizSelectedValue: [], //not using quizSelected because quizSelected is a prop
|
|
85
|
-
theInputData: [] //to create the list of drop down and text mixed togeter
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
computed: {},
|
|
90
|
-
|
|
91
|
-
watch: {
|
|
92
|
-
/**
|
|
93
|
-
* @description to pass value to AppCompQuiz
|
|
94
|
-
* @fires input-change to AppCompQuiz.vue
|
|
95
|
-
*/
|
|
96
|
-
quizSelectedValue(newValue) {
|
|
97
|
-
this.$emit('input-change', newValue)
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @description to pass the value from AppCompQuiz
|
|
102
|
-
*/
|
|
103
|
-
quizSelected(newValue) {
|
|
104
|
-
this.quizSelectedValue = newValue
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
mounted() {
|
|
109
|
-
if (this.solution !== null) {
|
|
110
|
-
this.solution.sort(function(a, b) {
|
|
111
|
-
return a.question_id - b.question_id
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
//to show the defaultAnswer
|
|
115
|
-
const defaultAnswer = {
|
|
116
|
-
value: null,
|
|
117
|
-
disabled: true,
|
|
118
|
-
text: this.$t('message.first_option_dropdown')
|
|
119
|
-
}
|
|
120
|
-
let selectedChoices = []
|
|
121
|
-
for (let i = 0; i < this.inputData.length; i++) {
|
|
122
|
-
let singleDropdown = {}
|
|
123
|
-
if (this.shuffleAnswers) {
|
|
124
|
-
singleDropdown = this.inputData[i]
|
|
125
|
-
singleDropdown[
|
|
126
|
-
Object.keys(this.inputData[i])[0].toString()
|
|
127
|
-
] = this.shuffleArray(Object.values(this.inputData[i])[0])
|
|
128
|
-
} else {
|
|
129
|
-
singleDropdown = this.inputData[i]
|
|
130
|
-
}
|
|
131
|
-
for (
|
|
132
|
-
let index = 0;
|
|
133
|
-
index < Object.values(singleDropdown)[0].length;
|
|
134
|
-
index++
|
|
135
|
-
) {
|
|
136
|
-
const element = Object.values(singleDropdown)[0][index]
|
|
137
|
-
if (
|
|
138
|
-
element.text &&
|
|
139
|
-
element.text == this.$t('message.first_option_dropdown')
|
|
140
|
-
) {
|
|
141
|
-
Object.values(singleDropdown)[0].splice(index, 1)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
Object.values(singleDropdown)[0].unshift(defaultAnswer)
|
|
145
|
-
|
|
146
|
-
selectedChoices.push(null)
|
|
147
|
-
}
|
|
148
|
-
if (this.quizSelected.length == 0) {
|
|
149
|
-
this.quizSelectedValue = selectedChoices
|
|
150
|
-
} else {
|
|
151
|
-
this.quizSelectedValue = this.quizSelected
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.createTextWithInput(this.textBase)
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
methods: {
|
|
158
|
-
/**
|
|
159
|
-
* @description create the object to genate the text and inputs
|
|
160
|
-
* @param {String} str the text with holes to fill
|
|
161
|
-
*/
|
|
162
|
-
createTextWithInput(str) {
|
|
163
|
-
const regex = /\$%\S*%\$/g // regex pattern to match exp: $%number%$
|
|
164
|
-
let matchAll = str.split(regex)
|
|
165
|
-
let listInput = []
|
|
166
|
-
for (let i = 0; i < this.inputData.length; i++) {
|
|
167
|
-
const element = this.inputData[i]
|
|
168
|
-
listInput.push({ id: 't' + i, content: matchAll[i] })
|
|
169
|
-
listInput.push({
|
|
170
|
-
id: i,
|
|
171
|
-
type: 'dropdown',
|
|
172
|
-
option: Object.values(element)[0]
|
|
173
|
-
})
|
|
174
|
-
}
|
|
175
|
-
let lastItem = matchAll.length - 1
|
|
176
|
-
listInput.push({ id: 't' + lastItem, content: matchAll[lastItem] })
|
|
177
|
-
this.theInputData = listInput
|
|
178
|
-
},
|
|
179
|
-
/**
|
|
180
|
-
* @description used by the dropdown quiz
|
|
181
|
-
* @param {Array} answers what the user has answered
|
|
182
|
-
* @param {Array} solution the correct choices
|
|
183
|
-
* @returns {Boolean}
|
|
184
|
-
* @todo Add a catching for error if the 2 question_id dont match
|
|
185
|
-
*/
|
|
186
|
-
compareSelectedAnswers(answers, solution) {
|
|
187
|
-
for (let index = 0; index < answers.length; index++) {
|
|
188
|
-
const elementAnswers = answers[index]
|
|
189
|
-
const elementSolution = solution[index]
|
|
190
|
-
if (elementAnswers.question_id == elementSolution.question_id) {
|
|
191
|
-
if (elementAnswers.reponse_id !== elementSolution.reponse_id) {
|
|
192
|
-
return false
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
return //if it return nothing there is a bug since this means the 2 question_id dont match
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return true
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* @description check if a values exists in a array
|
|
203
|
-
* @param {Array} array
|
|
204
|
-
* @param value
|
|
205
|
-
* @returns {Boolean}
|
|
206
|
-
*/
|
|
207
|
-
containsValue(array, value) {
|
|
208
|
-
return array.includes(value)
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* @description shuffles an array used to randomized the option order if shuffleAnswers is true
|
|
213
|
-
* @param {Array} array
|
|
214
|
-
* @returns {Array}
|
|
215
|
-
*/
|
|
216
|
-
shuffleArray(array) {
|
|
217
|
-
let newArray = []
|
|
218
|
-
let newArray2 = []
|
|
219
|
-
|
|
220
|
-
for (let i = 0; i < array.length; i++) {
|
|
221
|
-
const element = array[i]
|
|
222
|
-
//todo remove null values
|
|
223
|
-
newArray.push(element)
|
|
224
|
-
}
|
|
225
|
-
while (newArray.length > 0) {
|
|
226
|
-
let pos = Math.floor(newArray.length * Math.random())
|
|
227
|
-
newArray2.push(newArray[pos])
|
|
228
|
-
newArray.splice(pos, 1)
|
|
229
|
-
}
|
|
230
|
-
return newArray2
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
</script>
|
|
235
|
-
<style lang="scss" scoped>
|
|
236
|
-
select {
|
|
237
|
-
&.custom-select {
|
|
238
|
-
background-image: inherit;
|
|
239
|
-
|
|
240
|
-
&:focus {
|
|
241
|
-
border-color: inherit;
|
|
242
|
-
box-shadow: inherit;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/***** style dev *****/
|
|
248
|
-
.texteatrou {
|
|
249
|
-
display: inline;
|
|
250
|
-
}
|
|
251
|
-
.texteatrou > select {
|
|
252
|
-
display: inline;
|
|
253
|
-
width: auto;
|
|
254
|
-
}
|
|
255
|
-
</style>
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@ Description: This component is used to display media element (video/audio and Gsap timelined animation )
|
|
3
|
-
@ What it does: The component create the HTMLMediaElement tag( video/audio) from data provided by user.
|
|
4
|
-
Once the HTMLMediaElement tag is created, the component save the HTMLMediaElement to store and
|
|
5
|
-
make it available to all other component that will need id
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<template>
|
|
9
|
-
<div
|
|
10
|
-
v-if="mData"
|
|
11
|
-
id="contain-media-player"
|
|
12
|
-
class="app-media-player"
|
|
13
|
-
:class="[{ FS: fullScreen }, animationType, CCBrowser]"
|
|
14
|
-
:style="{ height: [fsHeigh] }"
|
|
15
|
-
>
|
|
16
|
-
<!------------------video section --------------------------->
|
|
17
|
-
<video
|
|
18
|
-
v-if="media && media.mType === 'video'"
|
|
19
|
-
id="video"
|
|
20
|
-
ref="video"
|
|
21
|
-
tabindex="0"
|
|
22
|
-
class="v-media m-video"
|
|
23
|
-
:poster="media.mPoster"
|
|
24
|
-
@loadedmetadata="updateMediaData($event)"
|
|
25
|
-
@load="() => {}"
|
|
26
|
-
@error="() => {}"
|
|
27
|
-
>
|
|
28
|
-
<source
|
|
29
|
-
v-for="(aMedia, index) in media.mSources"
|
|
30
|
-
:key="index"
|
|
31
|
-
:src="`${aMedia.src}`"
|
|
32
|
-
:type="`${media.mType}/${aMedia.type}`"
|
|
33
|
-
/>
|
|
34
|
-
<track
|
|
35
|
-
v-if="media.mSubtitle"
|
|
36
|
-
:src="media.mSubtitle.src"
|
|
37
|
-
:srclang="media.mSubtitle.srclang"
|
|
38
|
-
:label="media.mSubtitle.label"
|
|
39
|
-
/>
|
|
40
|
-
</video>
|
|
41
|
-
<!------------------ audio section --------------------------->
|
|
42
|
-
<video
|
|
43
|
-
v-if="media && media.mType === 'audio'"
|
|
44
|
-
ref="audio"
|
|
45
|
-
tabindex="0"
|
|
46
|
-
class="v-media m-audio"
|
|
47
|
-
:poster="media.mPoster"
|
|
48
|
-
@loadedmetadata="updateMediaData($event)"
|
|
49
|
-
>
|
|
50
|
-
<source
|
|
51
|
-
v-for="(aMedia, index) in media.mSources"
|
|
52
|
-
:key="index"
|
|
53
|
-
:src="`${aMedia.src}`"
|
|
54
|
-
:type="`${media.mType}/${aMedia.type}`"
|
|
55
|
-
/>
|
|
56
|
-
<track
|
|
57
|
-
v-if="media.mSubtitle"
|
|
58
|
-
:src="media.mSubtitle.src"
|
|
59
|
-
:srclang="media.mSubtitle.srcLang"
|
|
60
|
-
:label="media.mSubtitle.label"
|
|
61
|
-
/>
|
|
62
|
-
</video>
|
|
63
|
-
<!------------------animation section --------------------------->
|
|
64
|
-
<div
|
|
65
|
-
v-if="media && hasAnimation"
|
|
66
|
-
id="anim-box"
|
|
67
|
-
ref="anim-box"
|
|
68
|
-
class="anim-canvas"
|
|
69
|
-
:class="[animationType]"
|
|
70
|
-
role="figure"
|
|
71
|
-
aria-roledescription="Animation"
|
|
72
|
-
>
|
|
73
|
-
<p class="sr-only">{{ mediaA11Y.srTxt }}</p>
|
|
74
|
-
<a class="skip-link" href="" @click.prevent="skipTo('playbar-play')">
|
|
75
|
-
{{ mediaA11Y.skipToTxt }}
|
|
76
|
-
</a>
|
|
77
|
-
<slot ref="animScene" name="drawingCanvas">
|
|
78
|
-
You have an animation to create
|
|
79
|
-
</slot>
|
|
80
|
-
</div>
|
|
81
|
-
<!-- <div v-if="mData"> -->
|
|
82
|
-
<app-comp-play-bar :media-to-play="mData" />
|
|
83
|
-
<!-- </div> -->
|
|
84
|
-
<!-- <portal-target :key="$route.fullPath" name="playbar-portal"></portal-target> -->
|
|
85
|
-
</div>
|
|
86
|
-
</template>
|
|
87
|
-
|
|
88
|
-
<script>
|
|
89
|
-
import { mapGetters } from 'vuex'
|
|
90
|
-
|
|
91
|
-
export default {
|
|
92
|
-
props: {
|
|
93
|
-
mData: {
|
|
94
|
-
type: Object,
|
|
95
|
-
required: true
|
|
96
|
-
},
|
|
97
|
-
fullScreen: {
|
|
98
|
-
type: Boolean,
|
|
99
|
-
default: true
|
|
100
|
-
},
|
|
101
|
-
custom: {
|
|
102
|
-
type: Boolean,
|
|
103
|
-
default: false
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
data() {
|
|
108
|
-
return {
|
|
109
|
-
media: null,
|
|
110
|
-
timeline: null,
|
|
111
|
-
subtitleMenuButtons: [],
|
|
112
|
-
subtitlesMenu: null,
|
|
113
|
-
fsHeigh: null,
|
|
114
|
-
isMedia: false
|
|
115
|
-
//currentMediaElement: null
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
computed: {
|
|
119
|
-
...mapGetters([
|
|
120
|
-
'hasMediaElOrTimeline',
|
|
121
|
-
'getCurrentBrowser',
|
|
122
|
-
'getCurrentPage'
|
|
123
|
-
]),
|
|
124
|
-
|
|
125
|
-
setTimeout() {
|
|
126
|
-
return window.setTimeout
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
hasAnimation() {
|
|
130
|
-
if (
|
|
131
|
-
this.mData &&
|
|
132
|
-
(this.mData.animation || this.mData.type === 'pg_animation')
|
|
133
|
-
) {
|
|
134
|
-
return true
|
|
135
|
-
} else return false
|
|
136
|
-
},
|
|
137
|
-
animationType() {
|
|
138
|
-
let type = null
|
|
139
|
-
if (this.mData) {
|
|
140
|
-
if (this.mData.type === 'pg_animation') {
|
|
141
|
-
type = 'animationOnly'
|
|
142
|
-
}
|
|
143
|
-
if (this.mData.type === 'pg_media') {
|
|
144
|
-
if (this.mData.mediaData.mType === 'audio') {
|
|
145
|
-
type = 'audioAnimation'
|
|
146
|
-
} else if (this.mData.mediaData.mType === 'video') {
|
|
147
|
-
type = 'videoAnimation'
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return type
|
|
152
|
-
},
|
|
153
|
-
CCBrowser() {
|
|
154
|
-
let browser = this.getCurrentBrowser
|
|
155
|
-
|
|
156
|
-
if (browser === 'Safari') {
|
|
157
|
-
return 'safari'
|
|
158
|
-
} else {
|
|
159
|
-
return 'chrome'
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
mediaA11Y() {
|
|
164
|
-
let m = {
|
|
165
|
-
srTxt: '',
|
|
166
|
-
skipToTxt: ''
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.$i18n.locale === 'fr'
|
|
170
|
-
? (m = {
|
|
171
|
-
srTxt:
|
|
172
|
-
"Le contenu d'apprentissage sera seulement disponible à la fin de la narration. Appuyez le bouton jouer, pour lancer l'animation.",
|
|
173
|
-
skipToTxt: 'Passer au controleur de media'
|
|
174
|
-
})
|
|
175
|
-
: (m = {
|
|
176
|
-
srTxt:
|
|
177
|
-
'The learning content will be available only at the end of the narration. Press the play button to start the animation',
|
|
178
|
-
skipToTxt: 'Skip to play-bar'
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
return m
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
watch: {
|
|
185
|
-
media() {
|
|
186
|
-
let mElement = this.$refs[this.media.mType]
|
|
187
|
-
/* Handeling media subtitles
|
|
188
|
-
* if there is any subtitle, create the subtitle menu
|
|
189
|
-
*/
|
|
190
|
-
if (mElement && mElement.textTracks) {
|
|
191
|
-
this.subtitlesMenu = []
|
|
192
|
-
for (let i = 0; i < mElement.textTracks.length; i++) {
|
|
193
|
-
this.subtitlesMenu.push({
|
|
194
|
-
id: `subtitle-${mElement.textTracks[i].language}`,
|
|
195
|
-
lang: mElement.textTracks[i].language,
|
|
196
|
-
label: mElement.textTracks[i].label
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
|
|
202
|
-
mData: {
|
|
203
|
-
immediate: true,
|
|
204
|
-
handler() {
|
|
205
|
-
if (this.mData && this.mData.type == 'pg_media') {
|
|
206
|
-
this.media = {
|
|
207
|
-
mType: this.mData.mediaData.mType,
|
|
208
|
-
mSources: [...this.mData.mediaData.mSources],
|
|
209
|
-
mPoster: this.mData.mediaData.mPoster || '',
|
|
210
|
-
mSubtitle: this.mData.mediaData.mSubtitle || null,
|
|
211
|
-
mTranscript: this.mData.mediaData.mTranscript || null
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// Its an animation
|
|
215
|
-
else if (this.mData && this.mData.type == 'pg_animation') {
|
|
216
|
-
this.media = this.mData.animation
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
mounted() {
|
|
222
|
-
if (this.fullScreen) {
|
|
223
|
-
this.fsHeigh = ` ${window.innerHeight - 55}px`
|
|
224
|
-
} else {
|
|
225
|
-
this.fsHeigh = `100%`
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
this.$bus.$emit('videoFullScreen', this.fullScreen)
|
|
229
|
-
if (this.media) {
|
|
230
|
-
window.addEventListener('resize', this.caclWindowHeight)
|
|
231
|
-
}
|
|
232
|
-
this.currentMediaElement
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
beforeDestroy() {
|
|
236
|
-
this.$bus.$emit('videoFullScreen', false)
|
|
237
|
-
window.removeEventListener('resize', this.caclWindowHeight)
|
|
238
|
-
},
|
|
239
|
-
methods: {
|
|
240
|
-
hideSutitle() {
|
|
241
|
-
return
|
|
242
|
-
},
|
|
243
|
-
showSubtitle() {
|
|
244
|
-
return
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* @description select available= subtitle for title for a media
|
|
249
|
-
* @param {htmlElement} e
|
|
250
|
-
*/
|
|
251
|
-
selectSubtitle(e) {
|
|
252
|
-
let mElement = document.querySelector(
|
|
253
|
-
`.app-media-player> ${this.media.mType}`
|
|
254
|
-
)
|
|
255
|
-
let subtitlesOption = this.$el.querySelectorAll('.subtitles-menu> a')
|
|
256
|
-
|
|
257
|
-
//set all the subitle options as inactive
|
|
258
|
-
for (let i = 0; i < subtitlesOption.length; i++) {
|
|
259
|
-
subtitlesOption[i].setAttribute('data-state', 'inactive')
|
|
260
|
-
subtitlesOption[i].className = 'sub_inactive'
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
for (let i = 0; i < mElement.textTracks.length; i++) {
|
|
264
|
-
// For the 'subtitles-off' button, the first condition will never match so all will subtitles be turned off
|
|
265
|
-
if (mElement.textTracks[i].language == e.target.lang) {
|
|
266
|
-
mElement.textTracks[i].mode = 'showing'
|
|
267
|
-
e.target.setAttribute('data-state', 'active')
|
|
268
|
-
e.target.className = 'sub_active'
|
|
269
|
-
} else {
|
|
270
|
-
mElement.textTracks[i].mode = 'hidden'
|
|
271
|
-
e.target.setAttribute('data-state', 'active')
|
|
272
|
-
e.target.className = 'sub_active'
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* @description update the information for the mediaElement in the store
|
|
279
|
-
* @param {htmlElement} e
|
|
280
|
-
* @fires update-page to AppBaseModule.vue
|
|
281
|
-
*/
|
|
282
|
-
updateMediaData(e) {
|
|
283
|
-
//dispatch loading status of for this component
|
|
284
|
-
this.$bus.$emit('set-comp-status', 'AppCompMediaPlayer', 'loading')
|
|
285
|
-
|
|
286
|
-
this.$store
|
|
287
|
-
.dispatch('updateCurrentMediaElement', e.target)
|
|
288
|
-
.then(() => {
|
|
289
|
-
this.$bus.$emit('update-page')
|
|
290
|
-
if (this.mData.timeline) {
|
|
291
|
-
//Set the length (total duration) of the timeline to the duration of the media
|
|
292
|
-
//Note: using totalDuration() or duration() will not work to define the duration to the length of the media.
|
|
293
|
-
this.mData.timeline.set({}, {}, e.target.duration)
|
|
294
|
-
}
|
|
295
|
-
})
|
|
296
|
-
.then(() =>
|
|
297
|
-
this.$bus.$emit('set-comp-status', 'AppCompMediaPlayer', 'ready')
|
|
298
|
-
)
|
|
299
|
-
},
|
|
300
|
-
caclWindowHeight() {
|
|
301
|
-
document.getElementById(
|
|
302
|
-
'contain-media-player'
|
|
303
|
-
).style.height = `${window.innerHeight - 55}px`
|
|
304
|
-
},
|
|
305
|
-
// TODO: Checking that the media ressource exist/is available
|
|
306
|
-
// true: create set the ressource src
|
|
307
|
-
// false: disable play button, show on screen message media not available to user
|
|
308
|
-
//
|
|
309
|
-
async checkRessource(url) {
|
|
310
|
-
this.axios
|
|
311
|
-
.get(url, { responseType: 'blob' })
|
|
312
|
-
.then((res) => {
|
|
313
|
-
res
|
|
314
|
-
})
|
|
315
|
-
.catch((err) => {
|
|
316
|
-
err
|
|
317
|
-
})
|
|
318
|
-
},
|
|
319
|
-
/**
|
|
320
|
-
* @description- Skip directly to the specify containt/region*
|
|
321
|
-
* main content is Node with defined ID
|
|
322
|
-
* @param {String} targetID- Css selector for the target element
|
|
323
|
-
*/
|
|
324
|
-
|
|
325
|
-
skipTo(targetID) {
|
|
326
|
-
let skipTo = document.querySelector(`#wrapper-content`) //default definition of main element
|
|
327
|
-
|
|
328
|
-
if (targetID) {
|
|
329
|
-
let targetEl = document.querySelector(`#${targetID}`) // search for node element specified as main
|
|
330
|
-
|
|
331
|
-
if (targetEl) skipTo = targetEl
|
|
332
|
-
}
|
|
333
|
-
let targetTop = skipTo.offsetTop
|
|
334
|
-
|
|
335
|
-
let scrollOpt = {
|
|
336
|
-
top: targetTop - 100,
|
|
337
|
-
left: 0,
|
|
338
|
-
behavior: 'auto' //auto
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
//Allowing accessibility control with keyboard
|
|
342
|
-
skipTo.setAttribute('tabIndex', -1)
|
|
343
|
-
window.scrollTo(scrollOpt)
|
|
344
|
-
skipTo.focus()
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
async setMediaElement() {
|
|
348
|
-
const { type } = await this.getCurrentPage
|
|
349
|
-
if (!['pg_media, pg_animation'].includes(type)) return null
|
|
350
|
-
|
|
351
|
-
let mediaObject = {}
|
|
352
|
-
switch (type) {
|
|
353
|
-
case 'pg_media': {
|
|
354
|
-
const {
|
|
355
|
-
animation,
|
|
356
|
-
type,
|
|
357
|
-
mediaData: { mSources, mType, mSubtitle, mPoster, mTranscript },
|
|
358
|
-
timeline,
|
|
359
|
-
mElement
|
|
360
|
-
} = this.getCurrentPage
|
|
361
|
-
|
|
362
|
-
mediaObject = {
|
|
363
|
-
animation,
|
|
364
|
-
type,
|
|
365
|
-
mediaData: { mSources, mType, mSubtitle, mPoster, mTranscript },
|
|
366
|
-
timeline,
|
|
367
|
-
mElement
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
break
|
|
371
|
-
}
|
|
372
|
-
case 'pg_animation': {
|
|
373
|
-
const { animation, type, timeline } = this.getCurrentPage
|
|
374
|
-
mediaObject = { animation, type, timeline }
|
|
375
|
-
break
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return mediaObject
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
</script>
|
|
384
|
-
<style lang="scss">
|
|
385
|
-
$widthVideo: 100%;
|
|
386
|
-
$widthAudioAnimation: 50%;
|
|
387
|
-
|
|
388
|
-
%animation {
|
|
389
|
-
position: absolute;
|
|
390
|
-
top: 0;
|
|
391
|
-
left: 0;
|
|
392
|
-
width: $widthVideo;
|
|
393
|
-
height: 100%;
|
|
394
|
-
z-index: 1;
|
|
395
|
-
overflow: hidden;
|
|
396
|
-
}
|
|
397
|
-
</style>
|