@processmaker/screen-builder 2.99.3 → 3.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 (48) hide show
  1. package/dist/vue-form-builder.css +1 -1
  2. package/dist/vue-form-builder.es.js +9091 -7132
  3. package/dist/vue-form-builder.es.js.map +1 -1
  4. package/dist/vue-form-builder.umd.js +53 -53
  5. package/dist/vue-form-builder.umd.js.map +1 -1
  6. package/package.json +2 -1
  7. package/src/App.vue +14 -2
  8. package/src/DataProvider.js +42 -1
  9. package/src/VariableDataTypeProperties.js +1 -1
  10. package/src/components/ClipboardButton.vue +77 -0
  11. package/src/components/CssIcon.vue +21 -0
  12. package/src/components/ScreenTemplateCard.vue +257 -0
  13. package/src/components/ScreenTemplates.vue +216 -0
  14. package/src/components/ScreenToolbar.vue +24 -2
  15. package/src/components/SelectUserGroup.vue +274 -0
  16. package/src/components/TabsBar.vue +47 -1
  17. package/src/components/accordions.js +7 -1
  18. package/src/components/editor/loop.vue +22 -1
  19. package/src/components/editor/multi-column.vue +22 -2
  20. package/src/components/editor/pagesDropdown.vue +20 -2
  21. package/src/components/index.js +7 -1
  22. package/src/components/inspector/collection-data-source.vue +200 -0
  23. package/src/components/inspector/collection-display-mode.vue +87 -0
  24. package/src/components/inspector/collection-records-list.vue +156 -0
  25. package/src/components/inspector/column-setup.vue +123 -7
  26. package/src/components/inspector/encrypted-config.vue +78 -0
  27. package/src/components/inspector/index.js +4 -0
  28. package/src/components/inspector/page-select.vue +1 -0
  29. package/src/components/renderer/file-upload.vue +136 -3
  30. package/src/components/renderer/form-collection-record-control.vue +248 -0
  31. package/src/components/renderer/form-collection-view-control.vue +236 -0
  32. package/src/components/renderer/form-masked-input.vue +194 -9
  33. package/src/components/renderer/form-record-list.vue +271 -69
  34. package/src/components/renderer/index.js +2 -0
  35. package/src/components/screen-renderer.vue +2 -0
  36. package/src/components/task.vue +2 -1
  37. package/src/components/vue-form-builder.vue +156 -22
  38. package/src/components/vue-form-renderer.vue +10 -2
  39. package/src/form-builder-controls.js +168 -21
  40. package/src/global-properties.js +8 -0
  41. package/src/main.js +60 -1
  42. package/src/mixins/Clipboard.js +153 -0
  43. package/src/mixins/ScreenBase.js +7 -1
  44. package/src/mixins/index.js +1 -0
  45. package/src/store/modules/ClipboardManager.js +79 -0
  46. package/src/store/modules/clipboardModule.js +210 -0
  47. package/src/stories/ClipboardButton.stories.js +66 -0
  48. package/src/stories/PagesDropdown.stories.js +11 -8
@@ -4,7 +4,7 @@
4
4
  <b-button
5
5
  class="screen-toolbar-button"
6
6
  variant="link"
7
- :disabled="!canUndo"
7
+ :disabled="!canUndo || disabled"
8
8
  data-cy="toolbar-undo"
9
9
  @click="$emit('undo')"
10
10
  >
@@ -14,17 +14,27 @@
14
14
  <b-button
15
15
  class="screen-toolbar-button"
16
16
  variant="link"
17
- :disabled="!canRedo"
17
+ :disabled="!canRedo || disabled"
18
18
  data-cy="toolbar-redo"
19
19
  @click="$emit('redo')"
20
20
  >
21
21
  <i class="fas fa-redo" />
22
22
  {{ $t("Redo") }}
23
23
  </b-button>
24
+ <b-button
25
+ class="screen-toolbar-button"
26
+ variant="link"
27
+ data-cy="screen-templates"
28
+ @click="$emit('open-templates')"
29
+ >
30
+ <i class="fas fa-palette" />
31
+ {{ $t("Templates") }}
32
+ </b-button>
24
33
  <b-button
25
34
  type="button"
26
35
  class="screen-toolbar-button"
27
36
  variant="link"
37
+ :disabled="disabled"
28
38
  :title="$t('Calculated Properties')"
29
39
  data-cy="topbar-calcs"
30
40
  @click="$emit('open-calc')"
