@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.
Files changed (113) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintrc.js +11 -0
  3. package/.prettierrc +4 -0
  4. package/README.md +86 -0
  5. package/babel.config.js +1 -0
  6. package/components/Content/Blocks/Accordion.vue +133 -0
  7. package/components/Content/Blocks/ClickableIcons.vue +98 -0
  8. package/components/Content/Blocks/Feedback.vue +323 -0
  9. package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionLikert.vue +95 -0
  10. package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionOpenResponse.vue +45 -0
  11. package/components/Content/Blocks/FeedbackTemplates/FeedbackQuestionTrueFalse.vue +55 -0
  12. package/components/Content/Blocks/Image.vue +116 -0
  13. package/components/Content/Blocks/Math.vue +87 -0
  14. package/components/Content/Blocks/Tab.vue +89 -0
  15. package/components/Content/Blocks/Table.vue +68 -0
  16. package/components/Content/Blocks/UserUpload/DisplayUserFilesTable.vue +157 -0
  17. package/components/Content/Blocks/UserUpload/ManageDataTableUserFiles.vue +68 -0
  18. package/components/Content/Blocks/UserUpload.vue +240 -0
  19. package/components/Content/Blocks/Video.vue +245 -0
  20. package/components/Navigation/Items/CourseGlossaryToolNav.vue +39 -0
  21. package/components/Navigation/Items/GlossaryNav.vue +32 -0
  22. package/components/Navigation/Items/UserUploadNav.vue +35 -0
  23. package/components/Settings/AccordionSettings.vue +153 -0
  24. package/components/Settings/ClickableIconsSettings.vue +189 -0
  25. package/components/Settings/FeedbackSettings.vue +92 -0
  26. package/components/Settings/ImageSettings.vue +124 -0
  27. package/components/Settings/MathSettings.vue +81 -0
  28. package/components/Settings/TabSettings.vue +139 -0
  29. package/components/Settings/TextEditorSettings.vue +249 -0
  30. package/components/Settings/UserUploadSettings.vue +151 -0
  31. package/components/Settings/VideoSettings.vue +699 -0
  32. package/components/utils/ContentViewer.vue +69 -0
  33. package/components/utils/MathExpressionEditor.vue +294 -0
  34. package/components/utils/MathLiveWrapper.vue +86 -0
  35. package/components/utils/TinyMCEWrapper.vue +103 -0
  36. package/components/utils/glossary/CourseGlossary.vue +284 -0
  37. package/components/utils/glossary/CourseGlossaryForm.vue +106 -0
  38. package/components/utils/glossary/GlossaryToolTip.vue +87 -0
  39. package/config/tinymce.config.js +136 -0
  40. package/helpers/GlossaryHelper.ts +137 -0
  41. package/helpers/GlossaryTerm.ts +31 -0
  42. package/helpers/MathHelper.ts +236 -0
  43. package/helpers/tinymce/plugin.ts +83 -0
  44. package/i18n/en-US/components/content/blocks/feedback.ts +28 -0
  45. package/i18n/en-US/components/content/blocks/image.ts +5 -0
  46. package/i18n/en-US/components/content/blocks/index.ts +15 -0
  47. package/i18n/en-US/components/content/blocks/tab.ts +4 -0
  48. package/i18n/en-US/components/content/blocks/table.ts +4 -0
  49. package/i18n/en-US/components/content/blocks/user_upload.ts +12 -0
  50. package/i18n/en-US/components/content/blocks/video.ts +53 -0
  51. package/i18n/en-US/components/content/index.ts +5 -0
  52. package/i18n/en-US/components/index.ts +12 -0
  53. package/i18n/en-US/components/navigation/image.ts +4 -0
  54. package/i18n/en-US/components/navigation/index.ts +7 -0
  55. package/i18n/en-US/components/navigation/user_upload.ts +3 -0
  56. package/i18n/en-US/components/settings/clickable_icon.ts +9 -0
  57. package/i18n/en-US/components/settings/image.ts +1 -0
  58. package/i18n/en-US/components/settings/index.ts +13 -0
  59. package/i18n/en-US/components/settings/text_editor.ts +7 -0
  60. package/i18n/en-US/components/settings/user_upload.ts +11 -0
  61. package/i18n/en-US/components/settings/video.ts +13 -0
  62. package/i18n/en-US/components/utils/index.ts +5 -0
  63. package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +7 -0
  64. package/i18n/en-US/index.ts +16 -0
  65. package/i18n/en-US/modules/index.ts +5 -0
  66. package/i18n/en-US/pages/glossary.ts +7 -0
  67. package/i18n/en-US/pages/index.ts +7 -0
  68. package/i18n/en-US/pages/user_upload.ts +3 -0
  69. package/i18n/en-US/shared/content_blocks.ts +20 -0
  70. package/i18n/en-US/shared/index.ts +11 -0
  71. package/i18n/en-US/shared/menu.ts +3 -0
  72. package/i18n/en-US/shared/permission.ts +15 -0
  73. package/i18n/en-US/shared/settings.ts +15 -0
  74. package/index.js +15 -0
  75. package/jest.config.js +15 -0
  76. package/models/SurveyResult.ts +8 -0
  77. package/models/SurveyTemplate.ts +8 -0
  78. package/models/UserFileAsset.ts +12 -0
  79. package/package.json +48 -0
  80. package/pages/glossary.vue +31 -0
  81. package/pages/userUpload.vue +204 -0
  82. package/plugin.js +299 -0
  83. package/test/Components/Content/Blocks/Accordion.spec.js +21 -0
  84. package/test/Components/Content/Blocks/ClickableIcons.spec.js +21 -0
  85. package/test/Components/Content/Blocks/Feedback.spec.js +31 -0
  86. package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionLikert.spec.js +23 -0
  87. package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionOpenResponse.spec.js +23 -0
  88. package/test/Components/Content/Blocks/FeedbackTemplates/FeedbackQuestionTrueFalse.spec.js +23 -0
  89. package/test/Components/Content/Blocks/Image.spec.js +34 -0
  90. package/test/Components/Content/Blocks/Math.spec.js +34 -0
  91. package/test/Components/Content/Blocks/Tab.spec.js +20 -0
  92. package/test/Components/Content/Blocks/Table.spec.js +21 -0
  93. package/test/Components/Content/Blocks/UserUpload.spec.js +25 -0
  94. package/test/Components/Content/Blocks/Video.spec.js +112 -0
  95. package/test/Components/Settings/AccordionSettings.spec.js +45 -0
  96. package/test/Components/Settings/ClickableIconsSettings.spec.js +20 -0
  97. package/test/Components/Settings/FeedbackSettings.spec.js +20 -0
  98. package/test/Components/Settings/ImageSettings.spec.js +20 -0
  99. package/test/Components/Settings/MathSettings.spec.js +20 -0
  100. package/test/Components/Settings/TabSettings.spec.js +45 -0
  101. package/test/Components/Settings/UserUploadSettings.spec.js +20 -0
  102. package/test/__mocks__/componentsMock.js +83 -0
  103. package/test/__mocks__/contentBlockMock.js +119 -0
  104. package/test/__mocks__/contentSettingsMock.js +54 -0
  105. package/test/__mocks__/fileMock.js +1 -0
  106. package/test/__mocks__/helpersMock.js +53 -0
  107. package/test/__mocks__/modelMock.js +82 -0
  108. package/test/__mocks__/styleMock.js +1 -0
  109. package/test/helpers/GlossaryHelper.spec.js +227 -0
  110. package/test/helpers/MathHelper.spec.js +128 -0
  111. package/test/mocks.js +140 -0
  112. package/tsconfig.json +15 -0
  113. package/utils/index.js +18 -0
