fcad-core-dragon 2.0.0-beta.4 → 2.0.0-beta.6
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 +19 -0
- package/README.md +71 -71
- package/bk.scss +117 -117
- package/package.json +8 -8
- package/src/$locales/en.json +23 -23
- package/src/$locales/fr.json +22 -21
- package/src/assets/data/onboardingMessages.json +47 -47
- package/src/components/AppBase.vue +186 -116
- package/src/components/AppBaseButton.test.js +22 -0
- package/src/components/AppBaseButton.vue +13 -5
- package/src/components/AppBaseErrorDisplay.vue +438 -438
- package/src/components/AppBaseFlipCard.vue +84 -84
- package/src/components/AppBaseModule.vue +207 -128
- package/src/components/AppBasePage.vue +18 -45
- package/src/components/AppBasePopover.vue +41 -41
- package/src/components/AppCompAudio.vue +20 -17
- package/src/components/AppCompBranchButtons.vue +28 -70
- package/src/components/AppCompButtonProgress.vue +4 -9
- package/src/components/AppCompCarousel.vue +120 -90
- package/src/components/{AppCompTranscript.vue → AppCompContainer.vue} +8 -1
- package/src/components/AppCompInputCheckBoxNext.vue +5 -0
- package/src/components/AppCompInputDropdownNext.vue +50 -8
- package/src/components/AppCompInputRadioNext.vue +152 -152
- package/src/components/AppCompInputTextNext.vue +21 -2
- package/src/components/AppCompInputTextTableNext.vue +1 -0
- package/src/components/AppCompInputTextToFillDropdownNext.vue +8 -0
- package/src/components/AppCompInputTextToFillNext.vue +171 -171
- package/src/components/AppCompJauge.vue +74 -74
- package/src/components/AppCompMenu.vue +13 -7
- package/src/components/AppCompMenuItem.vue +228 -228
- package/src/components/AppCompNavigation.vue +43 -30
- package/src/components/AppCompNoteCall.vue +64 -38
- package/src/components/AppCompNoteCredit.vue +303 -105
- package/src/components/AppCompPlayBarNext.vue +25 -12
- package/src/components/AppCompPlayBarProgress.vue +82 -82
- package/src/components/AppCompPopUpNext.vue +1 -4
- package/src/components/AppCompQuizNext.vue +8 -4
- package/src/components/AppCompQuizRecall.vue +44 -22
- package/src/components/AppCompSVGNext.vue +2 -3
- package/src/components/AppCompSettingsMenu.vue +172 -172
- package/src/components/AppCompTableOfContent.vue +61 -62
- package/src/components/AppCompVideoPlayer.vue +17 -15
- package/src/components/AppCompViewDisplay.vue +6 -6
- package/src/components/BaseModule.vue +1 -18
- package/src/components/tests__/AppBaseButton.spec.js +53 -0
- package/src/composables/useQuiz.js +206 -206
- package/src/externalComps/ModuleView.vue +22 -22
- package/src/externalComps/SummaryView.vue +91 -91
- package/src/main.js +37 -32
- package/src/mixins/$mediaMixins.js +819 -819
- package/src/mixins/timerMixin.js +155 -155
- package/src/module/stores/appStore.js +59 -6
- package/src/module/xapi/ADL.js +144 -4
- 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/wrapper copy.js +1963 -0
- package/src/module/xapi/wrapper.js +121 -188
- package/src/module/xapi/xapiStatement.js +444 -444
- package/src/plugins/bus.js +8 -8
- package/src/plugins/gsap.js +14 -14
- package/src/plugins/helper.js +52 -12
- package/src/plugins/i18n.js +44 -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 +8 -2
- package/src/router/routes.js +312 -312
- package/src/shared/generalfuncs.js +210 -210
- package/src/shared/validators.js +38 -179
- package/vitest.config.js +19 -0
- package/src/components/AppCompPlayBar.vue +0 -1218
package/src/plugins/bus.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import emitter from 'tiny-emitter/instance'
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
$on: (...args) => emitter.on(...args),
|
|
5
|
-
$once: (...args) => emitter.once(...args),
|
|
6
|
-
$off: (...args) => emitter.off(...args),
|
|
7
|
-
$emit: (...args) => emitter.emit(...args)
|
|
8
|
-
}
|
|
1
|
+
import emitter from 'tiny-emitter/instance'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
$on: (...args) => emitter.on(...args),
|
|
5
|
+
$once: (...args) => emitter.once(...args),
|
|
6
|
+
$off: (...args) => emitter.off(...args),
|
|
7
|
+
$emit: (...args) => emitter.emit(...args)
|
|
8
|
+
}
|
package/src/plugins/gsap.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integrating gsap library as Vue plug in
|
|
3
|
-
* usage: import GsapPlugin from './utilies.js';
|
|
4
|
-
* ref: https: //vuejsdevelopers.com/2017/04/22/vue-js-libraries-plugins/
|
|
5
|
-
*/
|
|
6
|
-
import gsap from 'gsap'
|
|
7
|
-
import { MotionPathPlugin } from 'gsap/MotionPathPlugin'
|
|
8
|
-
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
9
|
-
import { Flip } from 'gsap/Flip'
|
|
10
|
-
|
|
11
|
-
gsap.registerPlugin(MotionPathPlugin, ScrollTrigger, Flip)
|
|
12
|
-
export default function GsapPlugin(app, name) {
|
|
13
|
-
app.config.globalProperties.$gsap = gsap
|
|
14
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Integrating gsap library as Vue plug in
|
|
3
|
+
* usage: import GsapPlugin from './utilies.js';
|
|
4
|
+
* ref: https: //vuejsdevelopers.com/2017/04/22/vue-js-libraries-plugins/
|
|
5
|
+
*/
|
|
6
|
+
import gsap from 'gsap'
|
|
7
|
+
import { MotionPathPlugin } from 'gsap/MotionPathPlugin'
|
|
8
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
9
|
+
import { Flip } from 'gsap/Flip'
|
|
10
|
+
|
|
11
|
+
gsap.registerPlugin(MotionPathPlugin, ScrollTrigger, Flip)
|
|
12
|
+
export default function GsapPlugin(app, name) {
|
|
13
|
+
app.config.globalProperties.$gsap = gsap
|
|
14
|
+
}
|
package/src/plugins/helper.js
CHANGED
|
@@ -90,35 +90,75 @@ export default function helper(app, name) {
|
|
|
90
90
|
* @description Method to return a myme time of a document
|
|
91
91
|
* @param {String} aType extension of the document exemple: doc , pdf ect...
|
|
92
92
|
* @return {String} mime
|
|
93
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types
|
|
93
94
|
*/
|
|
94
95
|
mimeTypeFor(aType) {
|
|
95
96
|
let mimeType = false
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
switch (true) {
|
|
99
|
+
case aType.endsWith('.doc'):
|
|
100
|
+
// Microsoft Word
|
|
98
101
|
mimeType = 'application/msword'
|
|
99
102
|
break
|
|
100
103
|
|
|
101
|
-
case 'docx':
|
|
104
|
+
case aType.endsWith('.docx'):
|
|
105
|
+
// Microsoft Word (OpenXML)
|
|
102
106
|
mimeType =
|
|
103
107
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
104
108
|
break
|
|
105
109
|
|
|
106
|
-
case 'pdf':
|
|
110
|
+
case aType.endsWith('.pdf'):
|
|
111
|
+
//Adobe Portable Document Format (PDF)
|
|
107
112
|
mimeType = 'application/pdf'
|
|
108
113
|
break
|
|
114
|
+
case aType.endsWith('.odt'):
|
|
115
|
+
// OpenDocument text document
|
|
116
|
+
mimeType = 'application/vnd.oasis.opendocument.text'
|
|
117
|
+
break
|
|
109
118
|
|
|
110
|
-
case 'txt':
|
|
119
|
+
case aType.endsWith('.txt'):
|
|
120
|
+
// Text, (generally ASCII or ISO 8859-n)
|
|
111
121
|
mimeType = 'text/plain'
|
|
112
122
|
break
|
|
113
123
|
|
|
114
|
-
case 'jpeg' || 'jpg':
|
|
124
|
+
case aType.endsWith('.jpeg') || aType.endsWith('.jpg'):
|
|
125
|
+
// JPEG images
|
|
115
126
|
mimeType = 'image/jpeg'
|
|
116
127
|
break
|
|
117
128
|
|
|
118
|
-
case 'png':
|
|
129
|
+
case aType.endsWith('.png'):
|
|
130
|
+
// Portable Network Graphics
|
|
119
131
|
mimeType = 'image/png'
|
|
120
132
|
break
|
|
133
|
+
case aType.endsWith('.csv'):
|
|
134
|
+
// Comma-separated values (CSV)
|
|
135
|
+
mimeType = 'text/csv'
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
case aType.endsWith('.xls'):
|
|
139
|
+
// Microsoft Excel
|
|
140
|
+
mimeType = 'application/vnd.ms-excel'
|
|
141
|
+
break
|
|
142
|
+
case aType.endsWith('.xlsx'):
|
|
143
|
+
// Microsoft Excel (OpenXML)
|
|
144
|
+
mimeType =
|
|
145
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
146
|
+
break
|
|
147
|
+
case aType.endsWith('.ppt'):
|
|
148
|
+
//Microsoft PowerPoint
|
|
149
|
+
mimeType = 'application/vnd.ms-powerpoint'
|
|
150
|
+
break
|
|
151
|
+
case aType.endsWith('.pptx'):
|
|
152
|
+
//Microsoft PowerPoint (OpenXML)
|
|
153
|
+
mimeType =
|
|
154
|
+
'tapplication/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
155
|
+
break
|
|
156
|
+
case aType.endsWith('.htm') || aType.endsWith('.html'):
|
|
157
|
+
// HyperText Markup Language (HTML)
|
|
158
|
+
mimeType = 'text/html'
|
|
159
|
+
break
|
|
121
160
|
}
|
|
161
|
+
|
|
122
162
|
return mimeType
|
|
123
163
|
},
|
|
124
164
|
/***
|
|
@@ -131,17 +171,17 @@ export default function helper(app, name) {
|
|
|
131
171
|
* @usage:
|
|
132
172
|
* - case 1: the file is in the public folder of your project. Use full path ex: downloadFile('./public/
|
|
133
173
|
* myfile')
|
|
134
|
-
* - case 2: file is in another folder in a src of your project. Use relative path.
|
|
135
|
-
*
|
|
174
|
+
* - case 2: file is in another folder in a src of your project. Use relative path. import the file and provide it to the * downloader fonction ex: downloadFile(require('@/accets/another_folder
|
|
175
|
+
* )
|
|
136
176
|
*
|
|
137
177
|
*/
|
|
138
178
|
async downloadFile(filepath, outputName) {
|
|
139
179
|
try {
|
|
140
180
|
if (filepath && filepath.constructor === String) {
|
|
141
181
|
const res = await axios.get(filepath, { responseType: 'blob' }) // use http request
|
|
142
|
-
|
|
143
182
|
const blob = new Blob([res.data], {
|
|
144
|
-
type: res.data.type
|
|
183
|
+
// type: res.data.type
|
|
184
|
+
type: this.mimeTypeFor(filepath)
|
|
145
185
|
}) // create a blob element
|
|
146
186
|
|
|
147
187
|
const alink = document.createElement('a') //create a element
|
|
@@ -171,7 +211,7 @@ export default function helper(app, name) {
|
|
|
171
211
|
getKeyFromLocal(key) {
|
|
172
212
|
if (!key) throw new Error('Missing Key for search')
|
|
173
213
|
const store = useAppStore()
|
|
174
|
-
const lang = store
|
|
214
|
+
const lang = store.$state.appConfigs.lang // get lang from the store
|
|
175
215
|
const dic = { en: locale_en, fr: locale_fr } //contents of the json locales
|
|
176
216
|
const keys = key.split('.')
|
|
177
217
|
|
package/src/plugins/i18n.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import _ from 'lodash'
|
|
2
|
-
/**
|
|
3
|
-
* Merge locales message in project.
|
|
4
|
-
* @summary To merge the locales dictionnary of this library with the locales that user will create
|
|
5
|
-
* @param {Object} i18n - vue-i18n internalization object.
|
|
6
|
-
*/
|
|
7
|
-
export default function mergeLocales(i18n) {
|
|
8
|
-
//const deep = import('lodash') // provide deep merge of Object cf: https://attacomsian.com/blog/javascript-merge-objects
|
|
9
|
-
|
|
10
|
-
/* I18n internal methods for mutating messages property
|
|
11
|
-
* mergeLocaleMessage: ƒ mergeLocaleMessage(locale, message)// will merge message with new message object. But does not do a deep merge of object
|
|
12
|
-
* setLocaleMessage: ƒ setLocaleMessage(locale, message)// set the message with new message object
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// This library locales files
|
|
16
|
-
// const thisLocales = require.context(
|
|
17
|
-
// '../$locales',
|
|
18
|
-
// true,
|
|
19
|
-
// /[A-Za-z0-9-_,\s]+\.json$/i
|
|
20
|
-
// )
|
|
21
|
-
|
|
22
|
-
const thisLocales = import.meta.glob('../$locales/*.json', { eager: true })
|
|
23
|
-
for (const path in thisLocales) {
|
|
24
|
-
const matched = path.match(/([A-Za-z0-9-_]+)\./i)
|
|
25
|
-
if (matched && matched.length > 1) {
|
|
26
|
-
const locale = matched[1]
|
|
27
|
-
// Using lodash to deeply merge objects
|
|
28
|
-
const messagesDeepCopy = JSON.parse(JSON.stringify(i18n.messages[locale]))
|
|
29
|
-
const mergedMessages = _.merge(messagesDeepCopy, thisLocales[path])
|
|
30
|
-
// Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
|
|
31
|
-
i18n.setLocaleMessage(locale, mergedMessages)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// thisLocales.keys().forEach((key) => {
|
|
35
|
-
// const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
|
36
|
-
// if (matched && matched.length > 1) {
|
|
37
|
-
// const locale = matched[1]
|
|
38
|
-
// // Using lodash to deeply merge objects
|
|
39
|
-
// const mergedMessages = deep.merge(i18n.messages[locale], thisLocales(key))
|
|
40
|
-
// // Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
|
|
41
|
-
// i18n.setLocaleMessage(locale, mergedMessages)
|
|
42
|
-
// }
|
|
43
|
-
// })
|
|
44
|
-
}
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
/**
|
|
3
|
+
* Merge locales message in project.
|
|
4
|
+
* @summary To merge the locales dictionnary of this library with the locales that user will create
|
|
5
|
+
* @param {Object} i18n - vue-i18n internalization object.
|
|
6
|
+
*/
|
|
7
|
+
export default function mergeLocales(i18n) {
|
|
8
|
+
//const deep = import('lodash') // provide deep merge of Object cf: https://attacomsian.com/blog/javascript-merge-objects
|
|
9
|
+
|
|
10
|
+
/* I18n internal methods for mutating messages property
|
|
11
|
+
* mergeLocaleMessage: ƒ mergeLocaleMessage(locale, message)// will merge message with new message object. But does not do a deep merge of object
|
|
12
|
+
* setLocaleMessage: ƒ setLocaleMessage(locale, message)// set the message with new message object
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// This library locales files
|
|
16
|
+
// const thisLocales = require.context(
|
|
17
|
+
// '../$locales',
|
|
18
|
+
// true,
|
|
19
|
+
// /[A-Za-z0-9-_,\s]+\.json$/i
|
|
20
|
+
// )
|
|
21
|
+
|
|
22
|
+
const thisLocales = import.meta.glob('../$locales/*.json', { eager: true })
|
|
23
|
+
for (const path in thisLocales) {
|
|
24
|
+
const matched = path.match(/([A-Za-z0-9-_]+)\./i)
|
|
25
|
+
if (matched && matched.length > 1) {
|
|
26
|
+
const locale = matched[1]
|
|
27
|
+
// Using lodash to deeply merge objects
|
|
28
|
+
const messagesDeepCopy = JSON.parse(JSON.stringify(i18n.messages[locale]))
|
|
29
|
+
const mergedMessages = _.merge(messagesDeepCopy, thisLocales[path])
|
|
30
|
+
// Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
|
|
31
|
+
i18n.setLocaleMessage(locale, mergedMessages)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// thisLocales.keys().forEach((key) => {
|
|
35
|
+
// const matched = key.match(/([A-Za-z0-9-_]+)\./i)
|
|
36
|
+
// if (matched && matched.length > 1) {
|
|
37
|
+
// const locale = matched[1]
|
|
38
|
+
// // Using lodash to deeply merge objects
|
|
39
|
+
// const mergedMessages = deep.merge(i18n.messages[locale], thisLocales(key))
|
|
40
|
+
// // Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
|
|
41
|
+
// i18n.setLocaleMessage(locale, mergedMessages)
|
|
42
|
+
// }
|
|
43
|
+
// })
|
|
44
|
+
}
|
package/src/plugins/save.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
/* creating a small save plug-in to save store state in the localStorage
|
|
2
|
-
* the plugin listen for mutations made to the Pinia store and store them in the local browser storage.
|
|
3
|
-
* This prevent reseting the store to initial state when the browser refreshes
|
|
4
|
-
* Note: the plugin is used in the store.js
|
|
5
|
-
*/
|
|
6
|
-
export function saveStatePlugin(store) {
|
|
7
|
-
store.subscribe((mutation, state) => {
|
|
8
|
-
// save the user data
|
|
9
|
-
if (Object.entries(state.$appStore.userMetaData).length) {
|
|
10
|
-
localStorage.setItem(
|
|
11
|
-
'userData',
|
|
12
|
-
JSON.stringify(state.$appStore.userMetaData)
|
|
13
|
-
)
|
|
14
|
-
} else if (
|
|
15
|
-
!localStorage.getItem('userData') &&
|
|
16
|
-
!Object.entries(state.$appStore.userMetaData).length
|
|
17
|
-
) {
|
|
18
|
-
localStorage.setItem('userData', JSON.stringify({}))
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* creating a small save plug-in to get stored state from the localStorage
|
|
25
|
-
*/
|
|
26
|
-
export function getSavedStatePlugin(type, id) {
|
|
27
|
-
if (localStorage.getItem('thisModule')) {
|
|
28
|
-
const savedData = JSON.parse(localStorage.getItem('thisModule'))
|
|
29
|
-
|
|
30
|
-
if (type === 'module' && savedData.id === id) return savedData
|
|
31
|
-
if (type === 'page' && savedData.children.length) {
|
|
32
|
-
return savedData.children.find((p) => id === p.id)
|
|
33
|
-
}
|
|
34
|
-
return JSON.parse(localStorage.getItem('thisModule'))
|
|
35
|
-
}
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
1
|
+
/* creating a small save plug-in to save store state in the localStorage
|
|
2
|
+
* the plugin listen for mutations made to the Pinia store and store them in the local browser storage.
|
|
3
|
+
* This prevent reseting the store to initial state when the browser refreshes
|
|
4
|
+
* Note: the plugin is used in the store.js
|
|
5
|
+
*/
|
|
6
|
+
export function saveStatePlugin(store) {
|
|
7
|
+
store.subscribe((mutation, state) => {
|
|
8
|
+
// save the user data
|
|
9
|
+
if (Object.entries(state.$appStore.userMetaData).length) {
|
|
10
|
+
localStorage.setItem(
|
|
11
|
+
'userData',
|
|
12
|
+
JSON.stringify(state.$appStore.userMetaData)
|
|
13
|
+
)
|
|
14
|
+
} else if (
|
|
15
|
+
!localStorage.getItem('userData') &&
|
|
16
|
+
!Object.entries(state.$appStore.userMetaData).length
|
|
17
|
+
) {
|
|
18
|
+
localStorage.setItem('userData', JSON.stringify({}))
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* creating a small save plug-in to get stored state from the localStorage
|
|
25
|
+
*/
|
|
26
|
+
export function getSavedStatePlugin(type, id) {
|
|
27
|
+
if (localStorage.getItem('thisModule')) {
|
|
28
|
+
const savedData = JSON.parse(localStorage.getItem('thisModule'))
|
|
29
|
+
|
|
30
|
+
if (type === 'module' && savedData.id === id) return savedData
|
|
31
|
+
if (type === 'page' && savedData.children.length) {
|
|
32
|
+
return savedData.children.find((p) => id === p.id)
|
|
33
|
+
}
|
|
34
|
+
return JSON.parse(localStorage.getItem('thisModule'))
|
|
35
|
+
}
|
|
36
|
+
return null
|
|
37
|
+
}
|