@processmaker/screen-builder 3.8.4 → 3.8.5
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/dist/vue-form-builder.css +1 -1
- package/dist/vue-form-builder.es.js +4001 -3896
- package/dist/vue-form-builder.es.js.map +1 -1
- package/dist/vue-form-builder.umd.js +38 -38
- package/dist/vue-form-builder.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/inspector/button/handler-event-property.js +9 -0
- package/src/components/inspector/code-editor.vue +127 -0
- package/src/components/inspector/index.js +1 -0
- package/src/components/renderer/form-button.vue +20 -1
- package/src/components/vue-form-builder.vue +2 -0
- package/src/form-builder-controls.js +3 -0
- package/src/mixins/DataReference.js +4 -0
- package/src/mixins/extensions/LoadFieldComponents.js +4 -0
- package/src/stories/CodeEditor.stories.js +112 -0
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<label :for="`${dataFeature}-code-input`">{{ $t(label) }} </label>
|
|
4
|
+
<div class="float-right buttons">
|
|
5
|
+
<b-button
|
|
6
|
+
:title="$t('Expand')"
|
|
7
|
+
variant="light"
|
|
8
|
+
size="sm"
|
|
9
|
+
:data-test="`${dataFeature}-expand-button`"
|
|
10
|
+
@click="expand"
|
|
11
|
+
><i class="fas fa-expand"></i></b-button>
|
|
12
|
+
</div>
|
|
13
|
+
<MonacoEditor
|
|
14
|
+
:id="`${dataFeature}-code-input`"
|
|
15
|
+
v-model="configValue"
|
|
16
|
+
:options="smallMonacoOptions"
|
|
17
|
+
class="editor"
|
|
18
|
+
language="javascript"
|
|
19
|
+
:data-test="`${dataFeature}-code-input`"
|
|
20
|
+
@change="updateValue"
|
|
21
|
+
/>
|
|
22
|
+
<small v-if="helper" class="form-text text-muted mt-2">{{ $t(helper) }}</small>
|
|
23
|
+
<b-modal
|
|
24
|
+
ref="codeEditorModal"
|
|
25
|
+
:title="$t(label)"
|
|
26
|
+
size="xl"
|
|
27
|
+
dialog-class="modal-dialog-fullscreen"
|
|
28
|
+
>
|
|
29
|
+
<MonacoEditor
|
|
30
|
+
v-model="configValue"
|
|
31
|
+
:options="largeMonacoOptions"
|
|
32
|
+
class="editor large-editor"
|
|
33
|
+
language="javascript"
|
|
34
|
+
:data-test="`${dataFeature}-code-input`"
|
|
35
|
+
@change="updateValue"
|
|
36
|
+
/>
|
|
37
|
+
<template #modal-footer="{ ok }">
|
|
38
|
+
<b-button @click="ok" class="btn btn-secondary text-uppercase">
|
|
39
|
+
{{ $t('Close') }}
|
|
40
|
+
</b-button>
|
|
41
|
+
</template>
|
|
42
|
+
</b-modal>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
import MonacoEditor from 'vue-monaco';
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
name: 'CodeEditor',
|
|
51
|
+
props: {
|
|
52
|
+
value: {
|
|
53
|
+
type: String,
|
|
54
|
+
required: false,
|
|
55
|
+
},
|
|
56
|
+
label: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
helper: {
|
|
61
|
+
type: String,
|
|
62
|
+
required: false,
|
|
63
|
+
},
|
|
64
|
+
dataFeature: {
|
|
65
|
+
type: String,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
components: {
|
|
70
|
+
MonacoEditor,
|
|
71
|
+
},
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
configValue: this.value || '',
|
|
75
|
+
smallMonacoOptions: {
|
|
76
|
+
lineNumbers: 'off',
|
|
77
|
+
lineDecorationsWidth: 0,
|
|
78
|
+
lineNumbersMinChars: 0,
|
|
79
|
+
minimap: { enabled: false },
|
|
80
|
+
fixedOverflowWidgets: true,
|
|
81
|
+
automaticLayout: true,
|
|
82
|
+
renderLineHighlight: 'none',
|
|
83
|
+
overviewRulerLanes: 0,
|
|
84
|
+
},
|
|
85
|
+
largeMonacoOptions: {
|
|
86
|
+
lineNumbers: 'on',
|
|
87
|
+
lineDecorationsWidth: 0,
|
|
88
|
+
minimap: { enabled: false },
|
|
89
|
+
automaticLayout: true,
|
|
90
|
+
overviewRulerLanes: 0,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
methods: {
|
|
95
|
+
updateValue() {
|
|
96
|
+
this.$emit('input', this.configValue);
|
|
97
|
+
},
|
|
98
|
+
expand() {
|
|
99
|
+
this.$refs.codeEditorModal.show();
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
watch: {
|
|
103
|
+
value(newVal) {
|
|
104
|
+
this.configValue = newVal;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<style scoped>
|
|
111
|
+
.buttons button {
|
|
112
|
+
min-width: 2.2em;
|
|
113
|
+
margin-left: 0.5em;
|
|
114
|
+
margin-bottom: 0.5em;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.editor {
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 10em;
|
|
120
|
+
border: 1px solid var(--gray);
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.large-editor {
|
|
125
|
+
height: 25em;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
@@ -30,3 +30,4 @@ export { default as LoadingSubmitButton } from "./loading-submit-button.vue";
|
|
|
30
30
|
export { default as LabelSubmitButton } from "./label-submit-button.vue";
|
|
31
31
|
export { default as AnalyticsSelector } from "./analytics-selector.vue";
|
|
32
32
|
export { default as EncryptedConfig } from "./encrypted-config.vue";
|
|
33
|
+
export { default as CodeEditor } from "./code-editor.vue";
|
|
@@ -22,7 +22,7 @@ import { getValidPath } from '@/mixins';
|
|
|
22
22
|
|
|
23
23
|
export default {
|
|
24
24
|
mixins: [getValidPath],
|
|
25
|
-
props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel'],
|
|
25
|
+
props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel', 'handler'],
|
|
26
26
|
data() {
|
|
27
27
|
return {
|
|
28
28
|
showSpinner: false
|
|
@@ -80,6 +80,8 @@ export default {
|
|
|
80
80
|
const trueValue = this.fieldValue || '1';
|
|
81
81
|
const value = (this.value == trueValue) ? null : trueValue;
|
|
82
82
|
this.$emit('input', value);
|
|
83
|
+
// Run handler after setting the value
|
|
84
|
+
await this.runHandler();
|
|
83
85
|
}
|
|
84
86
|
if (this.event !== 'pageNavigate' && this.name) {
|
|
85
87
|
this.setValue(this.$parent, this.name, this.fieldValue);
|
|
@@ -89,6 +91,8 @@ export default {
|
|
|
89
91
|
this.showSpinner = true;
|
|
90
92
|
}
|
|
91
93
|
this.$emit('input', this.fieldValue);
|
|
94
|
+
// Run handler after setting the value
|
|
95
|
+
await this.runHandler();
|
|
92
96
|
this.$nextTick(() => {
|
|
93
97
|
this.$emit('submit', this.eventData, this.loading, this.buttonInfo);
|
|
94
98
|
});
|
|
@@ -99,6 +103,21 @@ export default {
|
|
|
99
103
|
this.$emit('page-navigate', this.eventData);
|
|
100
104
|
}
|
|
101
105
|
},
|
|
106
|
+
async runHandler() {
|
|
107
|
+
if (this.handler) {
|
|
108
|
+
try {
|
|
109
|
+
const data = this.getScreenDataReference(null, (screen, name, value) => {
|
|
110
|
+
// Enable the data reference to be updated by the handler
|
|
111
|
+
screen.$set(screen.vdata, name, value);
|
|
112
|
+
});
|
|
113
|
+
await new Function(['toRaw'], this.handler).apply(data, [(item) => {
|
|
114
|
+
return item[Symbol.for('__v_raw')];
|
|
115
|
+
}]);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('❌ There is an error in the button handler', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
102
121
|
},
|
|
103
122
|
};
|
|
104
123
|
</script>
|
|
@@ -562,6 +562,7 @@ import TabsBar from "./TabsBar.vue";
|
|
|
562
562
|
import Sortable from './sortable/Sortable.vue';
|
|
563
563
|
import ClipboardButton from './ClipboardButton.vue';
|
|
564
564
|
import ScreenTemplates from './ScreenTemplates.vue';
|
|
565
|
+
import CodeEditor from "./inspector/code-editor.vue";
|
|
565
566
|
|
|
566
567
|
// To include another language in the Validator with variable processmaker
|
|
567
568
|
const globalObject = typeof window === "undefined" ? global : window;
|
|
@@ -633,6 +634,7 @@ export default {
|
|
|
633
634
|
Sortable,
|
|
634
635
|
ClipboardButton,
|
|
635
636
|
ScreenTemplates,
|
|
637
|
+
CodeEditor,
|
|
636
638
|
},
|
|
637
639
|
mixins: [HasColorProperty, testing, Clipboard],
|
|
638
640
|
props: {
|
|
@@ -13,6 +13,7 @@ import FormListTable from './components/renderer/form-list-table';
|
|
|
13
13
|
import FormAnalyticsChart from "./components/renderer/form-analytics-chart";
|
|
14
14
|
import FormCollectionRecordControl from './components/renderer/form-collection-record-control.vue';
|
|
15
15
|
import FormCollectionViewControl from './components/renderer/form-collection-view-control.vue';
|
|
16
|
+
import { handlerEventProperty } from './components/inspector/button/handler-event-property';
|
|
16
17
|
import {DataTypeProperty, DataFormatProperty, DataTypeDateTimeProperty} from './VariableDataTypeProperties';
|
|
17
18
|
import {
|
|
18
19
|
FormInput,
|
|
@@ -720,6 +721,7 @@ export default [
|
|
|
720
721
|
name: null,
|
|
721
722
|
fieldValue: null,
|
|
722
723
|
tooltip: {},
|
|
724
|
+
handler: '',
|
|
723
725
|
},
|
|
724
726
|
inspector: [
|
|
725
727
|
{
|
|
@@ -742,6 +744,7 @@ export default [
|
|
|
742
744
|
},
|
|
743
745
|
},
|
|
744
746
|
buttonTypeEvent,
|
|
747
|
+
handlerEventProperty,
|
|
745
748
|
LoadingSubmitButtonProperty,
|
|
746
749
|
LabelSubmitButtonProperty,
|
|
747
750
|
tooltipProperty,
|
|
@@ -28,6 +28,7 @@ function findScreenOwner(control, lastScreenContentIfNull = false) {
|
|
|
28
28
|
* @return {object} proxy
|
|
29
29
|
*/
|
|
30
30
|
function wrapScreenData(screen, customProperties = null, setter = null) {
|
|
31
|
+
const RAW = Symbol.for('__v_raw');
|
|
31
32
|
const handler = {
|
|
32
33
|
get: (target, name) => {
|
|
33
34
|
if (customProperties && customProperties[name]) {
|
|
@@ -44,6 +45,9 @@ function wrapScreenData(screen, customProperties = null, setter = null) {
|
|
|
44
45
|
}
|
|
45
46
|
return undefined;
|
|
46
47
|
}
|
|
48
|
+
if (name === RAW) {
|
|
49
|
+
return screen.vdata;
|
|
50
|
+
}
|
|
47
51
|
// Check if vdata exists
|
|
48
52
|
if (screen.vdata !== undefined && screen.vdata !== null) {
|
|
49
53
|
return screen.vdata[name];
|
|
@@ -101,6 +101,10 @@ export default {
|
|
|
101
101
|
properties[":disabled"] = isCalcProp || element.config.disabled;
|
|
102
102
|
// Events
|
|
103
103
|
properties['@submit'] = 'submitForm';
|
|
104
|
+
// Add handler event if Button
|
|
105
|
+
if(componentName === 'FormButton') {
|
|
106
|
+
properties[':handler'] = this.byRef(element.config.handler);
|
|
107
|
+
}
|
|
104
108
|
},
|
|
105
109
|
},
|
|
106
110
|
mounted() {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
+
import { userEvent, expect, within } from "@storybook/test";
|
|
3
|
+
import "../bootstrap";
|
|
4
|
+
import CodeEditor from "../components/inspector/code-editor.vue";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Components/CodeEditor",
|
|
8
|
+
component: CodeEditor,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
value: {
|
|
12
|
+
control: { type: 'text' },
|
|
13
|
+
description: 'The code value to display in the editor'
|
|
14
|
+
},
|
|
15
|
+
helper: {
|
|
16
|
+
control: { type: 'text' },
|
|
17
|
+
description: 'Helper text displayed below the editor'
|
|
18
|
+
},
|
|
19
|
+
dataFeature: {
|
|
20
|
+
control: { type: 'text' },
|
|
21
|
+
description: 'Data test attribute prefix for testing'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
render: (args, { argTypes }) => ({
|
|
25
|
+
props: Object.keys(argTypes),
|
|
26
|
+
components: { CodeEditor },
|
|
27
|
+
template: '<code-editor v-bind="$props" v-model="inputValue" @input="handleInput" />',
|
|
28
|
+
data() {
|
|
29
|
+
return { inputValue: args.value };
|
|
30
|
+
},
|
|
31
|
+
methods: {
|
|
32
|
+
handleInput(value) {
|
|
33
|
+
this.inputValue = value;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
watch: {
|
|
37
|
+
// Updates the value when the property changes in storybook controls
|
|
38
|
+
value(value) {
|
|
39
|
+
this.inputValue = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stories of the component
|
|
47
|
+
*/
|
|
48
|
+
// Preview the component with basic JavaScript code
|
|
49
|
+
export const Preview = {
|
|
50
|
+
args: {
|
|
51
|
+
label: "Click Handler",
|
|
52
|
+
helper: "Enter your JavaScript code here",
|
|
53
|
+
dataFeature: "code-editor",
|
|
54
|
+
value: "console.log('Hello, World!');\n\nfunction greet(name) {\n return `Hello, ${name}!`;\n}"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Story with empty value
|
|
59
|
+
export const EmptyEditor = {
|
|
60
|
+
args: {
|
|
61
|
+
label: "Empty Editor",
|
|
62
|
+
helper: "Start typing your code...",
|
|
63
|
+
dataFeature: "code-editor-empty",
|
|
64
|
+
value: ""
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvasElement }) => {
|
|
67
|
+
const canvas = within(canvasElement);
|
|
68
|
+
await userEvent.type(canvas.getByRole('textbox'), 'console.log("Hello, World!");');
|
|
69
|
+
// Check if the code is displayed
|
|
70
|
+
expect(canvas.getByRole('textbox')).toHaveValue('console.log("Hello, World!");');
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Story with long code
|
|
75
|
+
export const LongCode = {
|
|
76
|
+
args: {
|
|
77
|
+
label: "Long Code Example",
|
|
78
|
+
helper: "This editor contains a longer piece of code",
|
|
79
|
+
dataFeature: "code-editor-long",
|
|
80
|
+
value: `// This is a longer code example
|
|
81
|
+
function processUserData(users) {
|
|
82
|
+
return users
|
|
83
|
+
.filter(user => user.active)
|
|
84
|
+
.map(user => ({
|
|
85
|
+
id: user.id,
|
|
86
|
+
name: user.name,
|
|
87
|
+
email: user.email,
|
|
88
|
+
role: user.role,
|
|
89
|
+
lastLogin: user.lastLogin,
|
|
90
|
+
permissions: user.permissions || []
|
|
91
|
+
}))
|
|
92
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
93
|
+
.reduce((acc, user) => {
|
|
94
|
+
if (!acc[user.role]) {
|
|
95
|
+
acc[user.role] = [];
|
|
96
|
+
}
|
|
97
|
+
acc[user.role].push(user);
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Example usage
|
|
103
|
+
const users = [
|
|
104
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin', active: true, lastLogin: new Date() },
|
|
105
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user', active: true, lastLogin: new Date() },
|
|
106
|
+
{ id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'admin', active: false, lastLogin: new Date() }
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const processedUsers = processUserData(users);
|
|
110
|
+
console.log(processedUsers);`
|
|
111
|
+
}
|
|
112
|
+
};
|