@processmaker/screen-builder 3.8.7 → 3.8.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@processmaker/screen-builder",
3
- "version": "3.8.7",
3
+ "version": "3.8.9",
4
4
  "scripts": {
5
5
  "dev": "VITE_COVERAGE=true vite",
6
6
  "build": "vite build",
@@ -34,6 +34,7 @@
34
34
  "@chantouchsek/validatorjs": "1.2.3",
35
35
  "@storybook/addon-docs": "^7.6.13",
36
36
  "axios-extensions": "^3.1.6",
37
+ "flatted": "^3.3.3",
37
38
  "lodash": "^4.17.21",
38
39
  "lru-cache": "^10.0.1",
39
40
  "moment": "^2.30.1",
@@ -58,7 +58,7 @@ export default [
58
58
  },
59
59
  {
60
60
  name: 'Design',
61
- fields: ['color', 'bgcolor', 'variant', 'toggle', 'height', 'width', 'designerMode', 'bgcolormodern'],
61
+ fields: ['color', 'bgcolor', 'variant', 'variantStyle', 'toggle', 'height', 'width', 'designerMode', 'bgcolormodern'],
62
62
  open: false,
63
63
  },
64
64
  {
@@ -0,0 +1,377 @@
1
+ <template>
2
+ <div class="column-draggable" :selector="config.customCssSelector">
3
+ <draggable
4
+ style="min-height: 80px"
5
+ :list="items"
6
+ group="controls"
7
+ @change="onChange"
8
+ >
9
+ <div
10
+ v-for="(element, index) in items"
11
+ :key="index"
12
+ class="control-item"
13
+ :class="{ selected: selected === element, hasError: hasError(element) }"
14
+ @click.stop="inspect(element)"
15
+ >
16
+ <div v-if="element.container" @click.stop="inspect(element)">
17
+ <div
18
+ class="m-2 card border-0"
19
+ :class="{
20
+ 'ai-section-card': isAiSection(element) && selected === element
21
+ }"
22
+ >
23
+ <div
24
+ v-if="selected === element"
25
+ class="card-header form-element-header d-flex align-items-center border rounded"
26
+ :class="{ pulse: isAiSection(element) }"
27
+ >
28
+ <i class="fas fa-arrows-alt-v mr-1 text-muted" />
29
+ <i
30
+ v-if="element.config.icon"
31
+ :class="element.config.icon"
32
+ class="mr-2 ml-1"
33
+ />
34
+ {{ element.config.name || $t("Variable Name") }}
35
+ <b-badge
36
+ v-if="isInClipboard(items[index]) && screenType === 'form'"
37
+ data-cy="copied-badge"
38
+ class="m-2 custom-badge"
39
+ pill
40
+ >
41
+ <i class="far fa-check-circle"></i>
42
+ <span class="pl-2">{{ $t('Copied')}}</span>
43
+ </b-badge>
44
+ <div class="ml-auto">
45
+ <clipboard-button
46
+ v-if="screenType === 'form'"
47
+ :index="index"
48
+ :config="element.config"
49
+ :isInClipboard="isInClipboard(items[index])"
50
+ :addTitle="$t('Add to clipboard')"
51
+ :removeTitle="$t('Remove from clipboard')"
52
+ @addToClipboard="addToClipboard(items[index])"
53
+ @removeFromClipboard="removeFromClipboard(items[index])"
54
+ />
55
+ <button
56
+ v-if="isAiSection(element) && aiPreview(element)"
57
+ class="btn btn-sm btn-primary mr-2"
58
+ :title="$t('Apply Changes')"
59
+ @click="applyAiChanges(element)"
60
+ >
61
+ {{ $t("Apply Changes") }}
62
+ </button>
63
+ <button
64
+ v-if="!(isAiSection(element) && aiPreview(element))"
65
+ class="btn btn-sm btn-primary mr-2"
66
+ :title="$t('Copy Control')"
67
+ @click="duplicateItem(index)"
68
+ >
69
+ <i class="fas fa-copy text-light" />
70
+ </button>
71
+ <button
72
+ class="btn btn-sm btn-danger"
73
+ :aria-label="$t('Delete')"
74
+ @click="deleteItem(index)"
75
+ >
76
+ <i class="far fa-trash-alt text-light" />
77
+ </button>
78
+ </div>
79
+ </div>
80
+
81
+ <component
82
+ v-model="element.items"
83
+ :is="element['editor-component']"
84
+ :class="elementCssClass(element)"
85
+ :validation-errors="validationErrors"
86
+ class="mb-3 mr-3 ml-3"
87
+ :selected="selected"
88
+ :ai-element="element"
89
+ :config="element.config"
90
+ @inspect="inspect"
91
+ @update-state="$emit('update-state')"
92
+ :screen-type="screenType"
93
+ />
94
+ </div>
95
+ </div>
96
+
97
+ <div v-else :id="element.config.name ? element.config.name : undefined">
98
+ <div class="m-2" :class="{ card: selected === element }">
99
+ <div
100
+ v-if="selected === element"
101
+ class="card-header form-element-header d-flex align-items-center"
102
+ >
103
+ <i class="fas fa-arrows-alt-v mr-1 text-muted" />
104
+ <i
105
+ v-if="element.config.icon"
106
+ :class="element.config.icon"
107
+ class="mr-2 ml-1"
108
+ />
109
+ {{ element.config.name || $t("Variable Name") }}
110
+ <b-badge
111
+ v-if="isInClipboard(items[index]) && screenType === 'form'"
112
+ data-cy="copied-badge"
113
+ class="m-2 custom-badge"
114
+ pill
115
+ >
116
+ <i class="far fa-check-circle"></i>
117
+ <span class="pl-2">{{ $t('Copied')}}</span>
118
+ </b-badge>
119
+ <div class="ml-auto">
120
+ <clipboard-button
121
+ v-if="screenType === 'form'"
122
+ :index="index"
123
+ :config="element.config"
124
+ :isInClipboard="isInClipboard(items[index])"
125
+ :addTitle="$t('Add to clipboard')"
126
+ :removeTitle="$t('Remove from clipboard')"
127
+ @addToClipboard="addToClipboard(items[index])"
128
+ @removeFromClipboard="removeFromClipboard(items[index])"
129
+ />
130
+ <button
131
+ class="btn btn-sm btn-primary mr-2"
132
+ :title="$t('Copy Control')"
133
+ @click="duplicateItem(index)"
134
+ >
135
+ <i class="fas fa-copy text-light" />
136
+ </button>
137
+ <button
138
+ class="btn btn-sm btn-danger"
139
+ :aria-label="$t('Delete')"
140
+ @click="deleteItem(index)"
141
+ >
142
+ <i class="far fa-trash-alt text-light" />
143
+ </button>
144
+ </div>
145
+ </div>
146
+
147
+ <component
148
+ v-bind="element.config"
149
+ :is="element['editor-component']"
150
+ class="p-3"
151
+ :class="[
152
+ elementCssClass(element),
153
+ { 'prevent-interaction': !element.config.interactive }
154
+ ]"
155
+ :tabindex="element.config.interactive ? 0 : -1"
156
+ :config="element.config"
157
+ :screen-type="screenType"
158
+ @input="
159
+ element.config.interactive
160
+ ? (element.config.content = $event)
161
+ : null
162
+ "
163
+ />
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </draggable>
168
+ </div>
169
+ </template>
170
+
171
+ <script>
172
+ import draggable from "vuedraggable";
173
+ import _ from "lodash";
174
+ import {
175
+ FormCheckbox,
176
+ FormDatePicker,
177
+ FormHtmlEditor,
178
+ FormHtmlViewer,
179
+ FormInput,
180
+ FormSelectList,
181
+ FormTextArea
182
+ } from "@processmaker/vue-form-elements";
183
+ import { HasColorProperty, Clipboard } from "@/mixins";
184
+ import ClipboardButton from '../ClipboardButton.vue';
185
+ import * as renderer from "@/components/renderer";
186
+
187
+ export default {
188
+ name: "DynamicPanel",
189
+ components: {
190
+ draggable,
191
+ FormInput,
192
+ FormSelectList,
193
+ FormCheckbox,
194
+ FormTextArea,
195
+ FormDatePicker,
196
+ FormHtmlEditor,
197
+ FormHtmlViewer,
198
+ ClipboardButton,
199
+ ...renderer
200
+ },
201
+ mixins: [HasColorProperty, Clipboard],
202
+ props: ["value", "name", "config", "selected", "validationErrors", "screenType"],
203
+ data() {
204
+ return {
205
+ items: [],
206
+ cancelledJobs: []
207
+ };
208
+ },
209
+ watch: {
210
+ value: {
211
+ handler() {
212
+ this.items = this.value;
213
+ },
214
+ immediate: true
215
+ },
216
+ items() {
217
+ this.$emit("input", this.items);
218
+ }
219
+ },
220
+ mounted() {
221
+ if (
222
+ !localStorage.getItem("cancelledJobs") ||
223
+ localStorage.getItem("cancelledJobs") === "null"
224
+ ) {
225
+ this.cancelledJobs = [];
226
+ } else {
227
+ this.cancelledJobs = JSON.parse(localStorage.getItem("cancelledJobs"));
228
+ }
229
+ this.$root.$on("ai-form-generated", (formItems, nonce) => {
230
+ this.previewAiChanges(formItems, nonce);
231
+ });
232
+ this.$root.$on("ai-form-progress-updated", (progress, nonce) => {
233
+ this.updateProgress(progress, nonce);
234
+ });
235
+ },
236
+ methods: {
237
+ hasError(element) {
238
+ if (!this.validationErrors) {
239
+ return false;
240
+ }
241
+ return this.validationErrors.some(({ item }) => item === element);
242
+ },
243
+ inspect(element) {
244
+ this.$emit("inspect", element);
245
+ },
246
+ deleteItem(index) {
247
+ // Remove the item from the array in currentPage
248
+ this.items.splice(index, 1);
249
+ this.$emit("update-state");
250
+ },
251
+ duplicateItem(index) {
252
+ const duplicate = _.cloneDeep(this.items[index]);
253
+ this.items.push(duplicate);
254
+ this.$emit("update-state");
255
+ },
256
+ isAiSection(element) {
257
+ return element.component === "AiSection";
258
+ },
259
+ aiPreview(element) {
260
+ return element.items && element.items[0] && element.items[0].length;
261
+ },
262
+ applyAiChanges(element) {
263
+ this.$root.$emit("apply-ai-changes", element);
264
+ },
265
+ previewAiChanges(formItems, nonce) {
266
+ this.value.forEach((item) => {
267
+ if (
268
+ item.component === "AiSection" &&
269
+ nonce === item.config.aiConfig.nonce
270
+ ) {
271
+ this.$set(item, "items", JSON.parse(JSON.stringify(formItems)));
272
+ }
273
+ });
274
+ },
275
+ updateProgress(progress, nonce) {
276
+ this.value.forEach((item) => {
277
+ if (
278
+ item.component === "AiSection" &&
279
+ nonce === item.config.aiConfig.nonce
280
+ ) {
281
+ if (this.cancelledJobs.some((element) => element === nonce)) {
282
+ return;
283
+ }
284
+ this.$set(item.config.aiConfig, "progress", progress);
285
+ }
286
+ });
287
+ },
288
+ /**
289
+ * Triggered when the draggable container is changed.
290
+ */
291
+ onChange(e) {
292
+ this.$emit("update-state");
293
+ },
294
+ }
295
+ };
296
+ </script>
297
+
298
+ <style lang="scss" scoped>
299
+ .hasError {
300
+ border: 1px solid red;
301
+ border-radius: 0.25rem;
302
+
303
+ .form-element-header {
304
+ border-bottom: 1px solid red;
305
+ color: red;
306
+ }
307
+ }
308
+
309
+ .column-draggable {
310
+ border: 1px dashed #000;
311
+ min-height: 80px;
312
+ content: "Drag Controls";
313
+ }
314
+
315
+ .selected .column-draggable {
316
+ border: none;
317
+ }
318
+
319
+ .control-item {
320
+ position: relative;
321
+
322
+ .delete {
323
+ position: absolute;
324
+ top: 0px;
325
+ right: 0px;
326
+ display: none;
327
+ }
328
+
329
+ &.selected,
330
+ &:hover {
331
+ .mask {
332
+ border: 1px solid red;
333
+ }
334
+
335
+ .delete {
336
+ display: inline-block;
337
+ }
338
+ }
339
+
340
+ .mask {
341
+ position: absolute;
342
+ top: 0px;
343
+ left: 0px;
344
+ background-color: rgba(0, 0, 0, 0);
345
+ width: 100%;
346
+ height: 100%;
347
+ }
348
+ }
349
+
350
+ .ai-section-card {
351
+ border: 1px solid #8ab8ff !important;
352
+ }
353
+
354
+ .ai-section-card .card-header {
355
+ background: #cbdfff;
356
+ }
357
+ .pulse {
358
+ animation: pulse-animation 2s infinite;
359
+ }
360
+
361
+ @keyframes pulse-animation {
362
+ 0% {
363
+ box-shadow: 0 0 0 0px rgb(28 114 194 / 50%);
364
+ }
365
+ 100% {
366
+ box-shadow: 0 0 0 13px rgba(0, 0, 0, 0);
367
+ }
368
+ }
369
+ .custom-badge {
370
+ background-color: #D1F4D7 !important;
371
+ color: #06723A !important;
372
+ padding: 0.5rem 0.75rem;
373
+ border-radius: 8px;
374
+ font-weight: 500;
375
+ font-size: 14px;
376
+ }
377
+ </style>
@@ -1,2 +1,3 @@
1
1
  export { default as Loop } from "./loop.vue";