@@ -0,0 +1,245 @@
1
+ <template>
2
+ <div>
3
+ <v-card v-if="render && !hasSource">
4
+ <v-card-title>
5
+ <v-icon class="mr-2">mdi-cloud-question</v-icon>
6
+ {{
7
+ $t(
8
+ 'windward.core.components.content.blocks.video.video.not_configured_title'
9
+ )
10
+ }}
11
+ </v-card-title>
12
+ <v-card-text>{{
13
+ $t(
14
+ 'windward.core.components.content.blocks.video.video.edit_prompt'
15
+ )
16
+ }}</v-card-text>
17
+ </v-card>
18
+
19
+ <v-skeleton-loader
20
+ v-if="!render && !hasSource"
21
+ type="image, image, list-item-avatar"
22
+ class="reload-skeleton"
23
+ ></v-skeleton-loader>
24
+ <h2 v-if="block.metadata.config.title" class="pl-4">
25
+ {{ block.metadata.config.title }}
26
+ </h2>
27
+ <VuetifyPlayer
28
+ v-if="hasSource"
29
+ :language="$i18n && $i18n.locale ? $i18n.locale : 'en-US'"
30
+ :type="block.metadata.config.type"
31
+ :playlist="block.metadata.config.playlist"
32
+ :autoplay="block.metadata.config.attributes.autoplay"
33
+ :autopictureinpicture="
34
+ block.metadata.config.attributes.autopictureinpicture
35
+ "
36
+ :controls="block.metadata.config.attributes.controls"
37
+ :controlslist="block.metadata.config.attributes.controlslist"
38
+ :crossorigin="block.metadata.config.attributes.crossorigin"
39
+ :disablepictureinpicture="
40
+ block.metadata.config.attributes.disablepictureinpicture
41
+ "
42
+ :disableremoteplayback="
43
+ block.metadata.config.attributes.disableremoteplayback
44
+ "
45
+ :height="block.metadata.config.attributes.height"
46
+ :width="block.metadata.config.attributes.width"
47
+ :rewind="block.metadata.config.attributes.rewind"
48
+ :loop="block.metadata.config.attributes.loop"
49
+ :muted="block.metadata.config.attributes.muted"
50
+ :playsinline="block.metadata.config.attributes.playsinline"
51
+ :poster="block.metadata.config.attributes.poster"
52
+ :preload="block.metadata.config.attributes.preload"
53
+ :captionsmenu="block.metadata.config.attributes.captionsmenu"
54
+ :playlistmenu="block.metadata.config.attributes.playlistmenu"
55
+ :playlistautoadvance="
56
+ block.metadata.config.attributes.playlistautoadvance
57
+ "
58
+ :playbackrates="block.metadata.config.attributes.playbackrates"
59
+ />
60
+ </div>
61
+ </template>
62
+
63
+ <script>
64
+ import _ from 'lodash'
65
+ import VuetifyPlayer from '@mindedge/vuetify-player'
66
+ import ContentBlockAsset from '~/components/Content/ContentBlockAsset.vue'
67
+ import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
68
+
69
+ export default {
70
+ name: 'ContentBlockVideo',
71
+ components: {
72
+ VuetifyPlayer,
73
+ ContentBlockAsset,
74
+ },
75
+ extends: BaseContentBlock,
76
+ computed: {
77
+ hasSource() {
78
+ // __isDirty is used to communicate from the settings panel that a source / caption
79
+ // has changed and to reload the video player
80
+ if (this.block.metadata.config.__isDirty) {
81
+ return false
82
+ }
83
+
84
+ return !_.isEmpty(
85
+ _.get(
86
+ this.block.metadata.config,
87
+ 'playlist[0].sources[0]',
88
+ null
89
+ )
90
+ )
91
+ },
92
+ },
93
+ data() {
94
+ return {
95
+ saveState: false,
96
+
97
+ fileTab: null,
98
+
99
+ saveKey: null, // Used for checking changes in settings
100
+ // Default config settings
101
+ defaultConfig: {
102
+ // Default settings for new blocks
103
+ // This will be overridden in beforeMount()
104
+ type: 'video', // Allowed video|audio. In audio mode the player has a max-height of 40px
105
+ attributes: {
106
+ autoplay: false, // Autoplay on load. It's in the spec but DON'T USE THIS
107
+ autopictureinpicture: false, // Start with picture in picture mode
108
+ controls: true, // Show video controls. When false only play/pause allowed but clicking on the video itself
109
+ controlslist: 'nodownload noremoteplayback', // Space separated string per <video>. Allowed 'nodownload nofullscreen noremoteplayback'
110
+ crossorigin: 'anonymous',
111
+ disablepictureinpicture: true, // Shows the picture in picture button
112
+ disableremoteplayback: true, // Shows the remote playback button but functionality does not exist when clicked
113
+ height: 'auto',
114
+ width: '100%',
115
+ rewind: true, // Enabled the rewind 10s button
116
+ loop: false, // Loop the video on completion
117
+ muted: false, // Start the video muted
118
+ playsinline: false, // Force inline & disable fullscreen
119
+ poster: '', // Overridden with the playlist.poster if one is set there
120
+ preload: '',
121
+ captionsmenu: true, // Show the captions below the video
122
+ playlistmenu: true, // Show the playlist menu if there's multiple videos
123
+ playlistautoadvance: true, // Play the next source group
124
+ playbackrates: [0.5, 1, 1.5, 2], // Default playback speeds
125
+ },
126
+ playlist: [
127
+ /*{
128
+ name: '', // The video name to appear on playlists
129
+ poster: '', // A video specific poster in the playlist
130
+ ads: [
131
+ {
132
+ play_at_percent: 0,
133
+ sources: [
134
+ {
135
+ src: "https://domain.test/ad_example.mp4",
136
+ type: "video/mp4",
137
+ },
138
+ ],
139
+ tracks: [
140
+ {
141
+ src: "https://domain.test/ad_example-US.vtt",
142
+ kind: "captions",
143
+ srclang: "en-US",
144
+ default: true,
145
+ },
146
+ ],
147
+ },
148
+ ],
149
+ sources: [
150
+ {
151
+ src: "https://domain.test/example.mp4",
152
+ type: "video/mp4",
153
+ },
154
+ ],
155
+ tracks: [
156
+ {
157
+ src: "https://domain.test/example_en-US.vtt",
158
+ kind: "captions",
159
+ srclang: "en-US",
160
+ default: true,
161
+ },
162
+ ],
163
+ },*/
164
+ ],
165
+ },
166
+ }
167
+ },
168
+ beforeMount() {
169
+ // Apply the default config
170
+ if (_.isEmpty(this.block.metadata.config)) {
171
+ this.block.metadata.config = this.defaultConfig
172
+ }
173
+ },
174
+ mounted() {
175
+ // TODO Check privs
176
+ //const hasPrivs = true
177
+ //if (hasPrivs) {
178
+ //this.blockSettings = this.initEditorSettings(this.settings)
179
+ //}
180
+ this.saveKey = this.settingsKey()
181
+ },
182
+ methods: {
183
+ /**
184
+ * Hash out the settings to see if there's any changes
185
+ */
186
+ settingsKey() {
187
+ const str = JSON.stringify(this.block.metadata.config)
188
+ return str.split('').reduce(function (a, b) {
189
+ a = (a << 5) - a + b.charCodeAt(0)
190
+ return a & a
191
+ }, 0)
192
+ },
193
+ async onActionPanelEdit() {
194
+ // Push any setting changes up
195
+ await this.onBeforeSave
196
+
197
+ // We're moving from edit mode to render mode
198
+ // Prompt about any unsaved changes
199
+ if (!this.render && this.settingsKey() !== this.saveKey) {
200
+ const self = this
201
+ this.$toast.info(this.$t('shared.forms.unsaved'), {
202
+ icon: 'mdi-help',
203
+ duration: null,
204
+ action: [
205
+ {
206
+ text: this.$t('shared.forms.cancel'),
207
+ onClick: (e, toastObject) => {
208
+ toastObject.goAway(0)
209
+ self.render = true
210
+ },
211
+ },
212
+ {
213
+ text: this.$t('shared.forms.confirm'),
214
+ // router navigation
215
+ onClick: async (e, toastObject) => {
216
+ await this.onBeforeSave()
217
+ // Saved, rehash the setting changes key
218
+ this.saveKey = this.settingsKey()
219
+
220
+ self.render = true
221
+ toastObject.goAway(0)
222
+ },
223
+ },
224
+ ],
225
+ })
226
+ } else {
227
+ this.render = !this.render
228
+ }
229
+ },
230
+ async onBeforeSave() {
231
+ this.block.body = 'video'
232
+ },
233
+ onAfterSave() {
234
+ this.saveKey = this.settingsKey()
235
+ },
236
+ },
237
+ }
238
+ </script>
239
+
240
+ <style scoped>
241
+ .reload-skeleton {
242
+ aspect-ratio: 16 / 9;
243
+ overflow: hidden;
244
+ }
245
+ </style>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <v-list-item @click="dialog = true">
3
+ <v-list-item-action>
4
+ <v-icon>{{ config.icon }}</v-icon>
5
+ </v-list-item-action>
6
+ <v-list-item-content>
7
+ <v-list-item-title>{{ $t(config.i18n) }}</v-list-item-title>
8
+ <Dialog v-model="dialog" color="primary" text :trigger="false">
9
+ <template #title>{{ $t(config.i18n) }}</template>
10
+ <template #form="{ on, attrs }">
11
+ <div v-bind="attrs" v-on="on">
12
+ <CourseGlossary />
13
+ </div>
14
+ </template>
15
+ </Dialog>
16
+ </v-list-item-content>
17
+ </v-list-item>
18
+ </template>
19
+
20
+ <script>
21
+ import CourseGlossary from '../../utils/glossary/CourseGlossary.vue'
22
+ export default {
23
+ name: 'NavigationItemCourseGlossary',
24
+ components: { CourseGlossary },
25
+ props: {
26
+ config: { type: Object, required: true },
27
+ },
28
+ data() {
29
+ return {
30
+ dialog: false,
31
+ }
32
+ },
33
+ methods: {
34
+ close() {
35
+ this.dialog = false
36
+ },
37
+ },
38
+ }
39
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <v-list-item :to="'/course/' + course.id + '/glossary'">
3
+ <v-list-item-action>
4
+ <v-icon>mdi-comment-text-multiple</v-icon>
5
+ </v-list-item-action>
6
+ <v-list-item-content>
7
+ <v-list-item-title
8
+ >{{ $t('windward.core.shared.menu.course_glossary') }}
9
+ </v-list-item-title>
10
+ </v-list-item-content>
11
+ </v-list-item>
12
+ </template>
13
+
14
+ <script>
15
+ import { mapGetters } from 'vuex'
16
+
17
+ export default {
18
+ name: 'GlossaryNav',
19
+ layout: 'course',
20
+ middleware: ['auth'],
21
+ data() {
22
+ return {}
23
+ },
24
+ computed: {
25
+ ...mapGetters({
26
+ course: 'course/get',
27
+ }),
28
+ },
29
+ }
30
+ </script>
31
+
32
+ <style scoped></style>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-list-item :to="'/course/' + course.id + '/user-uploads'">
3
+ <v-list-item-action>
4
+ <v-icon>mdi-cloud-upload</v-icon>
5
+ </v-list-item-action>
6
+ <v-list-item-content>
7
+ <v-list-item-title
8
+ >{{
9
+ $t('windward.core.components.navigation.user_upload.title')
10
+ }}
11
+ </v-list-item-title>
12
+ </v-list-item-content>
13
+ </v-list-item>
14
+ </template>
15
+
16
+ <script>
17
+ import { mapGetters } from 'vuex'
18
+
19
+ export default {
20
+ components: {},
21
+ layout: 'course',
22
+ middleware: ['auth'],
23
+ data() {
24
+ return {}
25
+ },
26
+ computed: {
27
+ ...mapGetters({
28
+ course: 'course/get',
29
+ }),
30
+ },
31
+ created() {},
32
+ mounted() {},
33
+ methods: {},
34
+ }
35
+ </script>
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <div>
3
+ <v-container class="pa-0">
4
+ <v-btn
5
+ color="primary"
6
+ outlined
7
+ @click="onAddElement"
8
+ class="fullWidth"
9
+ ><v-icon>mdi-plus</v-icon>{{ $t('shared.forms.add') }}</v-btn
10
+ >
11
+ <v-expansion-panels flat>
12
+ <v-expansion-panel
13
+ v-for="(item, itemIndex) in block.metadata.config.items"
14
+ :key="itemIndex"
15
+ class="elevation-0"
16
+ >
17
+ <v-expansion-panel-header class="elevation-0">{{
18
+ 'item ' + (itemIndex + 1)
19
+ }}</v-expansion-panel-header>
20
+ <v-expansion-panel-content
21
+ class="elevation-0"
22
+ :key="expansionPanelKey"
23
+ >
24
+ <v-text-field
25
+ v-model="
26
+ block.metadata.config.items[itemIndex].header
27
+ "
28
+ :label="'Item Title'"
29
+ ></v-text-field>
30
+ <v-btn
31
+ @click="
32
+ onToggleExpand(
33
+ block.metadata.config.items[itemIndex]
34
+ )
35
+ "
36
+ >
37
+ <v-icon
38
+ v-if="
39
+ !block.metadata.config.items[itemIndex]
40
+ .expand
41
+ "
42
+ >mdi-arrow-expand-all</v-icon
43
+ >
44
+ <v-icon
45
+ v-if="
46
+ block.metadata.config.items[itemIndex]
47
+ .expand
48
+ "
49
+ >
50
+ mdi-arrow-collapse-all
51
+ </v-icon>
52
+ </v-btn>
53
+ <TextEditor
54
+ :api_key="api_key"
55
+ v-show="
56
+ !block.metadata.config.items[itemIndex].expand
57
+ "
58
+ v-model="
59
+ block.metadata.config.items[itemIndex].content
60
+ "
61
+ ></TextEditor>
62
+ <br />
63
+ <v-btn
64
+ class="mt-2 fullWidth"
65
+ color="primary"
66
+ outlined
67
+ @click="onRemoveElement(itemIndex)"
68
+ ><v-icon>mdi-delete-outline</v-icon
69
+ >{{ $t('shared.forms.delete') }}</v-btn
70
+ >
71
+ </v-expansion-panel-content>
72
+ </v-expansion-panel>
73
+ </v-expansion-panels>
74
+ </v-container>
75
+ </div>
76
+ </template>
77
+ <script>
78
+ import _ from 'lodash'
79
+ import Crypto from '~/helpers/Crypto'
80
+ import BaseContentSettings from '~/components/Content/Tool/BaseContentSettings.js'
81
+ import TextEditor from '~/components/Text/TextEditor'
82
+
83
+ export default {
84
+ name: 'AccordionSettings',
85
+ components: {
86
+ TextEditor,
87
+ },
88
+ extends: BaseContentSettings,
89
+ beforeMount() {
90
+ if (_.isEmpty(this.block)) {
91
+ this.block = {}
92
+ }
93
+ if (_.isEmpty(this.block.metadata)) {
94
+ this.block.metadata = {}
95
+ }
96
+ if (_.isEmpty(this.block.metadata.config)) {
97
+ this.block.metadata.config = {}
98
+ }
99
+ if (_.isEmpty(this.block.metadata.config.editOnContentItem)) {
100
+ this.block.metadata.config.editOnContentItem = false
101
+ }
102
+ if (_.isEmpty(this.block.metadata.config.items)) {
103
+ const defaultObject = {
104
+ header: '',
105
+ expand: false,
106
+ content: '',
107
+ }
108
+ this.block.metadata.config.items = []
109
+ this.block.metadata.config.items.push(defaultObject)
110
+ }
111
+ this.block.body = 'accordion'
112
+ },
113
+ data() {
114
+ return {
115
+ api_key: process.env.TINY_MCE_API_KEY,
116
+ expansionPanelKey: '0',
117
+ }
118
+ },
119
+ mounted() {},
120
+ beforeDestroy() {
121
+ if (this.debouncer) {
122
+ clearTimeout(this.debouncer)
123
+ }
124
+ },
125
+
126
+ methods: {
127
+ onAddElement() {
128
+ const default_item = {
129
+ header: '',
130
+ expand: false,
131
+ content: '',
132
+ }
133
+ this.block.metadata.config.items.push(default_item)
134
+ },
135
+ onRemoveElement(index) {
136
+ this.block.metadata.config.items.splice(index, 1)
137
+ this.expansionPanelKey = Crypto.id()
138
+ },
139
+ onToggleExpand(item) {
140
+ this.expansionPanelKey = Crypto.id()
141
+ return (item.expand = !item.expand)
142
+ },
143
+ },
144
+ }
145
+ </script>
146
+ <style scoped>
147
+ .v-progress-circular {
148
+ margin: 1rem;
149
+ }
150
+ .fullWidth {
151
+ width: 100%;
152
+ }
153
+ </style>
@@ -0,0 +1,189 @@
1
+ <template>
2
+ <div>
3
+ <v-container>
4
+ <v-col>
5
+ <v-text-field
6
+ v-model="block.metadata.config.title"
7
+ :label="
8
+ $t(
9
+ 'windward.core.components.settings.clickable_icon.title'
10
+ )
11
+ "
12
+ ></v-text-field>
13
+ <v-text-field
14
+ v-model="block.metadata.config.description"
15
+ :label="
16
+ $t(
17
+ 'windward.core.components.settings.clickable_icon.description'
18
+ )
19
+ "
20
+ ></v-text-field>
21
+ <v-btn color="primary" outlined @click="onAddElement"
22
+ ><v-icon>mdi-plus</v-icon
23
+ >{{ $t('shared.forms.add') }}</v-btn
24
+ >
25
+ </v-col>
26
+ <v-expansion-panels flat>
27
+ <v-expansion-panel
28
+ v-for="(item, itemIndex) in block.metadata.config.items"
29
+ :key="itemIndex"
30
+ class="elevation-0"
31
+ >
32
+ <v-expansion-panel-header class="elevation-0">{{
33
+ 'item ' + (itemIndex + 1)
34
+ }}</v-expansion-panel-header>
35
+ <v-expansion-panel-content
36
+ class="elevation-0"
37
+ :key="expansionPanelKey"
38
+ >
39
+ <v-text-field
40
+ :counter="1"
41
+ maxlength="1"
42
+ v-model="
43
+ block.metadata.config.items[itemIndex].icon
44
+ "
45
+ :label="
46
+ $t(
47
+ 'windward.core.components.settings.clickable_icon.item_icon',
48
+ [itemIndex + 1]
49
+ )
50
+ "
51
+ ></v-text-field>
52
+ <v-text-field
53
+ v-model="
54
+ block.metadata.config.items[itemIndex].title
55
+ "
56
+ :label="
57
+ $t(
58
+ 'windward.core.components.settings.clickable_icon.item_title'
59
+ )
60
+ "
61
+ ></v-text-field>
62
+
63
+ <ColorPicker
64
+ v-model="
65
+ block.metadata.config.items[itemIndex].color
66
+ "
67
+ :label="
68
+ $t(
69
+ 'windward.core.components.settings.clickable_icon.item_color'
70
+ )
71
+ "
72
+ ></ColorPicker>
73
+ <v-divider class="mt-3 mb-3" />
74
+ <span>{{
75
+ $t(
76
+ 'windward.core.components.settings.clickable_icon.item_text'
77
+ )
78
+ }}</span>
79
+ <TextEditor
80
+ :api_key="api_key"
81
+ v-model="
82
+ block.metadata.config.items[itemIndex].body
83
+ "
84
+ ></TextEditor>
85
+ <v-divider class="mt-3 mb-3" />
86
+ <v-btn
87
+ class="mt-2"
88
+ color="primary"
89
+ block
90
+ outlined
91
+ @click="onRemoveElement(itemIndex)"
92
+ ><v-icon>mdi-delete-outline</v-icon
93
+ >{{ $t('shared.forms.delete') }}</v-btn
94
+ >
95
+ </v-expansion-panel-content>
96
+ </v-expansion-panel>
97
+ </v-expansion-panels>
98
+ </v-container>
99
+ </div>
100
+ </template>
101
+ <script>
102
+ import _ from 'lodash'
103
+ import Crypto from '~/helpers/Crypto'
104
+ import BaseContentSettings from '~/components/Content/Tool/BaseContentSettings.js'
105
+ import TextEditor from '~/components/Text/TextEditor'
106
+ import ColorPicker from '~/components/ColorPicker.vue'
107
+ export default {
108
+ name: 'ClickableIconsSettings',
109
+ components: {
110
+ TextEditor,
111
+ ColorPicker,
112
+ },
113
+ extends: BaseContentSettings,
114
+ beforeMount() {
115
+ if (_.isEmpty(this.block)) {
116
+ this.block = {}
117
+ }
118
+ if (_.isEmpty(this.block.body)) {
119
+ this.block.body = 'clickable icons'
120
+ }
121
+ if (_.isEmpty(this.block.metadata)) {
122
+ this.block.metadata = {}
123
+ }
124
+ if (_.isEmpty(this.block.metadata.config)) {
125
+ this.block.metadata.config = {}
126
+ }
127
+ if (_.isEmpty(this.block.metadata.config.title)) {
128
+ this.block.metadata.config.title = ''
129
+ }
130
+ if (_.isEmpty(this.block.metadata.config.items)) {
131
+ this.block.metadata.config.items = []
132
+ }
133
+ },
134
+ data() {
135
+ return {
136
+ valid: true,
137
+ debounce: null,
138
+ loading: false,
139
+ api_key: process.env.TINY_MCE_API_KEY,
140
+ title: '',
141
+ description: '',
142
+ items: [
143
+ {
144
+ icon: '',
145
+ title: '',
146
+ body: '',
147
+ active: false,
148
+ },
149
+ ],
150
+ expansionPanelKey: '0',
151
+ }
152
+ },
153
+ mounted() {
154
+ if (this.block.metadata.config.items.length <= 0) {
155
+ this.onAddElement()
156
+ }
157
+ },
158
+ beforeDestroy() {
159
+ if (this.debouncer) {
160
+ clearTimeout(this.debouncer)
161
+ }
162
+ },
163
+
164
+ methods: {
165
+ async onBeforeSave() {
166
+ this.block.metadata.config.items = this.items
167
+ },
168
+ onAddElement() {
169
+ const defaultObject = {
170
+ icon: '',
171
+ title: '',
172
+ body: '',
173
+ color: {},
174
+ active: false,
175
+ }
176
+ this.block.metadata.config.items.push(defaultObject)
177
+ },
178
+ onRemoveElement(index) {
179
+ this.block.metadata.config.items.splice(index, 1)
180
+ this.expansionPanelKey = Crypto.id()
181
+ },
182
+ },
183
+ }
184
+ </script>
185
+ <style scoped>
186
+ .v-progress-circular {
187
+ margin: 1rem;
188
+ }
189
+ </style>