Skip to content

Commit 773111e

Browse files
committed
wip
1 parent 3a2259f commit 773111e

File tree

5 files changed

+120
-105
lines changed

5 files changed

+120
-105
lines changed

package-lock.json

Lines changed: 21 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/form-js-editor/src/features/properties-panel/entries/JSFunctionEntry.js

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FeelEntry, isFeelEntryEdited, TextAreaEntry, isTextAreaEntryEdited, ToggleSwitchEntry, isToggleSwitchEntryEdited } from '@bpmn-io/properties-panel';
1+
import { FeelEntry, isFeelEntryEdited, TextAreaEntry, isTextAreaEntryEdited } from '@bpmn-io/properties-panel';
22
import { get } from 'min-dash';
33

44
import { useService, useVariables } from '../hooks';
@@ -25,14 +25,6 @@ export function JSFunctionEntry(props) {
2525
field: field,
2626
isEdited: isTextAreaEntryEdited,
2727
isDefaultVisible: (field) => field.type === 'script'
28-
},
29-
{
30-
id: 'on-load-only',
31-
component: OnLoadOnlyEntry,
32-
editField: editField,
33-
field: field,
34-
isEdited: isToggleSwitchEntryEdited,
35-
isDefaultVisible: (field) => field.type === 'script'
3628
}
3729
];
3830

@@ -76,7 +68,7 @@ function FunctionParameters(props) {
7668
id,
7769
label: 'Function parameters',
7870
tooltip,
79-
description: 'Define the parameters to pass to the javascript context.',
71+
description: 'Define the parameters to pass to the javascript sandbox.',
8072
setValue,
8173
variables
8274
});
@@ -105,35 +97,9 @@ function FunctionDefinition(props) {
10597
debounce,
10698
element: field,
10799
getValue,
108-
description: 'Access function parameters via `data`, set results with `setValue`, and register cleanup functions with `onCleanup`.',
100+
description: 'Define the javascript function to execute. Register lifecycle hooks with onLoad({data}) and onData({data}). Use setValue(value) to return the result.',
109101
id,
110102
label: 'Javascript code',
111103
setValue
112104
});
113105
}
114-
115-
function OnLoadOnlyEntry(props) {
116-
const {
117-
editField,
118-
field,
119-
id
120-
} = props;
121-
122-
const path = [ 'onLoadOnly' ];
123-
124-
const getValue = () => {
125-
return !!get(field, path, false);
126-
};
127-
128-
const setValue = (value) => {
129-
editField(field, path, value);
130-
};
131-
132-
return ToggleSwitchEntry({
133-
element: field,
134-
id,
135-
label: 'Execute on load only',
136-
getValue,
137-
setValue
138-
});
139-
}

packages/form-js-viewer/assets/form-js-base.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,10 @@
12031203
margin-right: 4px;
12041204
}
12051205

1206+
.fjs-container .fjs-sandbox-iframe-container {
1207+
display: none;
1208+
}
1209+
12061210
/**
12071211
* Flatpickr style adjustments
12081212
*/