2
2
  export { default as MultiColumn } from "./multi-column.vue";
3
+ export { default as DynamicPanel } from "./dynamic-panel-editor.vue";
@@ -8,8 +8,10 @@ import * as inspector from "./inspector";
8
8
  import FormBuilderControls from "../form-builder-controls";
9
9
  import Task from "./task.vue";
10
10
  import Loop from "./editor/loop.vue";
11
+ import DynamicPanel from "./editor/dynamic-panel-editor.vue";
11
12
  import MultiColumn from "./editor/multi-column.vue";
12
13
  import FormLoop from "./renderer/form-loop.vue";
14
+ import FormDynamicPanel from "./renderer/form-dynamic-panel.vue";
13
15
  import NewFormMultiColumn from "./renderer/new-form-multi-column.vue";
14
16
  import FormNestedScreen from "./renderer/form-nested-screen.vue";
15
17
  import ScreenRenderer from "./screen-renderer.vue";
@@ -48,7 +50,7 @@ import FormListTable from "./renderer/form-list-table.vue";
48
50
  import FormAnalyticsChart from "./renderer/form-analytics-chart.vue";
49
51
  import accordions from "@/components/accordions";
50
52
  import VariableNameGenerator from "@/components/VariableNameGenerator";
51
- import { LinkButton } from "./renderer";
53
+ import { LinkButton, CaseProgressBar } from "./renderer";
52
54
  import "../assets/css/tabs.css";
