-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Related to #1102
- Loading branch information
Showing
10 changed files
with
269 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
packages/form-js-editor/src/features/properties-panel/entries/JSFunctionEntry.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { FeelEntry, isFeelEntryEdited, TextAreaEntry, isTextAreaEntryEdited, ToggleSwitchEntry, isToggleSwitchEntryEdited } from '@bpmn-io/properties-panel'; | ||
import { get } from 'min-dash'; | ||
|
||
import { useService, useVariables } from '../hooks'; | ||
|
||
export function JSFunctionEntry(props) { | ||
const { | ||
editField, | ||
field | ||
} = props; | ||
|
||
const entries = [ | ||
{ | ||
id: 'variable-mappings', | ||
component: FunctionParameters, | ||
editField: editField, | ||
field: field, | ||
isEdited: isFeelEntryEdited, | ||
isDefaultVisible: (field) => field.type === 'script' | ||
}, | ||
{ | ||
id: 'function', | ||
component: FunctionDefinition, | ||
editField: editField, | ||
field: field, | ||
isEdited: isTextAreaEntryEdited, | ||
isDefaultVisible: (field) => field.type === 'script' | ||
}, | ||
{ | ||
id: 'on-load-only', | ||
component: OnLoadOnlyEntry, | ||
editField: editField, | ||
field: field, | ||
isEdited: isToggleSwitchEntryEdited, | ||
isDefaultVisible: (field) => field.type === 'script' | ||
} | ||
]; | ||
|
||
return entries; | ||
} | ||
|
||
function FunctionParameters(props) { | ||
const { | ||
editField, | ||
field, | ||
id | ||
} = props; | ||
|
||
const debounce = useService('debounce'); | ||
|
||
const variables = useVariables().map(name => ({ name })); | ||
|
||
const path = [ 'functionParameters' ]; | ||
|
||
const getValue = () => { | ||
return get(field, path, ''); | ||
}; | ||
|
||
const setValue = (value) => { | ||
return editField(field, path, value || ''); | ||
}; | ||
|
||
const tooltip = <div> | ||
Functions parameters should be described as an object, e.g.: | ||
<pre><code>{`{ | ||
name: user.name, | ||
age: user.age | ||
}`}</code></pre> | ||
</div>; | ||
|
||
return FeelEntry({ | ||
debounce, | ||
feel: 'required', | ||
element: field, | ||
getValue, | ||
id, | ||
label: 'Function parameters', | ||
tooltip, | ||
description: 'Define the parameters to pass to the javascript context.', | ||
setValue, | ||
variables | ||
}); | ||
} | ||
|
||
function FunctionDefinition(props) { | ||
const { | ||
editField, | ||
field, | ||
id | ||
} = props; | ||
|
||
const debounce = useService('debounce'); | ||
|
||
const path = [ 'jsFunction' ]; | ||
|
||
const getValue = () => { | ||
return get(field, path, ''); | ||
}; | ||
|
||
const setValue = (value) => { | ||
return editField(field, path, value || ''); | ||
}; | ||
|
||
return TextAreaEntry({ | ||
debounce, | ||
element: field, | ||
getValue, | ||
description: 'Access function parameters via `data`, set results with `setValue`, and register cleanup functions with `onCleanup`.', | ||
id, | ||
label: 'Javascript code', | ||
setValue | ||
}); | ||
} | ||
|
||
function OnLoadOnlyEntry(props) { | ||
const { | ||
editField, | ||
field, | ||
id | ||
} = props; | ||
|
||
const path = [ 'onLoadOnly' ]; | ||
|
||
const getValue = () => { | ||
return !!get(field, path, false); | ||
}; | ||
|
||
const setValue = (value) => { | ||
editField(field, path, value); | ||
}; | ||
|
||
return ToggleSwitchEntry({ | ||
element: field, | ||
id, | ||
label: 'Execute on load only', | ||
getValue, | ||
setValue | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
packages/form-js-editor/src/render/components/editor-form-fields/EditorJSFunctionField.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { JSFunctionField, iconsByType } from '@bpmn-io/form-js-viewer'; | ||
import { editorFormFieldClasses } from '../Util'; | ||
|
||
const type = 'script'; | ||
|
||
export function EditorJSFunctionField(props) { | ||
const { field } = props; | ||
const { jsFunction = '' } = field; | ||
|
||
const Icon = iconsByType(type); | ||
|
||
let placeholderContent = 'JS function is empty'; | ||
|
||
if (jsFunction.trim()) { | ||
placeholderContent = 'JS function'; | ||
} | ||
|
||
return ( | ||
<div class={ editorFormFieldClasses(type) }> | ||
<div class="fjs-form-field-placeholder"> | ||
<Icon viewBox="0 0 54 54" />{placeholderContent} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
EditorJSFunctionField.config = { | ||
...JSFunctionField.config, | ||
escapeGridRender: false | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/form-js-viewer/src/render/components/form-fields/JSFunctionField.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { useCallback, useEffect, useState } from 'preact/hooks'; | ||
import { useExpressionEvaluation, useDeepCompareMemoize, usePrevious } from '../../hooks'; | ||
import { isObject } from 'min-dash'; | ||
|
||
const type = 'script'; | ||
|
||
export function JSFunctionField(props) { | ||
const { field, onChange } = props; | ||
const { jsFunction, functionParameters, onLoadOnly } = field; | ||
|
||
const [ loadLatch, setLoadLatch ] = useState(false); | ||
|
||
const paramsEval = useExpressionEvaluation(functionParameters); | ||
const params = useDeepCompareMemoize(isObject(paramsEval) ? paramsEval : {}); | ||
|
||
const functionMemo = useCallback((params) => { | ||
|
||
const cleanupCallbacks = []; | ||
|
||
try { | ||
|
||
setLoadLatch(true); | ||
const func = new Function('data', 'setValue', 'onCleanup', jsFunction); | ||
func(params, value => onChange({ field, value }), callback => cleanupCallbacks.push(callback)); | ||
|
||
} catch (error) { | ||
|
||
// invalid expression definition, may happen during editing | ||
if (error instanceof SyntaxError) { | ||
return; | ||
} | ||
|
||
console.error('Error evaluating expression:', error); | ||
onChange({ field, value: null }); | ||
} | ||
|
||
return () => { | ||
cleanupCallbacks.forEach(fn => fn()); | ||
}; | ||
|
||
}, [ jsFunction, field, onChange ]); | ||
|
||
const previousFunctionMemo = usePrevious(functionMemo); | ||
const previousParams = usePrevious(params); | ||
|
||
useEffect(() => { | ||
|
||
// reset load latch | ||
if (!onLoadOnly && loadLatch) { | ||
setLoadLatch(false); | ||
} | ||
|
||
const functionChanged = previousFunctionMemo !== functionMemo; | ||
const paramsChanged = previousParams !== params; | ||
const alreadyLoaded = onLoadOnly && loadLatch; | ||
|
||
const shouldExecute = functionChanged || paramsChanged && !alreadyLoaded; | ||
|
||
if (shouldExecute) { | ||
return functionMemo(params); | ||
} | ||
|
||
}, [ previousFunctionMemo, functionMemo, previousParams, params, loadLatch, onLoadOnly ]); | ||
|
||
return null; | ||
} | ||
|
||
JSFunctionField.config = { | ||
type, | ||
label: 'JS Function', | ||
group: 'basic-input', | ||
keyed: true, | ||
escapeGridRender: true, | ||
create: (options = {}) => ({ | ||
jsFunction: 'setValue(data.value)', | ||
functionParameters: '={\n value: 42\n}', | ||
...options, | ||
}) | ||
}; |
9 changes: 9 additions & 0 deletions
9
packages/form-js-viewer/src/render/components/icons/JSFunction.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters