@windward/core 0.0.1
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 +13 -0
- package/.eslintrc.js +11 -0
- package/.prettierrc +4 -0
- package/README.md +86 -0
- package/babel.config.js +1 -0
- package/components/Content/Blocks/Accordion.vue +133 -0
- package/components/Content/Blocks/ClickableIcons.vue +98 -0
- package/components/Content/Blocks/Feedback.vue +323 -0
- package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionLikert.vue +95 -0
- package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionOpenResponse.vue +45 -0
- package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionTrueFalse.vue +55 -0
- package/components/Content/Blocks/Image.vue +116 -0
- package/components/Content/Blocks/Math.vue +87 -0
- package/components/Content/Blocks/Tab.vue +89 -0
- package/components/Content/Blocks/Table.vue +68 -0
- package/components/Content/Blocks/UserUpload/DisplayUserFilesTable.vue +157 -0
- package/components/Content/Blocks/UserUpload/ManageDataTableUserFiles.vue +68 -0
- package/components/Content/Blocks/UserUpload.vue +240 -0
- package/components/Content/Blocks/Video.vue +245 -0
- package/components/Navigation/Items/CourseGlossaryToolNav.vue +39 -0
- package/components/Navigation/Items/GlossaryNav.vue +32 -0
- package/components/Navigation/Items/UserUploadNav.vue +35 -0
- package/components/Settings/AccordionSettings.vue +153 -0
- package/components/Settings/ClickableIconsSettings.vue +189 -0
- package/components/Settings/FeedbackSettings.vue +92 -0
- package/components/Settings/ImageSettings.vue +124 -0
- package/components/Settings/MathSettings.vue +81 -0
- package/components/Settings/TabSettings.vue +139 -0
- package/components/Settings/TextEditorSettings.vue +249 -0
- package/components/Settings/UserUploadSettings.vue +151 -0
- package/components/Settings/VideoSettings.vue +699 -0
- package/components/utils/ContentViewer.vue +69 -0
- package/components/utils/MathExpressionEditor.vue +294 -0
- package/components/utils/MathLiveWrapper.vue +86 -0
- package/components/utils/TinyMCEWrapper.vue +103 -0
- package/components/utils/glossary/CourseGlossary.vue +284 -0
- package/components/utils/glossary/CourseGlossaryForm.vue +106 -0
- package/components/utils/glossary/GlossaryToolTip.vue +87 -0
- package/config/tinymce.config.js +136 -0
- package/helpers/GlossaryHelper.ts +137 -0
- package/helpers/GlossaryTerm.ts +31 -0
- package/helpers/MathHelper.ts +236 -0
- package/helpers/tinymce/plugin.ts +83 -0
- package/i18n/en-US/components/content/blocks/feedback.ts +28 -0
- package/i18n/en-US/components/content/blocks/image.ts +5 -0
- package/i18n/en-US/components/content/blocks/index.ts +15 -0
- package/i18n/en-US/components/content/blocks/tab.ts +4 -0
- package/i18n/en-US/components/content/blocks/table.ts +4 -0
- package/i18n/en-US/components/content/blocks/user_upload.ts +12 -0
- package/i18n/en-US/components/content/blocks/video.ts +53 -0
- package/i18n/en-US/components/content/index.ts +5 -0
- package/i18n/en-US/components/index.ts +12 -0
- package/i18n/en-US/components/navigation/image.ts +4 -0
- package/i18n/en-US/components/navigation/index.ts +7 -0
- package/i18n/en-US/components/navigation/user_upload.ts +3 -0
- package/i18n/en-US/components/settings/clickable_icon.ts +9 -0
- package/i18n/en-US/components/settings/image.ts +1 -0
- package/i18n/en-US/components/settings/index.ts +13 -0
- package/i18n/en-US/components/settings/text_editor.ts +7 -0
- package/i18n/en-US/components/settings/user_upload.ts +11 -0
- package/i18n/en-US/components/settings/video.ts +13 -0
- package/i18n/en-US/components/utils/index.ts +5 -0
- package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +7 -0
- package/i18n/en-US/index.ts +16 -0
- package/i18n/en-US/modules/index.ts +5 -0
- package/i18n/en-US/pages/glossary.ts +7 -0
- package/i18n/en-US/pages/index.ts +7 -0
- package/i18n/en-US/pages/user_upload.ts +3 -0
- package/i18n/en-US/shared/content_blocks.ts +20 -0
- package/i18n/en-US/shared/index.ts +11 -0
- package/i18n/en-US/shared/menu.ts +3 -0
- package/i18n/en-US/shared/permission.ts +15 -0
- package/i18n/en-US/shared/settings.ts +15 -0
- package/index.js +15 -0
- package/jest.config.js +15 -0
- package/models/SurveyResult.ts +8 -0
- package/models/SurveyTemplate.ts +8 -0
- package/models/UserFileAsset.ts +12 -0
- package/package.json +48 -0
- package/pages/glossary.vue +31 -0
- package/pages/userUpload.vue +204 -0
- package/plugin.js +299 -0
- package/test/Components/Content/Blocks/Accordion.spec.js +21 -0
- package/test/Components/Content/Blocks/ClickableIcons.spec.js +21 -0
- package/test/Components/Content/Blocks/Feedback.spec.js +31 -0
- package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionLikert.spec.js +23 -0
- package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionOpenResponse.spec.js +23 -0
- package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionTrueFalse.spec.js +23 -0
- package/test/Components/Content/Blocks/Image.spec.js +34 -0
- package/test/Components/Content/Blocks/Math.spec.js +34 -0
- package/test/Components/Content/Blocks/Tab.spec.js +20 -0
- package/test/Components/Content/Blocks/Table.spec.js +21 -0
- package/test/Components/Content/Blocks/UserUpload.spec.js +25 -0
- package/test/Components/Content/Blocks/Video.spec.js +112 -0
- package/test/Components/Settings/AccordionSettings.spec.js +45 -0
- package/test/Components/Settings/ClickableIconsSettings.spec.js +20 -0
- package/test/Components/Settings/FeedbackSettings.spec.js +20 -0
- package/test/Components/Settings/ImageSettings.spec.js +20 -0
- package/test/Components/Settings/MathSettings.spec.js +20 -0
- package/test/Components/Settings/TabSettings.spec.js +45 -0
- package/test/Components/Settings/UserUploadSettings.spec.js +20 -0
- package/test/__mocks__/componentsMock.js +83 -0
- package/test/__mocks__/contentBlockMock.js +119 -0
- package/test/__mocks__/contentSettingsMock.js +54 -0
- package/test/__mocks__/fileMock.js +1 -0
- package/test/__mocks__/helpersMock.js +53 -0
- package/test/__mocks__/modelMock.js +82 -0
- package/test/__mocks__/styleMock.js +1 -0
- package/test/helpers/GlossaryHelper.spec.js +227 -0
- package/test/helpers/MathHelper.spec.js +128 -0
- package/test/mocks.js +140 -0
- package/tsconfig.json +15 -0
- package/utils/index.js +18 -0
package/.editorconfig
ADDED
package/.eslintrc.js
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Windward UI Plugin Library
|
|
2
|
+
|
|
3
|
+
Windward UI plugin library implements content components add amplify windward's ability to simplify editor's workflow
|
|
4
|
+
|
|
5
|
+
## Getting Started Locally with npm
|
|
6
|
+
|
|
7
|
+
**Software Requirements**:
|
|
8
|
+
|
|
9
|
+
- Node v16+, npm v8+
|
|
10
|
+
|
|
11
|
+
Clone the windward-ui-plugin-core repository
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ cd windward-ui-plugin-core
|
|
15
|
+
$ npm install
|
|
16
|
+
$ npm link
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Clone the windward-ui repository and follow README to setup
|
|
20
|
+
|
|
21
|
+
Link to Windward ui repo
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
$ cd windward-ui
|
|
25
|
+
$ npm install
|
|
26
|
+
$ cd node_modules
|
|
27
|
+
$ npm link @mindedge/windward-ui-plugin-core
|
|
28
|
+
$ npm run dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Your Repos are linked at this point and you are ready to start developing windward plugins
|
|
32
|
+
|
|
33
|
+
## Code Styles
|
|
34
|
+
|
|
35
|
+
### `import x from 'path'`
|
|
36
|
+
|
|
37
|
+
1. For files in **this repository** use `./` or `../`
|
|
38
|
+
|
|
39
|
+
eg:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
import UserFileAsset from './models/UserFileAsset'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. For files in **the windward-core repository** use `~/`
|
|
46
|
+
|
|
47
|
+
eg:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
import ContentBlockAsset from '~/components/Content/ContentBlockAsset.vue'
|
|
51
|
+
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
3. When **unit testing only** and for files in **this repository** use `./` or `../` or `@/`
|
|
55
|
+
|
|
56
|
+
For ease of use the `@/` alias is available to navigate to the root of this project.
|
|
57
|
+
The `@/` alias is synonymous to the `~/` however it's bound to THIS repository instead of the windward-core repository.
|
|
58
|
+
|
|
59
|
+
eg:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
import UserUpload from '@/components/Content/Blocks/UserUpload.vue'
|
|
63
|
+
```
|
|
64
|
+
4. To add a page use the below format in your `pages: []` in `plugin.js`
|
|
65
|
+
```javascript
|
|
66
|
+
{
|
|
67
|
+
// The name to be used by the vue router. This must be unique
|
|
68
|
+
name: 'PluginUserUploadPage',
|
|
69
|
+
// The vue router path. Use :course to allow the page to be accessed while in a course
|
|
70
|
+
path: '/course/:course/user-uploads',
|
|
71
|
+
// The template to use for the page
|
|
72
|
+
template: UserUploadPage,
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Code of Conduct
|
|
77
|
+
|
|
78
|
+
In order to ensure that the Windward community is welcoming to all we follow the same code of conduct as [Laravel](https://laravel.com/docs/contributions#code-of-conduct).
|
|
79
|
+
|
|
80
|
+
## Security Vulnerabilities
|
|
81
|
+
|
|
82
|
+
If you discover a security vulnerability within Windward, please create a ticket on this repository and tag it as "security"
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Windward LMS is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
package/babel.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = {presets: ['@babel/preset-env']}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-expansion-panels
|
|
4
|
+
flat
|
|
5
|
+
accordion
|
|
6
|
+
focusable
|
|
7
|
+
id="expansion-panel-setting"
|
|
8
|
+
>
|
|
9
|
+
<v-expansion-panel
|
|
10
|
+
v-for="(item, itemIndex) in block.metadata.config.items"
|
|
11
|
+
:key="itemIndex"
|
|
12
|
+
>
|
|
13
|
+
<v-expansion-panel-header class="header" color="primary">
|
|
14
|
+
{{
|
|
15
|
+
item.header === '' || item.header === null
|
|
16
|
+
? 'Item ' + (itemIndex + 1)
|
|
17
|
+
: item.header
|
|
18
|
+
}}
|
|
19
|
+
</v-expansion-panel-header>
|
|
20
|
+
<v-expansion-panel-content
|
|
21
|
+
class="body"
|
|
22
|
+
:key="expansionPanelKey"
|
|
23
|
+
>
|
|
24
|
+
<v-container>
|
|
25
|
+
<TextViewer
|
|
26
|
+
v-if="!item.expand"
|
|
27
|
+
v-model="item.content"
|
|
28
|
+
text-viewer
|
|
29
|
+
></TextViewer>
|
|
30
|
+
<TextEditor
|
|
31
|
+
v-if="!render && item.expand"
|
|
32
|
+
v-model="
|
|
33
|
+
block.metadata.config.items[itemIndex].content
|
|
34
|
+
"
|
|
35
|
+
></TextEditor>
|
|
36
|
+
</v-container>
|
|
37
|
+
</v-expansion-panel-content>
|
|
38
|
+
</v-expansion-panel>
|
|
39
|
+
</v-expansion-panels>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script>
|
|
44
|
+
import _ from 'lodash'
|
|
45
|
+
import Crypto from '~/helpers/Crypto'
|
|
46
|
+
import TextEditor from '~/components/Text/TextEditor.vue'
|
|
47
|
+
import TextViewer from '~/components/Text/TextViewer.vue'
|
|
48
|
+
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
name: 'ContentBlockAccordion',
|
|
52
|
+
components: {
|
|
53
|
+
TextEditor,
|
|
54
|
+
TextViewer,
|
|
55
|
+
},
|
|
56
|
+
extends: BaseContentBlock,
|
|
57
|
+
beforeMount() {
|
|
58
|
+
if (_.isEmpty(this.block)) {
|
|
59
|
+
this.block = {}
|
|
60
|
+
}
|
|
61
|
+
if (_.isEmpty(this.block.metadata)) {
|
|
62
|
+
this.block.metadata = {}
|
|
63
|
+
}
|
|
64
|
+
if (_.isEmpty(this.block.metadata.config)) {
|
|
65
|
+
this.block.metadata.config = {}
|
|
66
|
+
}
|
|
67
|
+
if (_.isEmpty(this.block.metadata.config.items)) {
|
|
68
|
+
const defaultObject = {
|
|
69
|
+
header: '',
|
|
70
|
+
expand: false,
|
|
71
|
+
content: '',
|
|
72
|
+
}
|
|
73
|
+
this.block.metadata.config.items = []
|
|
74
|
+
this.block.metadata.config.items.push(defaultObject)
|
|
75
|
+
}
|
|
76
|
+
this.block.body = 'accordion'
|
|
77
|
+
},
|
|
78
|
+
data() {
|
|
79
|
+
return {
|
|
80
|
+
expansionPanelKey: '0',
|
|
81
|
+
editingInContentItem: false,
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
watch: {
|
|
85
|
+
value(newValue) {
|
|
86
|
+
const length = newValue.metadata.config.items.length
|
|
87
|
+
let counter = 0
|
|
88
|
+
newValue.metadata.config.items.forEach((element) => {
|
|
89
|
+
if (
|
|
90
|
+
element.expand === true &&
|
|
91
|
+
this.editingInContentItem !== true
|
|
92
|
+
) {
|
|
93
|
+
this.editingInContentItem = true
|
|
94
|
+
this.expansionPanelKey = Crypto.id()
|
|
95
|
+
}
|
|
96
|
+
if (element.expand === false) {
|
|
97
|
+
counter = counter + 1
|
|
98
|
+
}
|
|
99
|
+
if (counter === length) {
|
|
100
|
+
this.editingInContentItem = false
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
render() {
|
|
105
|
+
if (this.render === true) {
|
|
106
|
+
this.block.metadata.config.items.forEach((element) => {
|
|
107
|
+
element.expand = false
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
methods: {
|
|
113
|
+
async onBeforeSave() {
|
|
114
|
+
this.block.metadata.config.items.forEach((element) => {
|
|
115
|
+
element.expand = false
|
|
116
|
+
})
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<style scoped>
|
|
123
|
+
.header {
|
|
124
|
+
color: white;
|
|
125
|
+
margin-top: 1px;
|
|
126
|
+
}
|
|
127
|
+
.body {
|
|
128
|
+
background: #f6f9f9;
|
|
129
|
+
}
|
|
130
|
+
#expansion-panel-setting {
|
|
131
|
+
z-index: 0;
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-container>
|
|
4
|
+
<v-row no-gutters>
|
|
5
|
+
<h2>{{ block.metadata.config.title }}</h2>
|
|
6
|
+
</v-row>
|
|
7
|
+
<v-row no-gutters>
|
|
8
|
+
<h3>{{ block.metadata.config.description }}</h3>
|
|
9
|
+
</v-row>
|
|
10
|
+
<v-row no-gutters>
|
|
11
|
+
<p>
|
|
12
|
+
{{
|
|
13
|
+
$t(
|
|
14
|
+
'windward.core.components.settings.clickable_icon.information'
|
|
15
|
+
)
|
|
16
|
+
}}
|
|
17
|
+
</p>
|
|
18
|
+
</v-row>
|
|
19
|
+
</v-container>
|
|
20
|
+
<v-container>
|
|
21
|
+
<v-row
|
|
22
|
+
v-for="(item, itemIndex) in block.metadata.config.items"
|
|
23
|
+
:key="itemIndex"
|
|
24
|
+
no-gutters
|
|
25
|
+
>
|
|
26
|
+
<v-col cols="2">
|
|
27
|
+
<v-btn
|
|
28
|
+
class="pt-8 pb-8 outlined-button mb-4"
|
|
29
|
+
:color="itemColor(item.color)"
|
|
30
|
+
x-large
|
|
31
|
+
outlined
|
|
32
|
+
@click="item.active = !item.active"
|
|
33
|
+
>
|
|
34
|
+
<v-icon>{{ item.icon }}</v-icon>
|
|
35
|
+
</v-btn>
|
|
36
|
+
</v-col>
|
|
37
|
+
<v-col cols="10" v-if="item.active">
|
|
38
|
+
<h4>{{ item.title }}</h4>
|
|
39
|
+
<TextViewer v-model="item.body"></TextViewer>
|
|
40
|
+
</v-col>
|
|
41
|
+
</v-row>
|
|
42
|
+
</v-container>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
<script>
|
|
46
|
+
import _ from 'lodash'
|
|
47
|
+
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
48
|
+
import TextViewer from '~/components/Text/TextViewer'
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
name: 'ClickableIcons',
|
|
52
|
+
components: {
|
|
53
|
+
TextViewer,
|
|
54
|
+
},
|
|
55
|
+
extends: BaseContentBlock,
|
|
56
|
+
computed: {
|
|
57
|
+
itemColor() {
|
|
58
|
+
return (v) => {
|
|
59
|
+
if (_.isObject(v)) {
|
|
60
|
+
return _.get(v, 'class', '')
|
|
61
|
+
} else {
|
|
62
|
+
return v
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
beforeMount() {
|
|
68
|
+
// Apply the default config
|
|
69
|
+
this.block.body = 'clickable icons'
|
|
70
|
+
if (_.isEmpty(this.block.metadata.config.items)) {
|
|
71
|
+
this.block.metadata.config.items = []
|
|
72
|
+
}
|
|
73
|
+
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
74
|
+
this.block.metadata.config.title = ''
|
|
75
|
+
}
|
|
76
|
+
if (_.isEmpty(this.block.metadata.config.description)) {
|
|
77
|
+
this.block.metadata.config.description = ''
|
|
78
|
+
}
|
|
79
|
+
if (this.block.metadata.config.items) {
|
|
80
|
+
this.block.metadata.config.items.forEach((element) => {
|
|
81
|
+
element.active = false
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
data() {
|
|
86
|
+
return {
|
|
87
|
+
api_key: process.env.TINY_MCE_API_KEY,
|
|
88
|
+
title: 'Title',
|
|
89
|
+
displayText: false,
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
<style scoped>
|
|
95
|
+
.outlined-button {
|
|
96
|
+
border-width: 4px;
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<v-container v-if="!doesFeedbackExist">
|
|
4
|
+
<v-row>
|
|
5
|
+
<h2>
|
|
6
|
+
{{
|
|
7
|
+
block.metadata.config.definition.name
|
|
8
|
+
? block.metadata.config.definition.name
|
|
9
|
+
: $t(
|
|
10
|
+
'plugin.core.components.content.blocks.feedback.feedback'
|
|
11
|
+
)
|
|
12
|
+
}}
|
|
13
|
+
</h2>
|
|
14
|
+
</v-row>
|
|
15
|
+
<v-row class="pt-2">
|
|
16
|
+
<p>
|
|
17
|
+
{{
|
|
18
|
+
$t(
|
|
19
|
+
'plugin.core.components.content.blocks.feedback.description'
|
|
20
|
+
)
|
|
21
|
+
}}
|
|
22
|
+
</p>
|
|
23
|
+
</v-row>
|
|
24
|
+
</v-container>
|
|
25
|
+
<v-container v-if="doesFeedbackExist">
|
|
26
|
+
<v-row>
|
|
27
|
+
<h2>
|
|
28
|
+
{{ block.metadata.config.definition.name }}
|
|
29
|
+
</h2>
|
|
30
|
+
</v-row>
|
|
31
|
+
<v-row class="pt-2">
|
|
32
|
+
<p>
|
|
33
|
+
{{
|
|
34
|
+
$t(
|
|
35
|
+
'plugin.core.components.content.blocks.feedback.sent_feedback'
|
|
36
|
+
)
|
|
37
|
+
}}
|
|
38
|
+
</p>
|
|
39
|
+
</v-row>
|
|
40
|
+
</v-container>
|
|
41
|
+
<v-divider class="mt-4 mb-6"></v-divider>
|
|
42
|
+
<v-form
|
|
43
|
+
ref="form"
|
|
44
|
+
v-model="valid"
|
|
45
|
+
lazy-validation
|
|
46
|
+
v-if="!doesFeedbackExist"
|
|
47
|
+
>
|
|
48
|
+
<div v-if="block.metadata.config.definition.metadata">
|
|
49
|
+
<v-container
|
|
50
|
+
v-for="(question, questionIndex) in chosenPreset"
|
|
51
|
+
:key="questionIndex"
|
|
52
|
+
fluid
|
|
53
|
+
>
|
|
54
|
+
<v-container
|
|
55
|
+
v-if="question.type === 'likert'"
|
|
56
|
+
class="pa-0 ma-0"
|
|
57
|
+
>
|
|
58
|
+
<FeedbackQuestionLikert
|
|
59
|
+
v-model="chosenPreset[questionIndex]"
|
|
60
|
+
></FeedbackQuestionLikert>
|
|
61
|
+
</v-container>
|
|
62
|
+
<v-container
|
|
63
|
+
v-if="question.type === 'true_false'"
|
|
64
|
+
class="pa-0 ma-0"
|
|
65
|
+
>
|
|
66
|
+
<FeedbackQuestionTrueFalse
|
|
67
|
+
v-model="chosenPreset[questionIndex]"
|
|
68
|
+
></FeedbackQuestionTrueFalse>
|
|
69
|
+
</v-container>
|
|
70
|
+
<v-container
|
|
71
|
+
v-if="question.type === 'open_response'"
|
|
72
|
+
class="pa-0 ma-0"
|
|
73
|
+
>
|
|
74
|
+
<FeedbackQuestionOpenResponse
|
|
75
|
+
v-model="chosenPreset[questionIndex]"
|
|
76
|
+
></FeedbackQuestionOpenResponse>
|
|
77
|
+
</v-container>
|
|
78
|
+
</v-container>
|
|
79
|
+
</div>
|
|
80
|
+
</v-form>
|
|
81
|
+
<v-row
|
|
82
|
+
class="d-flex justify-end align-center"
|
|
83
|
+
v-if="render && !doesFeedbackExist"
|
|
84
|
+
>
|
|
85
|
+
<v-btn
|
|
86
|
+
color="error"
|
|
87
|
+
class="text-center mt-4 mr-2"
|
|
88
|
+
@click="onReset()"
|
|
89
|
+
>{{
|
|
90
|
+
$t('plugin.core.components.content.blocks.feedback.reset')
|
|
91
|
+
}}</v-btn
|
|
92
|
+
>
|
|
93
|
+
<v-btn color="success" class="text-center mt-4" @click="validate">{{
|
|
94
|
+
$t('plugin.core.components.content.blocks.feedback.save')
|
|
95
|
+
}}</v-btn>
|
|
96
|
+
</v-row>
|
|
97
|
+
<v-form
|
|
98
|
+
ref="form"
|
|
99
|
+
v-model="valid"
|
|
100
|
+
disabled
|
|
101
|
+
lazy-validation
|
|
102
|
+
v-if="doesFeedbackExist"
|
|
103
|
+
>
|
|
104
|
+
<v-container
|
|
105
|
+
v-for="(
|
|
106
|
+
question, questionIndex
|
|
107
|
+
) in existingFeedback.survey_question_answers"
|
|
108
|
+
:key="questionIndex"
|
|
109
|
+
fluid
|
|
110
|
+
>
|
|
111
|
+
<div v-if="question.type === 'likert'">
|
|
112
|
+
<FeedbackQuestionLikert
|
|
113
|
+
v-model="
|
|
114
|
+
existingFeedback.survey_question_answers[
|
|
115
|
+
questionIndex
|
|
116
|
+
]
|
|
117
|
+
"
|
|
118
|
+
></FeedbackQuestionLikert>
|
|
119
|
+
</div>
|
|
120
|
+
<div v-if="question.type === 'true_false'">
|
|
121
|
+
<FeedbackQuestionTrueFalse
|
|
122
|
+
v-model="
|
|
123
|
+
existingFeedback.survey_question_answers[
|
|
124
|
+
questionIndex
|
|
125
|
+
]
|
|
126
|
+
"
|
|
127
|
+
></FeedbackQuestionTrueFalse>
|
|
128
|
+
</div>
|
|
129
|
+
<div v-if="question.type === 'open_response'">
|
|
130
|
+
<FeedbackQuestionOpenResponse
|
|
131
|
+
v-model="
|
|
132
|
+
existingFeedback.survey_question_answers[
|
|
133
|
+
questionIndex
|
|
134
|
+
]
|
|
135
|
+
"
|
|
136
|
+
:feedbackExist="true"
|
|
137
|
+
></FeedbackQuestionOpenResponse>
|
|
138
|
+
</div>
|
|
139
|
+
</v-container>
|
|
140
|
+
</v-form>
|
|
141
|
+
</v-container>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<script>
|
|
145
|
+
import _ from 'lodash'
|
|
146
|
+
import { mapGetters } from 'vuex'
|
|
147
|
+
import FeedbackQuestionLikert from './FeedbackTemplates/FeedbackQuestionLikert.vue'
|
|
148
|
+
import FeedbackQuestionOpenResponse from './FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
|
|
149
|
+
import FeedbackQuestionTrueFalse from './FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
|
|
150
|
+
import Uuid from '~/helpers/Uuid'
|
|
151
|
+
|
|
152
|
+
import SurveyResult from '../../../models/SurveyResult'
|
|
153
|
+
import Enrollment from '~/models/Enrollment'
|
|
154
|
+
import ContentBlock from '~/models/ContentBlock'
|
|
155
|
+
import Crypto from '~/helpers/Crypto'
|
|
156
|
+
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
157
|
+
|
|
158
|
+
export default {
|
|
159
|
+
name: 'Feedback',
|
|
160
|
+
components: {
|
|
161
|
+
FeedbackQuestionLikert,
|
|
162
|
+
FeedbackQuestionOpenResponse,
|
|
163
|
+
FeedbackQuestionTrueFalse,
|
|
164
|
+
},
|
|
165
|
+
extends: BaseContentBlock,
|
|
166
|
+
beforeMount() {
|
|
167
|
+
if (_.isEmpty(this.block)) {
|
|
168
|
+
this.block = {}
|
|
169
|
+
}
|
|
170
|
+
if (_.isEmpty(this.block.metadata)) {
|
|
171
|
+
this.block.metadata = {}
|
|
172
|
+
}
|
|
173
|
+
if (_.isEmpty(this.block.metadata.config)) {
|
|
174
|
+
this.block.metadata.config = {}
|
|
175
|
+
}
|
|
176
|
+
if (_.isEmpty(this.block.metadata.config)) {
|
|
177
|
+
this.block.metadata.config = {}
|
|
178
|
+
}
|
|
179
|
+
if (_.isEmpty(this.block.metadata.config.definition)) {
|
|
180
|
+
this.block.metadata.config.definition = {}
|
|
181
|
+
}
|
|
182
|
+
this.block.body = 'feedback'
|
|
183
|
+
},
|
|
184
|
+
data() {
|
|
185
|
+
return {
|
|
186
|
+
saveState: false,
|
|
187
|
+
courseUserId: '',
|
|
188
|
+
valid: true,
|
|
189
|
+
key: '0',
|
|
190
|
+
existingFeedback: '',
|
|
191
|
+
doesFeedbackExist: false,
|
|
192
|
+
templates: '',
|
|
193
|
+
chosenPreset: '',
|
|
194
|
+
studentResponse: '',
|
|
195
|
+
noResponse: this.$t(
|
|
196
|
+
'plugin.core.components.content.blocks.feedback.no_response_entered'
|
|
197
|
+
),
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
computed: {
|
|
201
|
+
...mapGetters({
|
|
202
|
+
enrollment: 'enrollment/get',
|
|
203
|
+
course: 'course/get',
|
|
204
|
+
}),
|
|
205
|
+
blockExists() {
|
|
206
|
+
return Uuid.test(this.block.id)
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
watch: {
|
|
210
|
+
value(newValue) {
|
|
211
|
+
if (
|
|
212
|
+
newValue.metadata.config.definition &&
|
|
213
|
+
newValue.metadata.config.definition.metadata &&
|
|
214
|
+
newValue.metadata.config.definition.metadata.definition &&
|
|
215
|
+
!this.render
|
|
216
|
+
) {
|
|
217
|
+
this.setUpBlock()
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
mounted() {
|
|
222
|
+
if (this.blockExists) {
|
|
223
|
+
this.courseUserId = this.enrollment.id
|
|
224
|
+
this.onCheckForFeedback()
|
|
225
|
+
if (_.isEmpty(this.chosenPreset)) {
|
|
226
|
+
this.setUpBlock()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
methods: {
|
|
231
|
+
setUpBlock() {
|
|
232
|
+
if (this.block.metadata.config.definition.metadata) {
|
|
233
|
+
this.block.metadata.config.definition.metadata.definition.forEach(
|
|
234
|
+
(element) => {
|
|
235
|
+
element.response = ''
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
this.chosenPreset =
|
|
239
|
+
this.block.metadata.config.definition.metadata.definition
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
async onCheckForFeedback() {
|
|
243
|
+
if (this.enrollment && this.block.id) {
|
|
244
|
+
this.existingFeedback = await new SurveyResult()
|
|
245
|
+
.for(
|
|
246
|
+
new Enrollment({ id: this.enrollment.id }),
|
|
247
|
+
new ContentBlock({ id: this.block.id })
|
|
248
|
+
)
|
|
249
|
+
.first()
|
|
250
|
+
}
|
|
251
|
+
if (!_.isEmpty(this.existingFeedback)) {
|
|
252
|
+
this.handleExistingFeedback()
|
|
253
|
+
} else {
|
|
254
|
+
this.onReset()
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
handleExistingFeedback() {
|
|
258
|
+
this.existingFeedback.survey_question_answers = JSON.parse(
|
|
259
|
+
this.existingFeedback.survey_question_answers
|
|
260
|
+
)
|
|
261
|
+
this.existingFeedback.survey_snapshot = JSON.parse(
|
|
262
|
+
this.existingFeedback.survey_snapshot
|
|
263
|
+
)
|
|
264
|
+
this.doesFeedbackExist = true
|
|
265
|
+
},
|
|
266
|
+
async onSaveFeedback() {
|
|
267
|
+
let version =
|
|
268
|
+
this.block.metadata.config.definition.id +
|
|
269
|
+
this.block.metadata.config.definition.updated_at
|
|
270
|
+
const feedbackData = {
|
|
271
|
+
survey_snapshot:
|
|
272
|
+
this.block.metadata.config.definition.metadata.definition,
|
|
273
|
+
survey_question_answers: this.chosenPreset,
|
|
274
|
+
version: version,
|
|
275
|
+
}
|
|
276
|
+
let feedback = new SurveyResult(feedbackData).for(
|
|
277
|
+
new Enrollment({ id: this.enrollment.id }),
|
|
278
|
+
new ContentBlock({ id: this.block.id })
|
|
279
|
+
)
|
|
280
|
+
try {
|
|
281
|
+
feedback = await feedback.save()
|
|
282
|
+
this.$toast.success(this.$t('shared.forms.saved'))
|
|
283
|
+
this.onCheckForFeedback()
|
|
284
|
+
} catch (e) {
|
|
285
|
+
this.$toast.error(this.$t('shared.forms.errors.unknown'))
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async validate() {
|
|
289
|
+
const valid = await this.$refs.form.validate()
|
|
290
|
+
if (valid) {
|
|
291
|
+
this.onSaveFeedback()
|
|
292
|
+
} else {
|
|
293
|
+
this.$toast.error(
|
|
294
|
+
this.$t(
|
|
295
|
+
'plugin.core.components.content.blocks.feedback.fill_out'
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
onReset() {
|
|
301
|
+
this.existingFeedback = null
|
|
302
|
+
if (this.chosenPreset) {
|
|
303
|
+
this.chosenPreset.forEach((element) => {
|
|
304
|
+
element.response = ''
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
this.doesFeedbackExist = false
|
|
308
|
+
this.key = Crypto.id()
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
</script>
|
|
313
|
+
<style scoped>
|
|
314
|
+
.question {
|
|
315
|
+
font-weight: bold;
|
|
316
|
+
}
|
|
317
|
+
.radio-group {
|
|
318
|
+
width: 100% !important;
|
|
319
|
+
}
|
|
320
|
+
.likert-radio {
|
|
321
|
+
margin-left: 55px;
|
|
322
|
+
}
|
|
323
|
+
</style>
|