53
55
  import FormCollectionRecordControl from "./renderer/form-collection-record-control.vue";
54
56
  import FormCollectionViewControl from "./renderer/form-collection-view-control.vue";
@@ -72,6 +74,7 @@ export {
72
74
  validationRulesProperty,
73
75
  toggleStyleProperty,
74
76
  buttonVariantStyleProperty,
77
+ linkVariantStyleProperty,
75
78
  defaultValueProperty,
76
79
  buttonTypeEvent,
77
80
  tooltipProperty,
@@ -144,10 +147,12 @@ export default {
144
147
  Vue.component("FormImage", FormImage);
145
148
  Vue.component("FormAvatar", FormAvatar);
146
149
  Vue.component("FormLoop", FormLoop);
150
+ Vue.component("FormDynamicPanel", FormDynamicPanel);
147
151
  Vue.component("FormMultiColumn", FormMultiColumn);
148
152
  Vue.component("FormNestedScreen", FormNestedScreen);
149
153
  Vue.component("FormRecordList", FormRecordList);
150
154
  Vue.component("Loop", Loop);
155
+ Vue.component("DynamicPanel", DynamicPanel);
151
156
  Vue.component("MultiColumn", MultiColumn);
152
157
  Vue.component("NewFormMultiColumn", NewFormMultiColumn);
153
158
  Vue.component("ScreenRenderer", ScreenRenderer);
@@ -166,6 +171,7 @@ export default {
166
171
  Vue.use(Vuex);
167
172
  Vue.component("FormListTable", FormListTable);
168
173
  Vue.component("LinkButton", LinkButton);
174
+ Vue.component("CaseProgressBar", CaseProgressBar);
169
175
  Vue.component("FormCollectionRecordControl", FormCollectionRecordControl);
170
176
  Vue.component("FormCollectionViewControl", FormCollectionViewControl);
171
177
  const store = new Vuex.Store({
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <div>
3
+ <div class="form-group border-bottom">
4
+ <FormInput
5
+ v-model="settings.varname"
6
+ :label="$t('Variable Name')"
7
+ :name="$t('Variable Name')"
8
+ :helper="$t('This variable will contain an array of objects')"
9
+ validation="regex:/^(?:[A-Z_.a-z])(?:[0-9A-Z_.a-z])*$/|required"
10
+ data-test="i1177-inspector-name"
11
+ />
12
+ </div>
13
+
14
+
15
+ <div v-if="screenType == 'form' && settings.type === 'new'" class="form-group border-bottom">
16
+ <FormInput
17
+ v-model="settings.indexName"
18
+ :label="$t('Index Name')"
19
+ :name="$t('Index Name')"
20
+ :helper="$t('Index Name of the dynamic panel')"
21
+ validation="required"
22
+ data-test="i1177-inspector-index-name"
23
+ />
24
+ </div>
25
+
26
+ <div class="form-group border-bottom">
27
+ <FormTextArea
28
+ v-model="settings.emptyStateMessage"
29
+ :label="$t('Empty State Message')"
30
+ :name="$t('Empty State Message')"
31
+ :helper="$t('Custom message to display when the panel has no data. Supports HTML and Mustache placeholders')"
32
+ :rows="3"
33
+ data-test="i1177-inspector-empty-state-message"
34
+ />
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script>
40
+ import { FormInput, FormTextArea } from '@processmaker/vue-form-elements';
41
+
42
+ export default {
43
+ props: ['value', 'screenType'],
44
+ inheritAttrs: false,
45
+ components: { FormInput, FormTextArea },
46
+ data() {
47
+ return {
48
+ settings: {
49
+ type: 'new',
50
+ varname: '',
51
+ indexName: '',
52
+ emptyStateMessage: this.$t('No data available for this dynamic panel'),
53
+ },
54
+ };
55
+ },
56
+ watch: {
57
+ settings: {
58
+ handler() {
59
+ this.$emit('input', this.settings);
60
+ this.$emit('setName', this.settings.varname);
61
+ },
62
+ deep: true,
63
+ },
64
+ value: {
65
+ handler() {
66
+ this.settings = this.value;
67
+ },
68
+ immediate: true,
69
+ },
70
+ },
71
+ };
72
+ </script>
73
+
@@ -18,6 +18,7 @@ export { default as ImageUpload } from "./image-upload.vue";
18
18
  export { default as ImageVariable } from "./image-variable.vue";
19
19
  export { default as InputVariable } from "./input-variable.vue";
20
20
  export { default as LoopInspector } from "./loop.vue";
21
+ export { default as DynamicPanelInspector } from "./dynamic-panel.vue";
21
22
  export { default as MustacheHelper } from "./mustache-helper.vue";
22
23
  export { default as OptionsList } from "./options-list.vue";
23
24
  export { default as OutboundConfig } from "./outbound-config.vue";