@@ -36,6 +46,7 @@
36
46
  type="button"
37
47
  class="screen-toolbar-button"
38
48
  variant="link"
49
+ :disabled="disabled"
39
50
  :title="$t('Custom CSS')"
40
51
  data-cy="topbar-css"
41
52
  @click="$emit('open-customCss')"
@@ -47,6 +58,7 @@
47
58
  type="button"
48
59
  class="screen-toolbar-button"
49
60
  variant="link"
61
+ :disabled="disabled"
50
62
  :title="$t('Watchers')"
51
63
  data-cy="topbar-watchers"
52
64
  @click="$emit('open-watchers')"
@@ -58,6 +70,7 @@
58
70
  type="button"
59
71
  class="screen-toolbar-button"
60
72
  variant="outlined-secondary"
73
+ :disabled="disabled"
61
74
  :popper-opts="{ placement: 'bottom-end' }"
62
75
  data-cy="topbar-options"
63
76
  >
@@ -77,6 +90,15 @@
77
90
 
78
91
  <script>
79
92
  export default {
93
+ props: {
94
+ /**
95
+ * Whether the toolbar is disabled or not
96
+ */
97
+ disabled: {
98
+ type: Boolean,
99
+ default: false
100
+ }
101
+ },
80
102
  data() {
81
103
  return {
82
104
  showToolbar: true
@@ -0,0 +1,274 @@
1
+ <template>
2
+ <div class="form-group">
3
+ <label v-if="label">{{ label }}</label>
4
+ <multiselect
5
+ :id="'category-select-' + _uid"
6
+ v-model="content"
7
+ track-by="id"
8
+ label="fullname"
9
+ group-values="items"
10
+ group-label="type"
11
+ :class="{'border border-danger':error}"
12
+ :loading="loading"
13
+ :placeholder="placeholder ? placeholder : $t('type here to search')"
14
+ :options="options"
15
+ :multiple="multiple"
16
+ :show-labels="false"
17
+ :searchable="true"
18
+ :internal-search="false"
19
+ @open="load(null)"
20
+ @input="updateSeletected"
21
+ @search-change="load">
22
+
23
+ <template slot="noResult">
24
+ <slot name="noResult">{{ $t("No elements found. Consider changing the search query.") }}</slot>
25
+ </template>
26
+ <template slot="noOptions">
27
+ <slot name="noOptions">{{ $t("No Data Available") }}</slot>
28
+ </template>
29
+ </multiselect>
30
+
31
+ <small v-if="error" class="text-danger">{{ error }}</small>
32
+ <small v-if="helper" class="form-text text-muted">{{ $t(helper) }}</small>
33
+
34
+ </div>
35
+
36
+ </template>
37
+
38
+ <script>
39
+ export default {
40
+ props: {
41
+ value: null,
42
+ label: {
43
+ type: String,
44
+ default: ""
45
+ },
46
+ multiple: {
47
+ type: Boolean,
48
+ default: true
49
+ },
50
+ hideUsers: {
51
+ type: Boolean,
52
+ default: false
53
+ },
54
+ hideGroups: {
55
+ type: Boolean,
56
+ default: false
57
+ },
58
+ error: String,
59
+ helper: String,
60
+ placeholder: String,
61
+ },
62
+ data () {
63
+ return {
64
+ loading: false,
65
+ selected: {
66
+ users: [],
67
+ groups: []
68
+ },
69
+ options: [],
70
+ results: [],
71
+ lastEmitted: "",
72
+ labelUsers: this.$t("Users"),
73
+ labelGroups: this.$t("Groups"),
74
+ };
75
+ },
76
+ computed: {
77
+ content: {
78
+ get () {
79
+ if (this.loading) {
80
+ return [];
81
+ }
82
+ return this.selected.users.map(user => {
83
+ let uid;
84
+ if (typeof user === 'number') {
85
+ uid = user;
86
+ } else {
87
+ uid = user.id;
88
+ }
89
+ return this.results.find(item => item.id === uid);
90
+ })
91
+ .concat(this.selected.groups.map(group => {
92
+ let gid;
93
+ if (typeof group == 'number') {
94
+ gid = "group-" + group;
95
+ } else {
96
+ gid = group.id;
97
+ }
98
+ return this.results.find(item => item.id === gid);
99
+
100
+ }));
101
+ },
102
+ set (value) {
103
+ this.selected.users = [];
104
+ this.selected.groups = [];
105
+ if (value === null) {
106
+ return;
107
+ }
108
+
109
+ // If it is array (this happens when the Select User/Group is selected)
110
+ // add value just if it is not empty
111
+ if (Array.isArray(value) && value.length) {
112
+ value.forEach(item => {
113
+ this.results.push(item);
114
+ if (typeof item.id === "number") {
115
+ this.selected.users.push(item.id);
116
+ } else {
117
+ this.selected.groups.push(parseInt(item.id.substr(6)));
118
+ }
119
+ });
120
+ }
121
+
122
+ //If an object arrives as value (this happens with Self Service and assign by expression)
123
+ if (!Array.isArray(value) && value)
124
+ {
125
+ this.results.push(value);
126
+ if (typeof value.id === "number") {
127
+ this.selected.users.push(value);
128
+ } else {
129
+ this.selected.groups.push(value);
130
+ }
131
+ }
132
+
133
+ }
134
+ }
135
+ },
136
+ watch: {
137
+ value: {
138
+ immediate: true,
139
+ deep: true,
140
+ handler (value) {
141
+ if (!value) {
142
+ return
143
+ }
144
+ if (value.users.length === 0 && value.groups.length === 0) {
145
+ return;
146
+ }
147
+ if (JSON.stringify(value) == this.lastEmitted) {
148
+ return;
149
+ }
150
+ this.loading = true;
151
+ let results = [];
152
+
153
+ let usersPromise = Promise.all(
154
+ value.users.map(item => {
155
+ if (typeof item == 'number' || typeof item == 'string') {
156
+ return ProcessMaker.apiClient.get("users/" + item);
157
+ } else {
158
+ if (item.assignee) {
159
+ let id = item.assignee;
160
+ return ProcessMaker.apiClient.get("users/" + id);
161
+ }
162
+ }
163
+ })
164
+ )
165
+ .then(items => {
166
+ items.forEach(item => {
167
+ results.push(this.addUsernameToFullName(item.data));
168
+ });
169
+ });
170
+
171
+ let groupsPromise = Promise.all(
172
+ value.groups.map(item => {
173
+ if (typeof item == 'number' || typeof item == 'string' ) {
174
+ return ProcessMaker.apiClient.get("groups/" + item);
175
+ } else {
176
+ if (item.assignee) {
177
+ let id = this.unformatGroup(item.assignee);
178
+ return ProcessMaker.apiClient.get("groups/" + id);
179
+ }
180
+ }
181
+ })
182
+ )
183
+ .then(items => {
184
+ items.forEach(item => {
185
+ results.push(this.formatGroup(item.data));
186
+ });
187
+ });
188
+
189
+ Promise.all([usersPromise, groupsPromise])
190
+ .then(() => {
191
+ this.content = results;
192
+ this.loading = false;
193
+ });
194
+ }
195
+ }
196
+ },
197
+ methods: {
198
+ updateSeletected() {
199
+ this.lastEmitted = JSON.stringify(this.selected);
200
+ this.$emit("input", this.selected);
201
+ },
202
+ addUsernameToFullName(user) {
203
+ if (!user.fullname || ! user.username)
204
+ {
205
+ return user;
206
+ }
207
+ let status = '';
208
+ if (user.status === 'INACTIVE') {
209
+ status = " - " + this.$t('Inactive');
210
+ }
211
+ return {...user, fullname: `${user.fullname} (${user.username}${status})`};
212
+ },
213
+ load (filter) {
214
+ this.options = [];
215
+ if (!this.hideUsers) {
216
+ this.loadUsers(filter);
217
+ }
218
+ if (!this.hideGroups) {
219
+ this.loadGroups(filter);
220
+ }
221
+ },
222
+ loadUsers (filter) {
223
+ ProcessMaker.apiClient
224
+ .get("users" + (typeof filter === "string" ? "?filter=" + filter : ""))
225
+ .then(response => {
226
+ const users = response.data.data.map(user => this.addUsernameToFullName(user));
227
+ this.users = users;
228
+
229
+ if (response.data.data) {
230
+ this.options.push({
231
+ "type": this.labelUsers,
232
+ "items": users,
233
+ });
234
+ }
235
+ });
236
+ },
237
+ loadGroups (filter) {
238
+ ProcessMaker.apiClient
239
+ .get("groups" + (typeof filter === "string" ? "?filter=" + filter : ""))
240
+ .then(response => {
241
+ let groups = response.data.data.map(item => {
242
+ return this.formatGroup(item);
243
+ });
244
+
245
+ if (groups) {
246
+ this.options.push({
247
+ "type": this.labelGroups,
248
+ "items": groups
249
+ });
250
+ }
251
+ });
252
+ },
253
+ formatGroup (item) {
254
+ if (item && typeof item.id == 'number') {
255
+ item.id = "group-" + item.id;
256
+ }
257
+ item.fullname = item.name;
258
+ if (item.status === 'INACTIVE') {
259
+ item.fullname += " (" + this.$t('Inactive') + ")";
260
+ }
261
+ return item;
262
+ },
263
+ unformatGroup(groupId) {
264
+ if (typeof groupId == 'number') {
265
+ return groupId;
266
+ } else {
267
+ let id = groupId.replace('group-', "");
268
+ return id;
269
+ }
270
+
271
+ }
272
+ },
273
+ };
274
+ </script>
@@ -58,6 +58,29 @@
58
58
  </div>
59
59
  </template>
60
60
  </b-tab>
61
+ <b-tab
62
+ v-if="showClipboard"
63
+ ref="clipboard"
64
+ class="h-100 w-100"
65
+ name="clipboard"
66
+ >
67
+ <template #title>
68
+ {{ $t('Clipboard') }}
69
+ <span
70
+ :data-test="`close-clipboard-tab`"
71
+ class="close-tab"
72
+ role="link"
73
+ @click.stop="closeClipboard"
74
+ >
75
+ <i class="fas fa-times" />
76
+ </span>
77
+ </template>
78
+ <template #default>
79
+ <div class="h-100 w-100" data-test="tab-content">
80
+ <slot :current-page="clipboardPageIndex" />
81
+ </div>
82
+ </template>
83
+ </b-tab>
61
84
  <template #tabs-end>
62
85
  <div
63
86
  v-if="tabsListOverflow"
@@ -115,6 +138,13 @@ export default {
115
138
  isMultiPage: {
116
139
  type: Boolean,
117
140
  default: true
141
+ },
142
+ /**
143
+ * Show clipboard tab
144
+ */
145
+ showClipboard: {
146
+ type: Boolean,
147
+ default: false
118
148
  }
119
149
  },
120
150
  data() {
@@ -128,6 +158,9 @@ export default {
128
158
  };
129
159
  },
130
160
  computed: {
161
+ clipboardPageIndex() {
162
+ return this.pages.length;
163
+ },
131
164
  validLocalOpenedPages() {
132
165
  return this.localOpenedPages.filter((page) => this.pages[page]);
133
166
  }
@@ -170,9 +203,22 @@ export default {
170
203
  this.checkTabsOverflow();
171
204
  },
172
205
  methods: {
206
+ openClipboard() {
207
+ if (this.$refs.clipboard) {
208
+ this.$refs.clipboard.activate();
209
+ }
210
+ },
211
+ closeClipboard() {
212
+ this.$emit('close-clipboard');
213
+ },
173
214
  tabOpened() {
174
215
  const pageIndex = this.localOpenedPages[this.activeTab];
175
- this.$emit("tab-opened", pageIndex);
216
+ const isInClipboard = (this.activeTab === this.$refs.tabs.tabs.length - 1) && this.showClipboard;
217
+ if (isInClipboard) {
218
+ this.$emit("tab-opened", this.clipboardPageIndex);
219
+ } else {
220
+ this.$emit("tab-opened", pageIndex);
221
+ }
176
222
  },
177
223
  pageNumber(index) {
178
224
  return index + 1;
@@ -13,7 +13,11 @@ export default [
13
13
  'initiallyChecked',
14
14
  'screen',
15
15
  'multipleUpload',
16
- 'linkUrl'
16
+ 'linkUrl',
17
+ 'collection',
18
+ 'record',
19
+ 'collectionmode',
20
+ 'submitCollectionCheck',
17
21
  ],
18
22
  open: true,
19
23
  },
@@ -47,6 +51,7 @@ export default [
47
51
  },
48
52
  fields: [
49
53
  'fields',
54
+ 'paginationOption',
50
55
  { name: 'options', hideFor: 'FormMultiColumn' },
51
56
  ],
52
57
  open: false,
@@ -80,6 +85,7 @@ export default [
80
85
  {name: 'tabindex', showFor: 'FormSelectList'},
81
86
  {name: 'tabindex', showFor: 'FormButton'},
82
87
  {name: 'tabindex', showFor: 'FormTextArea'},
88
+ {name: 'encryptedConfig', showFor: 'FormInput'},
83
89
  ],
84
90
  open: false,
85
91
  },
@@ -28,6 +28,15 @@
28
28
  />
29
29
  {{ element.config.name || $t("Variable Name") }}
30
30
  <div class="ml-auto">
31
+ <clipboard-button
32
+ :index="index"
33
+ :config="element.config"
34
+ :isInClipboard="isInClipboard(items[index])"
35
+ :addTitle="$t('Add to clipboard')"
36
+ :removeTitle="$t('Remove from clipboard')"
37
+ @addToClipboard="addToClipboard(items[index])"
38
+ @removeFromClipboard="removeFromClipboard(items[index])"
39
+ />
31
40
  <button
32
41
  v-if="isAiSection(element) && aiPreview(element)"
33
42
  class="btn btn-sm btn-primary mr-2"
@@ -83,6 +92,15 @@
83
92
  />
84
93
  {{ element.config.name || $t("Variable Name") }}
85
94
  <div class="ml-auto">
95
+ <clipboard-button
96
+ :index="index"
97
+ :config="element.config"
98
+ :isInClipboard="isInClipboard(items[index])"
99
+ :addTitle="$t('Add to clipboard')"
100
+ :removeTitle="$t('Remove from clipboard')"
101
+ @addToClipboard="addToClipboard(items[index])"
102
+ @removeFromClipboard="removeFromClipboard(items[index])"
103
+ />
86
104
  <button
87
105
  class="btn btn-sm btn-secondary mr-2"
88
106
  :title="$t('Copy Control')"
@@ -136,6 +154,8 @@ import {
136
154
  FormTextArea
137
155
  } from "@processmaker/vue-form-elements";
138
156
  import { HasColorProperty } from "@/mixins";
157
+ import { Clipboard } from "@/mixins";
158
+ import ClipboardButton from '../ClipboardButton.vue';
139
159
  import * as renderer from "@/components/renderer";
140
160
 
141
161
  export default {
@@ -149,9 +169,10 @@ export default {
149
169
  FormDatePicker,
150
170
  FormHtmlEditor,
151
171
  FormHtmlViewer,
172
+ ClipboardButton,
152
173
  ...renderer
153
174
  },
154
- mixins: [HasColorProperty],
175
+ mixins: [HasColorProperty, Clipboard],
155
176
  props: ["value", "name", "config", "selected", "validationErrors"],
156
177
  data() {
157
178
  return {
@@ -43,6 +43,15 @@
43
43
  />
44
44
  {{ element.config.name || $t("Variable Name") }}
45
45
  <div class="ml-auto">
46
+ <clipboard-button
47
+ :index="index"
48
+ :config="element.config"
49
+ :isInClipboard="isInClipboard(element)"
50
+ :addTitle="$t('Add to clipboard')"
51
+ :removeTitle="$t('Remove from clipboard')"
52
+ @addToClipboard="addToClipboard(element)"
53
+ @removeFromClipboard="removeFromClipboard(element)"
54
+ />
46
55
  <button
47
56
  v-if="isAiSection(element) && aiPreview(element)"
48
57
  class="btn btn-sm btn-primary mr-2"
@@ -100,6 +109,15 @@
100
109
  />
101
110
  {{ element.config.name || $t("Variable Name") }}
102
111
  <div class="ml-auto">
112
+ <clipboard-button
113
+ :index="index"
114
+ :config="element.config"
115
+ :isInClipboard="isInClipboard(element)"
116
+ :addTitle="$t('Add to clipboard')"
117
+ :removeTitle="$t('Remove from clipboard')"
118
+ @addToClipboard="addToClipboard(element)"
119
+ @removeFromClipboard="removeFromClipboard(element)"
120
+ />
103
121
  <button
104
122
  class="btn btn-sm btn-secondary mr-2"
105
123
  :title="$t('Copy Control')"
@@ -156,8 +174,9 @@ import {
156
174
  FormTextArea
157
175
  } from "@processmaker/vue-form-elements";
158
176
  import { HasColorProperty } from "@/mixins";
177
+ import { Clipboard } from "@/mixins";
159
178
  import * as renderer from "@/components/renderer";
160
-
179
+ import ClipboardButton from '../ClipboardButton.vue';
161
180
  const defaultColumnWidth = 1;
162
181
 
163
182
  export default {
@@ -171,9 +190,10 @@ export default {
171
190
  FormDatePicker,
172
191
  FormHtmlEditor,
173
192
  FormHtmlViewer,
193
+ ClipboardButton,
174
194
  ...renderer
175
195
  },
176
- mixins: [HasColorProperty],
196
+ mixins: [HasColorProperty, Clipboard],
177
197
  props: ["value", "name", "config", "selected", "validationErrors"],
178
198
  data() {
179
199
  return {
@@ -19,7 +19,7 @@
19
19
  <!-- Option to add a new page -->
20
20
  <b-dropdown-item data-test="add-page" @click="onAddPage">
21
21
  <!-- Icon for adding a new page -->
22
- <i class="fa fa-plus platform-dropdown-item-icon"></i>
22
+ <i class="fas fa-plus platform-dropdown-item-icon text-dark w-icon text-center"></i>
23
23
  <!-- Text for adding a new page -->
24
24
  {{ $t("Create Page") }}
25
25
  </b-dropdown-item>
@@ -27,11 +27,19 @@
27
27
  <!-- Option to see all pages -->
28
28
  <b-dropdown-item data-test="see-all-pages" @click="onSeeAllPages">
29
29
  <!-- Icon for seeing all pages -->
30
- <i class="fa fa-eye platform-dropdown-item-icon"></i>
30
+ <i class="far fa-eye platform-dropdown-item-icon text-dark w-icon text-center"></i>
31
31
  <!-- Text for seeing all pages -->
32
32
  {{ $t("See all pages") }}
33
33
  </b-dropdown-item>
34
34
 
35
+ <!-- Option to open Clipboard page -->
36
+ <b-dropdown-item data-test="clipboard" @click="onClipboard" class="d-flex">
37
+ <!-- Icon for clipboard -->
38
+ <i class="far fa-clipboard platform-dropdown-item-icon text-dark w-icon text-center"></i>
39
+ <!-- Text for clipboard -->
40
+ {{ $t("Clipboard") }}
41
+ </b-dropdown-item>
42
+
35
43
  <!-- Divider between adding and viewing options -->
36
44
  <b-dropdown-divider></b-dropdown-divider>
37
45
 
@@ -100,6 +108,13 @@ export default {
100
108
  this.$emit("seeAllPages");
101
109
  },
102
110
 
111
+ /**
112
+ * Handle when user clicks on "Clipboard
113
+ */
114
+ onClipboard() {
115
+ this.$emit("clipboard");
116
+ },
117
+
103
118
  /**
104
119
  * Handler for when a specific page is clicked.
105
120
  * Emits the "clickPage" event with the selected page.
@@ -122,4 +137,7 @@ export default {
122
137
  // Style for the icons in dropdown items.
123
138
  color: #1572c2;
124
139
  }
140
+ .w-icon {
141
+ width: 1.25rem;
142
+ }
125
143
  </style>
@@ -29,6 +29,7 @@ import { LRUCache } from "lru-cache";
29
29
  import Vuex from "vuex";
30
30
  import globalErrorsModule from "../store/modules/globalErrorsModule";
31
31
  import undoRedoModule from "../store/modules/undoRedoModule";
32
+ import clipboardModule from "../store/modules/clipboardModule";
32
33
  import BasicSearch from "./basic-search.vue";
33
34
  import ComputedProperties from "./computed-properties.vue";
34
35
  import CustomCSS from "./custom-css.vue";
@@ -49,6 +50,8 @@ import accordions from "@/components/accordions";
49
50
  import VariableNameGenerator from "@/components/VariableNameGenerator";
50
51
  import { LinkButton } from "./renderer";
51
52
  import "../assets/css/tabs.css";
53
+ import FormCollectionRecordControl from "./renderer/form-collection-record-control.vue";
54
+ import FormCollectionViewControl from "./renderer/form-collection-view-control.vue";
52
55
 
53
56
  const rendererComponents = {
54
57
  ...renderer,
@@ -163,11 +166,14 @@ export default {
163
166
  Vue.use(Vuex);
164
167
  Vue.component("FormListTable", FormListTable);
165
168
  Vue.component("LinkButton", LinkButton);
169
+ Vue.component("FormCollectionRecordControl", FormCollectionRecordControl);
170
+ Vue.component("FormCollectionViewControl", FormCollectionViewControl);
166
171
  const store = new Vuex.Store({
167
172
  modules: {
168
173
  globalErrorsModule,
169
174
  // @todo Improve how to load this module, it is used only in the form builder, not used in the form renderer.
170
- undoRedoModule
175
+ undoRedoModule,
176
+ clipboardModule
171
177
  }
172
178
  });
173
179
  Vue.mixin({ store });