packages/form-js-viewer/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"lodash": "^4.5.0",
5858
"marked": "^12.0.1",
5959
"min-dash": "^4.2.1",
60-
"preact": "^10.5.14"
60+
"preact": "^10.5.14",
61+
"uuid": "^9.0.1"
6162
},
6263
"sideEffects": [
6364
"*.css"
Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,118 @@
11
import Sandbox from 'websandbox';
2-
import { useCallback, useEffect, useState } from 'preact/hooks';
3-
import { useExpressionEvaluation, useDeepCompareMemoize } from '../../hooks';
2+
import { useEffect, useState } from 'preact/hooks';
3+
import { useExpressionEvaluation, useDeepCompareMemoize, usePrevious } from '../../hooks';
44
import { isObject } from 'min-dash';
5-
5+
import { v4 as uuidv4 } from 'uuid';
66

77
export function JSFunctionField(props) {
88
const { field, onChange } = props;
99
const { jsFunction, functionParameters } = field;
1010

1111
const [ sandbox, setSandbox ] = useState(null);
12+
const [ iframeContainerId ] = useState(`fjs-sandbox-iframe-container_${uuidv4()}`);
13+
const [ hasLoaded , setHasLoaded ] = useState(false);
1214

1315
const paramsEval = useExpressionEvaluation(functionParameters);
1416
const params = useDeepCompareMemoize(isObject(paramsEval) ? paramsEval : {});
1517

16-
const rebuildSandbox = useCallback(() => {
17-
const localApi = {
18+
// setup the sandbox
19+
useEffect(() => {
20+
const hostAPI = {
1821
setValue: function(value) {
19-
onChange({ field, value });
22+
if (isValidData(value)) {
23+
onChange({ field, value });
24+
}
2025
}
2126
};
2227

23-
const newSandbox = Sandbox.create(localApi, {
24-
frameContainer: '.iframe__container',
25-
frameClassName: 'simple__iframe'
28+
const _sandbox = Sandbox.create(hostAPI, {
29+
frameContainer: `#${iframeContainerId}`,
30+
frameClassName: 'fjs-sandbox-iframe'
2631
});
2732

28-
newSandbox.promise.then((sandboxInstance) => {
29-
setSandbox(sandboxInstance);
30-
sandboxInstance.run(`
31-
Websandbox.connection.setLocalApi({
32-
onInit: () => Websandbox.connection.remote.onInit(),
33-
onData: (data) => Websandbox.connection.remote.onData(data),
34-
});
33+
const wrappedUserCode = `
34+
const dataCallbacks = [];
35+
const loadCallbacks = [];
36+
37+
const api = {
38+
onData: (callback) => datacallbacks.push(callback),
39+
offData: (callback) => dataCallbacks.splice(dataCallbacks.indexOf(callback), 1),
40+
onLoad: (callback) => loadCallbacks.push(callback),
41+
offLoad: (callback) => loadCallbacks.splice(loadCallbacks.indexOf(callback), 1),
42+
}
43+
44+
const onData = (callback) => {
45+
dataCallbacks.push(callback);
46+
}
3547
36-
// Custom user code
48+
const offData = (callback) => {
49+
dataCallbacks.splice(dataCallbacks.indexOf(callback), 1);
50+
}
51+
52+
Websandbox.connection.setLocalApi({
53+
sendData: ({data}) => dataCallbacks.forEach(callback => callback(data)),
54+
load: ({data}) => loadCallbacks.forEach(callback => callback(data))
55+
});
56+
57+
const setValue = (value) => {
58+
Websandbox.connection.remote.setValue(value);
59+
}
60+
61+
// Custom user code
62+
try {
3763
${jsFunction}
38-
`);
64+
}
65+
catch (e) {
66+
setValue(null);
67+
}
68+
`;
3969

40-
sandboxInstance.connection.remote.onInit();
70+
_sandbox.promise.then((sandboxInstance) => {
71+
setSandbox(sandboxInstance);
72+
sandboxInstance
73+
.run(wrappedUserCode)
74+
.then(() => setHasLoaded(false))
75+
.catch(() => { onChange({ field, value: null }); });
4176
});
42-
}, [ jsFunction, onChange, field ]);
4377

44-
useEffect(() => {
45-
rebuildSandbox();
46-
}, [ rebuildSandbox ]);
78+
return () => {
79+
_sandbox.destroy();
80+
};
81+
82+
}, [ iframeContainerId, jsFunction, onChange, field, functionParameters ]);
4783

84+
const prevParams = usePrevious(params);
85+
const prevSandbox = usePrevious(sandbox);
86+
87+
// make calls to the sandbox
4888
useEffect(() => {
49-
if (sandbox && sandbox.connection && sandbox.connection.remote.onData) {
89+
const hasChanged = prevParams !== params || prevSandbox !== sandbox;
90+
const hasConnection = sandbox && sandbox.connection && sandbox.connection.remote.onData;
91+
92+
if (hasChanged && hasConnection) {
93+
94+
if (!hasLoaded) {
95+
setHasLoaded(true);
96+
const loadResult = sandbox.connection.remote.onLoad();
97+
98+
if (isValidData(loadResult)) {
99+
onChange({ field, value: loadResult });
100+
}
101+
}
102+
50103
sandbox.connection.remote.onData(params);
104+
const dataResult = sandbox.connection.remote.onData();
105+
106+
if (isValidData(dataResult)) {
107+
onChange({ field, value: dataResult });
108+
}
109+
51110
}
52-
}, [ params, sandbox ]);
111+
}, [ params, sandbox, hasLoaded, prevParams, prevSandbox, onChange, field ]);
53112

54-
return null;
113+
return (
114+
<div id={ iframeContainerId } className="fjs-sandbox-iframe-container"></div>
115+
);
55116
}
56117

57118
JSFunctionField.config = {
@@ -61,8 +122,10 @@ JSFunctionField.config = {
61122
keyed: true,
62123
escapeGridRender: true,
63124
create: (options = {}) => ({
64-
jsFunction: 'setValue(data.value)',
125+
jsFunction: 'onData((data) => setValue(data.value))',
65126
functionParameters: '={\n value: 42\n}',
66127
...options,
67128
})
68129
};
130+
131+
const isValidData = (data) => [ 'object', 'boolean', 'number', 'string' ].includes(typeof data);

0 commit comments

Comments
 (0)