@processmaker/screen-builder 3.8.4 → 3.8.6

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.4",
3
+ "version": "3.8.6",
4
4
  "scripts": {
5
5
  "dev": "VITE_COVERAGE=true vite",
6
6
  "build": "vite build",
@@ -57,7 +57,7 @@
57
57
  "@fortawesome/fontawesome-free": "^5.6.1",
58
58
  "@originjs/vite-plugin-commonjs": "^1.0.3",
59
59
  "@panter/vue-i18next": "^0.15.2",
60
- "@processmaker/vue-form-elements": "0.65.2",
60
+ "@processmaker/vue-form-elements": "0.65.3",
61
61
  "@processmaker/vue-multiselect": "2.3.0",
62
62
  "@storybook/addon-essentials": "^7.6.13",
63
63
  "@storybook/addon-interactions": "^7.6.13",
@@ -80,7 +80,7 @@
80
80
  "cypress-wait-until": "^3.0.1",
81
81
  "eslint": "^8.21.0",
82
82
  "eslint-config-airbnb-base": "^15.0.0",
83
- "eslint-config-prettier": "^8.5.0",
83
+ "eslint-config-prettier": "^8.10.2",
84
84
  "eslint-plugin-cypress": "^2.15.1",
85
85
  "eslint-plugin-import": "^2.26.0",
86
86
  "eslint-plugin-jest": "^22.4.1",
@@ -116,7 +116,7 @@
116
116
  },
117
117
  "peerDependencies": {
118
118
  "@panter/vue-i18next": "^0.15.0",
119
- "@processmaker/vue-form-elements": "0.65.2",
119
+ "@processmaker/vue-form-elements": "0.65.3",
120
120
  "i18next": "^15.0.8",
121
121
  "vue": "^2.6.12",
122
122
  "vuex": "^3.1.1"
@@ -0,0 +1,9 @@
1
+ export const handlerEventProperty = {
2
+ type: 'CodeEditor',
3
+ field: 'handler',
4
+ config: {
5
+ label: 'Click Handler',
6
+ helper: 'The handler is a JavaScript function that will be executed when the button is clicked.',
7
+ dataFeature: 'i1177',
8
+ },
9
+ };
@@ -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
+ };