fcad-core-dragon 2.1.0-beta.2 → 2.1.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.cjs +81 -81
- package/CHANGELOG +20 -0
- package/bk.scss +117 -117
- package/package.json +30 -31
- package/src/assets/data/onboardingMessages.json +47 -47
- package/src/components/AppBase.vue +167 -39
- package/src/components/AppBaseButton.test.js +0 -1
- package/src/components/AppBaseErrorDisplay.vue +438 -438
- package/src/components/AppBaseFlipCard.vue +84 -84
- package/src/components/AppBaseModule.vue +103 -116
- package/src/components/AppBasePage.vue +13 -13
- package/src/components/AppBasePopover.vue +41 -41
- package/src/components/AppCompMenu.vue +2 -1
- package/src/components/AppCompPlayBarNext.vue +157 -16
- package/src/components/AppCompPopUpNext.vue +3 -3
- package/src/components/AppCompQuizRecall.vue +2 -3
- package/src/components/AppCompSettingsMenu.vue +172 -172
- package/src/components/AppCompTableOfContent.vue +1 -1
- package/src/components/AppCompViewDisplay.vue +6 -6
- package/src/components/tests__/useTimer.spec.js +91 -0
- package/src/composables/useIdleDetector.js +56 -0
- package/src/composables/useQuiz.js +1 -1
- package/src/composables/useTimer.js +175 -0
- package/src/externalComps/ModuleView.vue +22 -22
- package/src/externalComps/SummaryView.vue +91 -91
- package/src/main.js +2 -0
- package/src/module/stores/appStore.js +10 -34
- package/src/module/xapi/ADL.js +1 -0
- package/src/module/xapi/Crypto/Hasher.js +241 -241
- package/src/module/xapi/Crypto/WordArray.js +278 -278
- package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
- package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
- package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
- package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
- package/src/module/xapi/Crypto/encoders/Base.js +105 -105
- package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
- package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
- package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
- package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
- package/src/module/xapi/Crypto/index.js +53 -53
- package/src/module/xapi/Statement/activity.js +47 -47
- package/src/module/xapi/Statement/agent.js +55 -55
- package/src/module/xapi/Statement/group.js +26 -26
- package/src/module/xapi/Statement/index.js +259 -259
- package/src/module/xapi/Statement/statement.js +253 -253
- package/src/module/xapi/Statement/statementRef.js +23 -23
- package/src/module/xapi/Statement/substatement.js +22 -22
- package/src/module/xapi/Statement/verb.js +36 -36
- package/src/module/xapi/activitytypes.js +17 -17
- package/src/module/xapi/utils.js +167 -167
- package/src/module/xapi/verbs.js +294 -294
- package/src/module/xapi/xapiStatement.js +444 -444
- package/src/plugins/analytics.js +34 -0
- package/src/plugins/bus.js +8 -8
- package/src/plugins/gsap.js +14 -14
- package/src/plugins/i18n.js +26 -44
- package/src/plugins/save.js +37 -37
- package/src/plugins/scorm.js +287 -287
- package/src/plugins/xapi.js +11 -11
- package/src/public/index.html +33 -33
- package/src/router/index.js +6 -3
- package/src/components/AppCompPlayBarProgress.vue +0 -82
- package/src/mixins/$mediaMixins.js +0 -809
- package/src/mixins/timerMixin.js +0 -195
- package/src/module/xapi/wrapper copy.js +0 -1963
|
@@ -1,172 +1,172 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<b-sidebar
|
|
3
|
-
id="sidebar-settings"
|
|
4
|
-
title="Settings"
|
|
5
|
-
right
|
|
6
|
-
shadow
|
|
7
|
-
backdrop
|
|
8
|
-
no-header
|
|
9
|
-
@hidden="onClose"
|
|
10
|
-
>
|
|
11
|
-
<div class="pl-3 pr-3">
|
|
12
|
-
<b-form-group :label="$t('user_settings.title')">
|
|
13
|
-
<b-form-checkbox
|
|
14
|
-
v-for="(setting, index) in Object.entries(settingsSelected)"
|
|
15
|
-
:key="index"
|
|
16
|
-
v-model="settingsSelected[setting[0]]"
|
|
17
|
-
:aria-describedby="ariaDescribedby"
|
|
18
|
-
:name="`parameter-${setting[0]}`"
|
|
19
|
-
:text-field="$t(`user_settings.${setting[0]}`)"
|
|
20
|
-
switch
|
|
21
|
-
stacked
|
|
22
|
-
size="lg"
|
|
23
|
-
@change="onSettingsChange"
|
|
24
|
-
>
|
|
25
|
-
<span>{{ $t(`user_settings.${setting[0]}`) }}</span>
|
|
26
|
-
</b-form-checkbox>
|
|
27
|
-
</b-form-group>
|
|
28
|
-
<app-base-button
|
|
29
|
-
v-b-toggle.sidebar-settings
|
|
30
|
-
class="float-right btn-primary"
|
|
31
|
-
:title="$t('user_settings.close')"
|
|
32
|
-
>
|
|
33
|
-
{{ $t('user_settings.close') }}
|
|
34
|
-
</app-base-button>
|
|
35
|
-
</div>
|
|
36
|
-
</b-sidebar>
|
|
37
|
-
</template>
|
|
38
|
-
|
|
39
|
-
<script>
|
|
40
|
-
import { mapState, mapActions } from 'pinia'
|
|
41
|
-
import { useAppStore } from '../module/stores/appStore'
|
|
42
|
-
import AppBaseButton from './AppBaseButton.vue'
|
|
43
|
-
|
|
44
|
-
export default {
|
|
45
|
-
components: { AppBaseButton },
|
|
46
|
-
props: {},
|
|
47
|
-
data() {
|
|
48
|
-
return {
|
|
49
|
-
settingsSelected: {},
|
|
50
|
-
settingsNeedUpdate: false
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
computed: {
|
|
54
|
-
...mapState(useAppStore, [
|
|
55
|
-
'getApplicationSettings',
|
|
56
|
-
'getModuleInfo',
|
|
57
|
-
'getUserInteraction',
|
|
58
|
-
'getConnectionInfo',
|
|
59
|
-
'onboardingEnabled'
|
|
60
|
-
]),
|
|
61
|
-
settingsOptions() {
|
|
62
|
-
return this.settingsOptionsELPlus
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
watch: {
|
|
66
|
-
getApplicationSettings: {
|
|
67
|
-
immediate: true,
|
|
68
|
-
deep: true,
|
|
69
|
-
handler() {
|
|
70
|
-
this.getAppSettingsInStore()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
created() {
|
|
75
|
-
setTimeout(() => this.getAppSettingsInStore(), 1700)
|
|
76
|
-
|
|
77
|
-
this.$bus.$on('save-settings', (settings) => {
|
|
78
|
-
this.saveSettings(settings)
|
|
79
|
-
})
|
|
80
|
-
},
|
|
81
|
-
methods: {
|
|
82
|
-
...mapActions(useAppStore, [
|
|
83
|
-
'setApplicationSettings',
|
|
84
|
-
'updateUserMetaData'
|
|
85
|
-
]),
|
|
86
|
-
getAppSettingsInStore() {
|
|
87
|
-
this.settingsSelected = { ...this.getApplicationSettings }
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
//Save user settings to the store
|
|
91
|
-
onSettingsChange() {
|
|
92
|
-
this.settingsNeedUpdate = true
|
|
93
|
-
this.saveSettingsToStore(this.settingsSelected)
|
|
94
|
-
},
|
|
95
|
-
saveSettingsToStore(settings) {
|
|
96
|
-
this.setApplicationSettings(settings)
|
|
97
|
-
},
|
|
98
|
-
/**
|
|
99
|
-
* @description Save the user preferred settings to vue store and LRS or local store:
|
|
100
|
-
* LRS: Send a XAPI Statement to the LRS
|
|
101
|
-
* IndexedDB: Set a new Key for preferred settings in the userInteraction in store
|
|
102
|
-
* Update current value of settings
|
|
103
|
-
* @param {Object} appSettings - group of application settings {autoplay, subtitles, onboarding}
|
|
104
|
-
*
|
|
105
|
-
*/
|
|
106
|
-
saveSettings(appSettings) {
|
|
107
|
-
this.saveSettingsToStore(appSettings) //save to store
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
this.getModuleInfo.packageType === 'xapi' &&
|
|
111
|
-
this.getConnectionInfo &&
|
|
112
|
-
this.getConnectionInfo.remote
|
|
113
|
-
) {
|
|
114
|
-
let text
|
|
115
|
-
//Defining the text to display for stmt description and definition
|
|
116
|
-
switch (this.$i18n.locale) {
|
|
117
|
-
case 'fr':
|
|
118
|
-
if (this.getModuleInfo.courseID)
|
|
119
|
-
text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
|
|
120
|
-
else text = `Le ${this.getModuleInfo.id}`
|
|
121
|
-
break
|
|
122
|
-
case 'en':
|
|
123
|
-
if (this.getModuleInfo.courseID)
|
|
124
|
-
text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
|
|
125
|
-
else text = `The ${this.getModuleInfo.id}`
|
|
126
|
-
break
|
|
127
|
-
}
|
|
128
|
-
//Creating custom statement
|
|
129
|
-
const stmt = {
|
|
130
|
-
id: (() => {
|
|
131
|
-
return this.getModuleInfo.courseID
|
|
132
|
-
? this.getModuleInfo.courseID
|
|
133
|
-
: null
|
|
134
|
-
})(),
|
|
135
|
-
verb: 'preferred',
|
|
136
|
-
definition: text,
|
|
137
|
-
description: text,
|
|
138
|
-
extensions: [
|
|
139
|
-
{
|
|
140
|
-
id: 'application-settings',
|
|
141
|
-
content: {
|
|
142
|
-
userSettings: { ...appSettings }
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
]
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
setTimeout(() => {
|
|
149
|
-
this.$bus.$emit('send-xapi-statement', stmt) //send xapi statement
|
|
150
|
-
}, 1000)
|
|
151
|
-
} else {
|
|
152
|
-
this.$set(this.getUserInteraction, 'userSettings', appSettings) // adding the usersettings to user Interaction to save to save to the local DB
|
|
153
|
-
this.updateUserMetaData(this.getUserInteraction) //force update of the userInteraction to ensure saving of settings in local DB
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
//update sidebar data
|
|
157
|
-
this.getAppSettingsInStore()
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @description -Action to perform when the sidebar closed- will require saving when changing occurred
|
|
162
|
-
*/
|
|
163
|
-
onClose() {
|
|
164
|
-
if (this.settingsNeedUpdate) {
|
|
165
|
-
this.saveSettingsToStore(this.settingsSelected)
|
|
166
|
-
this.saveSettings(this.getApplicationSettings)
|
|
167
|
-
this.settingsNeedUpdate = false
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<b-sidebar
|
|
3
|
+
id="sidebar-settings"
|
|
4
|
+
title="Settings"
|
|
5
|
+
right
|
|
6
|
+
shadow
|
|
7
|
+
backdrop
|
|
8
|
+
no-header
|
|
9
|
+
@hidden="onClose"
|
|
10
|
+
>
|
|
11
|
+
<div class="pl-3 pr-3">
|
|
12
|
+
<b-form-group :label="$t('user_settings.title')">
|
|
13
|
+
<b-form-checkbox
|
|
14
|
+
v-for="(setting, index) in Object.entries(settingsSelected)"
|
|
15
|
+
:key="index"
|
|
16
|
+
v-model="settingsSelected[setting[0]]"
|
|
17
|
+
:aria-describedby="ariaDescribedby"
|
|
18
|
+
:name="`parameter-${setting[0]}`"
|
|
19
|
+
:text-field="$t(`user_settings.${setting[0]}`)"
|
|
20
|
+
switch
|
|
21
|
+
stacked
|
|
22
|
+
size="lg"
|
|
23
|
+
@change="onSettingsChange"
|
|
24
|
+
>
|
|
25
|
+
<span>{{ $t(`user_settings.${setting[0]}`) }}</span>
|
|
26
|
+
</b-form-checkbox>
|
|
27
|
+
</b-form-group>
|
|
28
|
+
<app-base-button
|
|
29
|
+
v-b-toggle.sidebar-settings
|
|
30
|
+
class="float-right btn-primary"
|
|
31
|
+
:title="$t('user_settings.close')"
|
|
32
|
+
>
|
|
33
|
+
{{ $t('user_settings.close') }}
|
|
34
|
+
</app-base-button>
|
|
35
|
+
</div>
|
|
36
|
+
</b-sidebar>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import { mapState, mapActions } from 'pinia'
|
|
41
|
+
import { useAppStore } from '../module/stores/appStore'
|
|
42
|
+
import AppBaseButton from './AppBaseButton.vue'
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
components: { AppBaseButton },
|
|
46
|
+
props: {},
|
|
47
|
+
data() {
|
|
48
|
+
return {
|
|
49
|
+
settingsSelected: {},
|
|
50
|
+
settingsNeedUpdate: false
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
computed: {
|
|
54
|
+
...mapState(useAppStore, [
|
|
55
|
+
'getApplicationSettings',
|
|
56
|
+
'getModuleInfo',
|
|
57
|
+
'getUserInteraction',
|
|
58
|
+
'getConnectionInfo',
|
|
59
|
+
'onboardingEnabled'
|
|
60
|
+
]),
|
|
61
|
+
settingsOptions() {
|
|
62
|
+
return this.settingsOptionsELPlus
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
watch: {
|
|
66
|
+
getApplicationSettings: {
|
|
67
|
+
immediate: true,
|
|
68
|
+
deep: true,
|
|
69
|
+
handler() {
|
|
70
|
+
this.getAppSettingsInStore()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
created() {
|
|
75
|
+
setTimeout(() => this.getAppSettingsInStore(), 1700)
|
|
76
|
+
|
|
77
|
+
this.$bus.$on('save-settings', (settings) => {
|
|
78
|
+
this.saveSettings(settings)
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
methods: {
|
|
82
|
+
...mapActions(useAppStore, [
|
|
83
|
+
'setApplicationSettings',
|
|
84
|
+
'updateUserMetaData'
|
|
85
|
+
]),
|
|
86
|
+
getAppSettingsInStore() {
|
|
87
|
+
this.settingsSelected = { ...this.getApplicationSettings }
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
//Save user settings to the store
|
|
91
|
+
onSettingsChange() {
|
|
92
|
+
this.settingsNeedUpdate = true
|
|
93
|
+
this.saveSettingsToStore(this.settingsSelected)
|
|
94
|
+
},
|
|
95
|
+
saveSettingsToStore(settings) {
|
|
96
|
+
this.setApplicationSettings(settings)
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* @description Save the user preferred settings to vue store and LRS or local store:
|
|
100
|
+
* LRS: Send a XAPI Statement to the LRS
|
|
101
|
+
* IndexedDB: Set a new Key for preferred settings in the userInteraction in store
|
|
102
|
+
* Update current value of settings
|
|
103
|
+
* @param {Object} appSettings - group of application settings {autoplay, subtitles, onboarding}
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
saveSettings(appSettings) {
|
|
107
|
+
this.saveSettingsToStore(appSettings) //save to store
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
this.getModuleInfo.packageType === 'xapi' &&
|
|
111
|
+
this.getConnectionInfo &&
|
|
112
|
+
this.getConnectionInfo.remote
|
|
113
|
+
) {
|
|
114
|
+
let text
|
|
115
|
+
//Defining the text to display for stmt description and definition
|
|
116
|
+
switch (this.$i18n.locale) {
|
|
117
|
+
case 'fr':
|
|
118
|
+
if (this.getModuleInfo.courseID)
|
|
119
|
+
text = `Le ${this.getModuleInfo.id} de ${this.getModuleInfo.courseID}`
|
|
120
|
+
else text = `Le ${this.getModuleInfo.id}`
|
|
121
|
+
break
|
|
122
|
+
case 'en':
|
|
123
|
+
if (this.getModuleInfo.courseID)
|
|
124
|
+
text = `The ${this.getModuleInfo.id} of ${this.getModuleInfo.courseID}`
|
|
125
|
+
else text = `The ${this.getModuleInfo.id}`
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
//Creating custom statement
|
|
129
|
+
const stmt = {
|
|
130
|
+
id: (() => {
|
|
131
|
+
return this.getModuleInfo.courseID
|
|
132
|
+
? this.getModuleInfo.courseID
|
|
133
|
+
: null
|
|
134
|
+
})(),
|
|
135
|
+
verb: 'preferred',
|
|
136
|
+
definition: text,
|
|
137
|
+
description: text,
|
|
138
|
+
extensions: [
|
|
139
|
+
{
|
|
140
|
+
id: 'application-settings',
|
|
141
|
+
content: {
|
|
142
|
+
userSettings: { ...appSettings }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
this.$bus.$emit('send-xapi-statement', stmt) //send xapi statement
|
|
150
|
+
}, 1000)
|
|
151
|
+
} else {
|
|
152
|
+
this.$set(this.getUserInteraction, 'userSettings', appSettings) // adding the usersettings to user Interaction to save to save to the local DB
|
|
153
|
+
this.updateUserMetaData(this.getUserInteraction) //force update of the userInteraction to ensure saving of settings in local DB
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//update sidebar data
|
|
157
|
+
this.getAppSettingsInStore()
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @description -Action to perform when the sidebar closed- will require saving when changing occurred
|
|
162
|
+
*/
|
|
163
|
+
onClose() {
|
|
164
|
+
if (this.settingsNeedUpdate) {
|
|
165
|
+
this.saveSettingsToStore(this.settingsSelected)
|
|
166
|
+
this.saveSettings(this.getApplicationSettings)
|
|
167
|
+
this.settingsNeedUpdate = false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<template>
|
|
11
11
|
<div v-if="error" id="sidebar-submenu" :class="{ isOpen: isOpened }">
|
|
12
|
-
<focus-trap :active="isOpened">
|
|
12
|
+
<focus-trap :active="isOpened" :prevent-scroll="true">
|
|
13
13
|
<div ref="target">
|
|
14
14
|
<!-- <div class="submenu-header"> -->
|
|
15
15
|
<app-base-button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<router-view />
|
|
3
|
-
</template>
|
|
4
|
-
<script>
|
|
5
|
-
export default {}
|
|
6
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<router-view />
|
|
3
|
+
</template>
|
|
4
|
+
<script>
|
|
5
|
+
export default {}
|
|
6
|
+
</script>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { Timer } from '../../composables/useTimer'
|
|
3
|
+
|
|
4
|
+
// vitest vi utility ref: https://vitest.dev/api/vi.html
|
|
5
|
+
// vi fakeTimers ref: https://vitest.dev/api/vi.html#vi-usefaketimers
|
|
6
|
+
|
|
7
|
+
describe('Timer class', () => {
|
|
8
|
+
let timer
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
Timer.timers.clear() // Clear existing timers before each test
|
|
12
|
+
timer = new Timer()
|
|
13
|
+
vi.useFakeTimers() // Enable fake timers before each test
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
timer.destroy() // destroy created timer after each test
|
|
18
|
+
vi.useRealTimers() // Restore real timers after each test
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should create a Timer instance with a unique ID', () => {
|
|
22
|
+
const timer2 = new Timer() //create a second instance
|
|
23
|
+
expect(timer2.getTimerID()).not.toBe(timer.getTimerID())
|
|
24
|
+
expect(Timer.timers.size).toBe(2) // Two timers should be registered
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should return a timer instance by it ID', () => {
|
|
28
|
+
const timer2 = new Timer('timer22') //create a second instance with custom ID
|
|
29
|
+
expect(timer.getTimer('timer22')).toBeInstanceOf(Timer)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should throw error when a timer instance doesn`t exist', () => {
|
|
33
|
+
const id = 'noExistingTimer'
|
|
34
|
+
expect(() => timer.getTimer(id)).toThrowError(
|
|
35
|
+
`Timer with ID ${id} does not exist.`
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should initialize timer at 0 seconds', () => {
|
|
40
|
+
expect(timer.getTime()).toBe(0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should start the timer and increment time every second', () => {
|
|
44
|
+
timer.start()
|
|
45
|
+
expect(timer.getTimerState()).toBe('started')
|
|
46
|
+
|
|
47
|
+
vi.advanceTimersByTime(5000) // Fast-forward 3 seconds
|
|
48
|
+
expect(timer.getTime()).toBe(5) // Should be approximately 5 seconds
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should display time in correct format when converting to ISOformats', () => {
|
|
52
|
+
timer.start()
|
|
53
|
+
expect(timer.getTimerState()).toBe('started')
|
|
54
|
+
|
|
55
|
+
vi.advanceTimersByTime(65000) // Fast-forward 3 seconds
|
|
56
|
+
|
|
57
|
+
const fullISOFormat = '1970-01-01T00:01:05.000Z'
|
|
58
|
+
const isoTimeStr = '00:01:05'
|
|
59
|
+
|
|
60
|
+
expect(timer.getTime()).toBe(65) // Should be approximately 1 min 5 seconds (00:01:05)
|
|
61
|
+
expect(timer.formatToISOString(timer.getTime())).toBe(fullISOFormat)
|
|
62
|
+
expect(timer.ISOTimeParser(timer.getTime())).toBe(isoTimeStr)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should pause the timer', () => {
|
|
66
|
+
timer.start()
|
|
67
|
+
vi.advanceTimersByTime(2000)
|
|
68
|
+
timer.pause()
|
|
69
|
+
const current = timer.getTime()
|
|
70
|
+
|
|
71
|
+
// Advance time but timer should stay paused
|
|
72
|
+
vi.advanceTimersByTime(3000)
|
|
73
|
+
expect(timer.getTime()).toBe(current) // about 2 seconds
|
|
74
|
+
expect(timer.ISOTimeParser(timer.getTime())).toBe('00:00:02')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should reset the timer to 0 when timer stops', () => {
|
|
78
|
+
timer.start()
|
|
79
|
+
vi.advanceTimersByTime(4000)
|
|
80
|
+
timer.stop()
|
|
81
|
+
expect(timer.getTime()).toBe(0)
|
|
82
|
+
expect(timer.getTimerState()).toBe('stopped')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should destroy timer', () => {
|
|
86
|
+
const id = timer.getTimerID()
|
|
87
|
+
timer.destroy()
|
|
88
|
+
expect(Timer.timers.get(id)).toBeUndefined()
|
|
89
|
+
expect(Timer.timers.size).toBe(0)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export class IdleDetector {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.idleCounter = 0
|
|
4
|
+
this.idleTimeoutID = null
|
|
5
|
+
this.detectorState = 'stopped' // can be 'pause' or 'stop'
|
|
6
|
+
this.idleTimer = null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @description Start the idle timer -
|
|
11
|
+
* creates an interval to track idle time and a timeout to trigger actions
|
|
12
|
+
* after a specified period of inactivity.
|
|
13
|
+
* @param {Function} action - a callback function to execute when the idle timeout is reached.
|
|
14
|
+
* @param {Number} timeout - the duration in milliseconds before the action is triggered.
|
|
15
|
+
*/
|
|
16
|
+
startIdleTimer(action = null, timeout = 0) {
|
|
17
|
+
this.idleTimer = setInterval(() => {
|
|
18
|
+
this.idleCounter += 1
|
|
19
|
+
}, 1000)
|
|
20
|
+
this.detectorState = 'started'
|
|
21
|
+
|
|
22
|
+
if (!action || typeof action !== 'function') return
|
|
23
|
+
|
|
24
|
+
this.idleTimeoutID = setTimeout(() => action(), timeout)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @description Reset the idle timer -
|
|
29
|
+
* clears the current idle timer and timeout,
|
|
30
|
+
*/
|
|
31
|
+
stopIdleTimer() {
|
|
32
|
+
this.idleCounter = 0
|
|
33
|
+
clearInterval(this.idleTimer)
|
|
34
|
+
clearTimeout(this.idleTimeoutID)
|
|
35
|
+
this.detectorState = 'stopped'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @description Get the current state of the timer
|
|
40
|
+
*
|
|
41
|
+
* @returns {String} - timer state, can be 'started' or 'stopped'
|
|
42
|
+
*/
|
|
43
|
+
getDectorState() {
|
|
44
|
+
return this.detectorState
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @description Get the elapsed time for the idle timer.
|
|
49
|
+
* This is the time the idle detector has been running since it was started.
|
|
50
|
+
*
|
|
51
|
+
* @returns {Number} - the time count in seconds
|
|
52
|
+
*/
|
|
53
|
+
getElapsedTime() {
|
|
54
|
+
return this.idleCounter
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//This composable
|
|
1
|
+
//This composable should Extend the functionality of a the quiz
|
|
2
2
|
|
|
3
3
|
import i18n from '@/i18n' //Must import directly the local from project app because vue-18in does not work in legacy mode with composable
|
|
4
4
|
|