refactor(plugin-workflow): change canvas card and adjust styles (#1529)
* refactor(plugin-workflow): optimize workflow canvas * feat(plugin-workflow): allow click on node card to open config and adjust styles * fix(plugin-workflow): fix collection trigger linkages
This commit is contained in:
parent
4d44e31b31
commit
37998d03ad
@ -123,6 +123,8 @@ ReadPretty.JSON = (props) => {
|
||||
<pre
|
||||
className={cx(prefixCls, props.className, css`
|
||||
margin-bottom: 0;
|
||||
line-height: 1.5;
|
||||
font-size: 90%;
|
||||
`)}
|
||||
style={props.style}
|
||||
>
|
||||
|
@ -20,7 +20,7 @@ interface AddButtonProps {
|
||||
export function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
const compile = useCompile();
|
||||
const api = useAPIClient();
|
||||
const { workflow, onNodeAdded } = useFlowContext() ?? {};
|
||||
const { workflow, refresh } = useFlowContext() ?? {};
|
||||
if (!workflow) {
|
||||
return null;
|
||||
}
|
||||
@ -30,8 +30,9 @@ export function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
const type = keyPath.pop();
|
||||
const config = {};
|
||||
const [optionKey] = keyPath;
|
||||
const instruction = instructions.get(type);
|
||||
if (optionKey) {
|
||||
const { value } = instructions.get(type)?.options?.find(item => item.key === optionKey) ?? {};
|
||||
const { value } = instruction.options?.find(item => item.key === optionKey) ?? {};
|
||||
Object.assign(config, value);
|
||||
}
|
||||
|
||||
@ -40,47 +41,46 @@ export function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
type,
|
||||
upstreamId: upstream?.id ?? null,
|
||||
branchIndex,
|
||||
title: compile(instruction.title),
|
||||
config
|
||||
}
|
||||
});
|
||||
|
||||
onNodeAdded(node);
|
||||
refresh();
|
||||
}
|
||||
|
||||
const groups = [
|
||||
{ value: 'control', name: `{{t("Control", { ns: "${NAMESPACE}" })}}` },
|
||||
{ value: 'collection', name: `{{t("Collection operations", { ns: "${NAMESPACE}" })}}` },
|
||||
{ value: 'manual', name: `{{t("Manual", { ns: "${NAMESPACE}" })}}` },
|
||||
{ value: 'extended', name: `{{t("Extended types", { ns: "${NAMESPACE}" })}}` },
|
||||
];
|
||||
const instructionList = (Array.from(instructions.getValues()) as Instruction[]);
|
||||
|
||||
const groups = [
|
||||
{ key: 'control', label: `{{t("Control", { ns: "${NAMESPACE}" })}}` },
|
||||
{ key: 'collection', label: `{{t("Collection operations", { ns: "${NAMESPACE}" })}}` },
|
||||
{ key: 'manual', label: `{{t("Manual", { ns: "${NAMESPACE}" })}}` },
|
||||
{ key: 'extended', label: `{{t("Extended types", { ns: "${NAMESPACE}" })}}` },
|
||||
]
|
||||
.filter(group => instructionList.filter(item => item.group === group.key).length)
|
||||
.map(group => {
|
||||
const groupInstructions = instructionList.filter(item => item.group === group.key);
|
||||
|
||||
return {
|
||||
...group,
|
||||
type: 'group',
|
||||
children: groupInstructions.map(item => ({
|
||||
key: item.type,
|
||||
label: item.title,
|
||||
type: item.options ? 'subMenu' : null,
|
||||
children: item.options ? item.options.map(option => ({
|
||||
key: option.key,
|
||||
label: option.label,
|
||||
})) : null
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cx(addButtonClass)}>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu onClick={ev => onCreate(ev)}>
|
||||
{groups.map(group => {
|
||||
const groupInstructions = instructionList.filter(item => item.group === group.value);
|
||||
return groupInstructions.length ? (
|
||||
<Menu.ItemGroup key={group.value} title={compile(group.name)}>
|
||||
{groupInstructions.map(item => item.options
|
||||
? (
|
||||
<Menu.SubMenu key={item.type} title={compile(item.title)}>
|
||||
{item.options.map(option => (
|
||||
<Menu.Item key={option.key}>{compile(option.label)}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
: (
|
||||
<Menu.Item key={item.type}>{compile(item.title)}</Menu.Item>
|
||||
))}
|
||||
</Menu.ItemGroup>
|
||||
) : null;
|
||||
})}
|
||||
</Menu>
|
||||
}
|
||||
overlay={<Menu onClick={ev => onCreate(ev)} items={compile(groups)} />}
|
||||
disabled={workflow.executed}
|
||||
>
|
||||
<Button shape="circle" icon={<PlusOutlined />} />
|
||||
|
@ -117,8 +117,7 @@ export function WorkflowCanvas() {
|
||||
<FlowContext.Provider value={{
|
||||
workflow,
|
||||
nodes,
|
||||
onNodeAdded: refresh,
|
||||
onNodeRemoved: refresh
|
||||
refresh,
|
||||
}}>
|
||||
<div className="workflow-toolbar">
|
||||
<header>
|
||||
|
@ -33,8 +33,8 @@ export default observer(({ value, disabled, onChange }: any) => {
|
||||
const compile = useCompile();
|
||||
const form = useForm();
|
||||
const { getCollection, getCollectionFields } = useCollectionManager();
|
||||
const { values: data } = useForm();
|
||||
const collectionName = data?.config?.collection;
|
||||
const { values: config } = useForm();
|
||||
const collectionName = config?.collection;
|
||||
const fields = getCollectionFields(collectionName)
|
||||
.filter(field => (
|
||||
!field.hidden
|
||||
|
@ -17,10 +17,9 @@ export default {
|
||||
type: 'calculation',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
'config.engine': {
|
||||
engine: {
|
||||
type: 'string',
|
||||
title: `{{t("Calculation engine", { ns: "${NAMESPACE}" })}}`,
|
||||
name: 'config.engine',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'RadioWithTooltip',
|
||||
'x-component-props': {
|
||||
@ -29,10 +28,9 @@ export default {
|
||||
required: true,
|
||||
default: 'math.js'
|
||||
},
|
||||
'config.expression': {
|
||||
expression: {
|
||||
type: 'string',
|
||||
title: `{{t("Calculation expression", { ns: "${NAMESPACE}" })}}`,
|
||||
name: 'config.expression',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
'x-component-props': {
|
||||
@ -40,7 +38,7 @@ export default {
|
||||
},
|
||||
['x-validator'](value, rules, { form }) {
|
||||
const { values } = form;
|
||||
const { evaluate } = evaluators.get(values.config.engine) as Evaluator;
|
||||
const { evaluate } = evaluators.get(values.engine) as Evaluator;
|
||||
const exp = value.trim().replace(/{{([^{}]+)}}/g, '1');
|
||||
try {
|
||||
evaluate(exp);
|
||||
@ -50,7 +48,7 @@ export default {
|
||||
}
|
||||
},
|
||||
'x-reactions': {
|
||||
dependencies: ['config.engine'],
|
||||
dependencies: ['engine'],
|
||||
fulfill: {
|
||||
schema: {
|
||||
description: '{{renderReference($deps[0])}}',
|
||||
|
@ -304,9 +304,8 @@ export default {
|
||||
type: 'condition',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
'config.rejectOnFalse': {
|
||||
rejectOnFalse: {
|
||||
type: 'boolean',
|
||||
name: 'config.rejectOnFalse',
|
||||
title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -324,10 +323,9 @@ export default {
|
||||
}
|
||||
],
|
||||
},
|
||||
'config.engine': {
|
||||
engine: {
|
||||
type: 'string',
|
||||
title: `{{t("Calculation engine", { ns: "${NAMESPACE}" })}}`,
|
||||
name: 'config.engine',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'RadioWithTooltip',
|
||||
'x-component-props': {
|
||||
@ -339,14 +337,13 @@ export default {
|
||||
required: true,
|
||||
default: 'basic',
|
||||
},
|
||||
'config.calculation': {
|
||||
calculation: {
|
||||
type: 'string',
|
||||
name: 'config.calculation',
|
||||
title: `{{t("Condition", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'CalculationConfig',
|
||||
'x-reactions': {
|
||||
dependencies: ['config.engine'],
|
||||
dependencies: ['engine'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{$deps[0] === "basic"}}'
|
||||
@ -355,10 +352,9 @@ export default {
|
||||
},
|
||||
required: true
|
||||
},
|
||||
'config.expression': {
|
||||
expression: {
|
||||
type: 'string',
|
||||
title: `{{t("Condition expression", { ns: "${NAMESPACE}" })}}`,
|
||||
name: 'config.expression',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
'x-component-props': {
|
||||
@ -366,7 +362,7 @@ export default {
|
||||
},
|
||||
['x-validator'](value, rules, { form }) {
|
||||
const { values } = form;
|
||||
const { evaluate } = evaluators.get(values.config.engine);
|
||||
const { evaluate } = evaluators.get(values.engine);
|
||||
const exp = value.trim().replace(/{{([^{}]+)}}/g, '1');
|
||||
try {
|
||||
evaluate(exp);
|
||||
@ -376,7 +372,7 @@ export default {
|
||||
}
|
||||
},
|
||||
'x-reactions': {
|
||||
dependencies: ['config.engine'],
|
||||
dependencies: ['engine'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{$deps[0] !== "basic"}}'
|
||||
|
@ -14,10 +14,7 @@ export default {
|
||||
type: 'create',
|
||||
group: 'collection',
|
||||
fieldset: {
|
||||
'config.collection': {
|
||||
...collection,
|
||||
name: 'config.collection'
|
||||
},
|
||||
collection,
|
||||
// multiple: {
|
||||
// type: 'boolean',
|
||||
// title: '多条数据',
|
||||
@ -28,7 +25,7 @@ export default {
|
||||
// disabled: true
|
||||
// }
|
||||
// },
|
||||
'config.params.values': values
|
||||
'params.values': values
|
||||
},
|
||||
view: {
|
||||
|
||||
|
@ -7,18 +7,16 @@ export default {
|
||||
type: 'delay',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
'config.duration': {
|
||||
duration: {
|
||||
type: 'number',
|
||||
name: 'config.duration',
|
||||
title: `{{t("Duration", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Duration',
|
||||
default: 60000,
|
||||
required: true
|
||||
},
|
||||
'config.endStatus': {
|
||||
endStatus: {
|
||||
type: 'number',
|
||||
name: 'config.endStatus',
|
||||
title: `{{t("End Status", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
|
@ -8,10 +8,9 @@ export default {
|
||||
type: 'destroy',
|
||||
group: 'collection',
|
||||
fieldset: {
|
||||
'config.collection': collection,
|
||||
'config.params': {
|
||||
collection,
|
||||
params: {
|
||||
type: 'object',
|
||||
name: 'config.params',
|
||||
title: '',
|
||||
'x-decorator': 'FormItem',
|
||||
properties: {
|
||||
|
@ -1,18 +1,18 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import {
|
||||
CloseOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
import { Button, message, Modal, Tag, Alert } from 'antd';
|
||||
import { Button, message, Modal, Tag, Alert, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import parse from 'json-templates';
|
||||
|
||||
import { Registry } from '@nocobase/utils/client';
|
||||
import { SchemaComponent, SchemaInitializerItemOptions, useActionContext, useAPIClient, useCompile, useRequest, useResourceActionContext } from '@nocobase/client';
|
||||
import { ActionContext, SchemaComponent, SchemaInitializerItemOptions, useActionContext, useAPIClient, useCompile, useRequest, useResourceActionContext } from '@nocobase/client';
|
||||
|
||||
import { nodeBlockClass, nodeCardClass, nodeClass, nodeHeaderClass, nodeMetaClass, nodeTitleClass } from '../style';
|
||||
import { nodeBlockClass, nodeCardClass, nodeClass, nodeMetaClass, nodeTitleClass } from '../style';
|
||||
import { AddButton } from '../AddButton';
|
||||
import { useFlowContext } from '../FlowContext';
|
||||
|
||||
@ -76,17 +76,11 @@ function useUpdateAction() {
|
||||
message.error(lang('Node in executed workflow cannot be modified'));
|
||||
return;
|
||||
}
|
||||
// TODO: how to do validation separately for each field? especially disabled for dynamic fields?
|
||||
try {
|
||||
await form.submit();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
await form.submit();
|
||||
await api.resource('flow_nodes', data.id).update?.({
|
||||
filterByTk: data.id,
|
||||
values: {
|
||||
title: form.values.title,
|
||||
config: form.values.config
|
||||
config: form.values
|
||||
}
|
||||
});
|
||||
ctx.setVisible(false);
|
||||
@ -153,7 +147,7 @@ export function Node({ data }) {
|
||||
export function RemoveButton() {
|
||||
const { t } = useTranslation();
|
||||
const api = useAPIClient();
|
||||
const { workflow, nodes, onNodeRemoved } = useFlowContext() ?? {};
|
||||
const { workflow, nodes, refresh } = useFlowContext() ?? {};
|
||||
const current = useNodeContext();
|
||||
if (!workflow) {
|
||||
return null;
|
||||
@ -162,10 +156,10 @@ export function RemoveButton() {
|
||||
|
||||
async function onRemove() {
|
||||
async function onOk() {
|
||||
const { data: { data: node } } = await resource.destroy?.({
|
||||
await resource.destroy?.({
|
||||
filterByTk: current.id
|
||||
});
|
||||
onNodeRemoved(node);
|
||||
refresh();
|
||||
}
|
||||
|
||||
const usingNodes = nodes.filter(node => {
|
||||
@ -225,6 +219,7 @@ export function JobButton() {
|
||||
className={cx('workflow-node-job-button', css`
|
||||
border: 2px solid #d9d9d9;
|
||||
border-radius: 50%;
|
||||
cursor: not-allowed;
|
||||
`)}
|
||||
/>
|
||||
);
|
||||
@ -312,131 +307,161 @@ export function JobButton() {
|
||||
}
|
||||
|
||||
export function NodeDefaultView(props) {
|
||||
const compile = useCompile();
|
||||
const { workflow } = useFlowContext() ?? {};
|
||||
if (!workflow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data, children } = props;
|
||||
const compile = useCompile();
|
||||
const api = useAPIClient();
|
||||
const { workflow, refresh } = useFlowContext() ?? {};
|
||||
|
||||
const instruction = instructions.get(data.type);
|
||||
const detailText = workflow.executed ? '{{t("View")}}' : '{{t("Configure")}}';
|
||||
const typeTitle = compile(instruction.title);
|
||||
|
||||
const [editingTitle, setEditingTitle] = useState<string>(data.title ?? typeTitle);
|
||||
const [editingConfig, setEditingConfig] = useState(false);
|
||||
|
||||
async function onChangeTitle(next) {
|
||||
const title = next || typeTitle;
|
||||
setEditingTitle(title);
|
||||
if (title === data.title) {
|
||||
return;
|
||||
}
|
||||
await api.resource('flow_nodes').update?.({
|
||||
filterByTk: data.id,
|
||||
values: {
|
||||
title
|
||||
}
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onOpenDrawer(ev) {
|
||||
if (ev.target === ev.currentTarget) {
|
||||
setEditingConfig(true);
|
||||
return;
|
||||
}
|
||||
const whiteSet = new Set(['workflow-node-meta', 'workflow-node-config-button', 'ant-input-disabled']);
|
||||
for (let el = ev.target; el && el !== ev.currentTarget; el = el.parentNode) {
|
||||
if ((Array.from(el.classList) as string[]).some((name: string) => whiteSet.has(name))) {
|
||||
setEditingConfig(true);
|
||||
ev.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(nodeClass, `workflow-node-type-${data.type}`)}>
|
||||
<div className={cx(nodeCardClass)}>
|
||||
<div className={cx(nodeHeaderClass)}>
|
||||
<div className={cx(nodeMetaClass)}>
|
||||
<Tag>{compile(instruction.title)}</Tag>
|
||||
</div>
|
||||
<h4 className={cx(nodeTitleClass)}>
|
||||
<strong>{data.title}</strong>
|
||||
<span className="workflow-node-id">#{data.id}</span>
|
||||
</h4>
|
||||
<RemoveButton />
|
||||
<JobButton />
|
||||
<div className={cx(nodeCardClass, { configuring: editingConfig })} onClick={onOpenDrawer}>
|
||||
<div className={cx(nodeMetaClass, 'workflow-node-meta')}>
|
||||
<Tag>{typeTitle}</Tag>
|
||||
<span className="workflow-node-id">{data.id}</span>
|
||||
</div>
|
||||
<SchemaComponent
|
||||
scope={instruction.scope}
|
||||
components={instruction.components}
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
...(instruction.view ? { view: instruction.view } : {}),
|
||||
config: {
|
||||
type: 'void',
|
||||
title: detailText,
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
<div>
|
||||
<Input.TextArea
|
||||
disabled={workflow.executed}
|
||||
value={editingTitle}
|
||||
onChange={(ev) => setEditingTitle(ev.target.value)}
|
||||
onBlur={(ev) => onChangeTitle(ev.target.value)}
|
||||
autoSize
|
||||
/>
|
||||
</div>
|
||||
<RemoveButton />
|
||||
<JobButton />
|
||||
<ActionContext.Provider value={{ visible: editingConfig, setVisible: setEditingConfig }}>
|
||||
<SchemaComponent
|
||||
scope={instruction.scope}
|
||||
components={instruction.components}
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
...(instruction.view ? { view: instruction.view } : {}),
|
||||
config: {
|
||||
type: 'void',
|
||||
'x-content': detailText,
|
||||
'x-component': Button,
|
||||
'x-component-props': {
|
||||
type: 'link',
|
||||
className: 'workflow-node-config-button'
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
[`${instruction.type}_${data.id}`]: {
|
||||
type: 'void',
|
||||
title: instruction.title,
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
disabled: workflow.executed,
|
||||
useValues(options) {
|
||||
const d = useNodeContext();
|
||||
return useRequest(() => {
|
||||
return Promise.resolve({ data: d });
|
||||
}, options);
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
...(workflow.executed ? {
|
||||
alert: {
|
||||
'x-component': Alert,
|
||||
'x-component-props': {
|
||||
type: 'warning',
|
||||
showIcon: true,
|
||||
message: `{{t("Node in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
|
||||
className: css`
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
margin-bottom: 2em;
|
||||
`
|
||||
},
|
||||
}
|
||||
} : {}),
|
||||
title: {
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
title: '{{t("Name")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
config: {
|
||||
[`${instruction.type}_${data.id}`]: {
|
||||
type: 'void',
|
||||
title: instruction.title,
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
disabled: workflow.executed,
|
||||
useValues(options) {
|
||||
const { config } = useNodeContext();
|
||||
return useRequest(() => {
|
||||
return Promise.resolve({ data: config });
|
||||
}, options);
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
...(workflow.executed ? {
|
||||
alert: {
|
||||
type: 'void',
|
||||
name: 'config',
|
||||
'x-component': 'fieldset',
|
||||
'x-component': Alert,
|
||||
'x-component-props': {
|
||||
type: 'warning',
|
||||
showIcon: true,
|
||||
message: `{{t("Node in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
|
||||
className: css`
|
||||
.ant-input,
|
||||
.ant-select,
|
||||
.ant-cascader-picker,
|
||||
.ant-picker,
|
||||
.ant-input-number,
|
||||
.ant-input-affix-wrapper{
|
||||
width: auto;
|
||||
min-width: 6em;
|
||||
}
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
margin-bottom: 2em;
|
||||
`
|
||||
},
|
||||
properties: instruction.fieldset
|
||||
}
|
||||
} : {}),
|
||||
fieldset: {
|
||||
type: 'void',
|
||||
'x-component': 'fieldset',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
.ant-input,
|
||||
.ant-select,
|
||||
.ant-cascader-picker,
|
||||
.ant-picker,
|
||||
.ant-input-number,
|
||||
.ant-input-affix-wrapper{
|
||||
width: auto;
|
||||
min-width: 6em;
|
||||
}
|
||||
`
|
||||
},
|
||||
actions: workflow.executed
|
||||
? null
|
||||
: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: useUpdateAction,
|
||||
},
|
||||
properties: instruction.fieldset
|
||||
},
|
||||
actions: workflow.executed
|
||||
? null
|
||||
: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
},
|
||||
},
|
||||
}
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: useUpdateAction,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} as ISchema
|
||||
}
|
||||
}
|
||||
} as ISchema
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -34,9 +34,8 @@ export default {
|
||||
type: 'manual',
|
||||
group: 'manual',
|
||||
fieldset: {
|
||||
'config.assignees': {
|
||||
assignees: {
|
||||
type: 'array',
|
||||
name: 'config.assignees',
|
||||
title: `{{t("Assignees", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'AssigneesSelect',
|
||||
@ -53,15 +52,14 @@ export default {
|
||||
required: true,
|
||||
default: [],
|
||||
},
|
||||
'config.mode': {
|
||||
mode: {
|
||||
type: 'number',
|
||||
name: 'config.mode',
|
||||
title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ModeConfig',
|
||||
default: 1,
|
||||
'x-reactions': {
|
||||
dependencies: ['config.assignees'],
|
||||
dependencies: ['assignees'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{$deps[0].length > 1}}',
|
||||
@ -69,7 +67,7 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
'config.schema': {
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: `{{t("User interface", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { css, cx } from "@emotion/css";
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from "antd";
|
||||
|
||||
import { NodeDefaultView } from ".";
|
||||
@ -17,9 +17,8 @@ export default {
|
||||
type: 'parallel',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
'config.mode': {
|
||||
mode: {
|
||||
type: 'string',
|
||||
name: 'config.mode',
|
||||
title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'RadioWithTooltip',
|
||||
@ -50,7 +49,7 @@ export default {
|
||||
},
|
||||
render(data) {
|
||||
const { id, config: { mode } } = data;
|
||||
const { nodes } = useFlowContext();
|
||||
const { workflow, nodes } = useFlowContext();
|
||||
const branches = nodes.reduce((result, node) => {
|
||||
if (node.upstreamId === id && node.branchIndex != null) {
|
||||
return result.concat(node);
|
||||
@ -90,6 +89,7 @@ export default {
|
||||
shape="circle"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setBranchCount(branchCount - 1)}
|
||||
disabled={workflow.executed}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@ -104,7 +104,9 @@ export default {
|
||||
height: 2em;
|
||||
`}
|
||||
>
|
||||
<Tooltip title={lang('Add branch')}>
|
||||
<Tooltip title={lang('Add branch')} className={css`
|
||||
visibility: ${workflow.executed ? 'hidden' : 'visible'}
|
||||
`}>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
className={css`
|
||||
@ -117,6 +119,7 @@ export default {
|
||||
}
|
||||
`}
|
||||
onClick={() => setBranchCount(branchCount + 1)}
|
||||
disabled={workflow.executed}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ export default {
|
||||
type: 'query',
|
||||
group: 'collection',
|
||||
fieldset: {
|
||||
'config.collection': collection,
|
||||
collection,
|
||||
// 'config.multiple': {
|
||||
// type: 'boolean',
|
||||
// title: `{{t("Multiple records", { ns: "${NAMESPACE}" })}}`,
|
||||
@ -25,9 +25,8 @@ export default {
|
||||
// disabled: true
|
||||
// }
|
||||
// },
|
||||
'config.params': {
|
||||
params: {
|
||||
type: 'object',
|
||||
name: 'config.params',
|
||||
title: '',
|
||||
'x-decorator': 'FormItem',
|
||||
properties: {
|
||||
|
@ -11,9 +11,8 @@ export default {
|
||||
type: 'request',
|
||||
group: 'extended',
|
||||
fieldset: {
|
||||
'config.method': {
|
||||
method: {
|
||||
type: 'string',
|
||||
name: 'config.method',
|
||||
required: true,
|
||||
title: `{{t("HTTP method", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
@ -31,9 +30,8 @@ export default {
|
||||
],
|
||||
default: 'POST'
|
||||
},
|
||||
'config.url': {
|
||||
url: {
|
||||
type: 'string',
|
||||
name: 'config.url',
|
||||
required: true,
|
||||
title: `{{t("URL", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
@ -52,9 +50,8 @@ export default {
|
||||
placeholder: 'https://www.nocobase.com',
|
||||
},
|
||||
},
|
||||
'config.headers': {
|
||||
headers: {
|
||||
type: 'array',
|
||||
name: 'config.headers',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: `{{t("Headers", { ns: "${NAMESPACE}" })}}`,
|
||||
@ -99,9 +96,8 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
'config.params': {
|
||||
params: {
|
||||
type: 'array',
|
||||
name: 'config.params',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
title: `{{t("Parameters", { ns: "${NAMESPACE}" })}}`,
|
||||
@ -145,9 +141,8 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
'config.data': {
|
||||
data: {
|
||||
type: 'string',
|
||||
name: 'config.data',
|
||||
title: `{{t("Body", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
@ -159,15 +154,14 @@ export default {
|
||||
},
|
||||
placeholder: `{{t("Input request data", { ns: "${NAMESPACE}" })}}`,
|
||||
className: css`
|
||||
font-size: 85%;
|
||||
font-size: 90%;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
`
|
||||
},
|
||||
description: `{{t("Only support standard JSON data", { ns: "${NAMESPACE}" })}}`,
|
||||
},
|
||||
'config.timeout': {
|
||||
timeout: {
|
||||
type: 'number',
|
||||
name: 'config.timeout',
|
||||
title: `{{t("Timeout config", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
@ -179,9 +173,8 @@ export default {
|
||||
defaultValue: 5000,
|
||||
},
|
||||
},
|
||||
'config.ignoreFail': {
|
||||
ignoreFail: {
|
||||
type: 'boolean',
|
||||
name: 'config.ignoreFail',
|
||||
title: `{{t("Ignore fail request and continue workflow", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
|
@ -12,12 +12,19 @@ export default {
|
||||
type: 'update',
|
||||
group: 'collection',
|
||||
fieldset: {
|
||||
'config.collection': collection,
|
||||
'config.params.filter': {
|
||||
...filter,
|
||||
title: `{{t("Only update records matching conditions", { ns: "${NAMESPACE}" })}}`,
|
||||
},
|
||||
'config.params.values': values
|
||||
collection,
|
||||
params: {
|
||||
type: 'object',
|
||||
title: '',
|
||||
'x-decorator': 'FormItem',
|
||||
properties: {
|
||||
filter: {
|
||||
...filter,
|
||||
title: `{{t("Only update records matching conditions", { ns: "${NAMESPACE}" })}}`,
|
||||
},
|
||||
values
|
||||
}
|
||||
}
|
||||
},
|
||||
view: {
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { NAMESPACE } from "../locale";
|
||||
export const collection = {
|
||||
type: 'string',
|
||||
title: '{{t("Collection")}}',
|
||||
name: 'config.collection',
|
||||
required: true,
|
||||
'x-reactions': ['{{useCollectionDataSource()}}'],
|
||||
'x-decorator': 'FormItem',
|
||||
@ -19,7 +18,6 @@ export const collection = {
|
||||
export const values = {
|
||||
type: 'object',
|
||||
title: '{{t("Fields values")}}',
|
||||
name: 'config.params.values',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelAlign: 'left',
|
||||
@ -34,7 +32,6 @@ export const values = {
|
||||
export const filter = {
|
||||
type: 'object',
|
||||
title: '{{t("Filter")}}',
|
||||
name: 'config.params.filter',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelAlign: 'left',
|
||||
@ -46,7 +43,7 @@ export const filter = {
|
||||
'x-component-props': {
|
||||
useProps() {
|
||||
const { values } = useForm();
|
||||
const options = useCollectionFilterOptions(values.config?.collection);
|
||||
const options = useCollectionFilterOptions(values?.collection);
|
||||
return {
|
||||
options,
|
||||
className: css`
|
||||
|
@ -157,19 +157,27 @@ export const nodeClass = css`
|
||||
`;
|
||||
|
||||
export const nodeCardClass = css`
|
||||
position: relative;
|
||||
width: 20em;
|
||||
background: #fff;
|
||||
padding: 1em;
|
||||
box-shadow: 0 .25em .5em rgba(0, 0, 0, .1);
|
||||
border-radius: .5em;
|
||||
cursor: pointer;
|
||||
transition: box-shadow .3s ease;
|
||||
|
||||
&.configuring{
|
||||
box-shadow: 0 .25em 1em rgba(0, 100, 200, .25);
|
||||
}
|
||||
|
||||
.workflow-node-remove-button,
|
||||
.workflow-node-job-button{
|
||||
position: absolute;
|
||||
right: -.5em;
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
.workflow-node-remove-button{
|
||||
right: .5em;
|
||||
top: .5em;
|
||||
color: #999;
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease;
|
||||
@ -185,8 +193,8 @@ export const nodeCardClass = css`
|
||||
|
||||
.workflow-node-job-button{
|
||||
display: flex;
|
||||
top: 0;
|
||||
right: 0;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
min-width: 1.25rem;
|
||||
@ -200,7 +208,32 @@ export const nodeCardClass = css`
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input{
|
||||
font-weight: bold;
|
||||
|
||||
&:not(:focus){
|
||||
transition: background-color .3s ease, border-color .3s ease;
|
||||
border-color: #f7f7f7;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
&:not(:disabled):hover{
|
||||
border-color: #eee;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&:disabled:hover{
|
||||
border-color: #f7f7f7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workflow-node-config-button{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
box-shadow: 0 .25em .5em rgba(0, 0, 0, .25);
|
||||
|
||||
.workflow-node-remove-button{
|
||||
opacity: 1;
|
||||
}
|
||||
@ -213,13 +246,20 @@ export const nodeHeaderClass = css`
|
||||
|
||||
export const nodeMetaClass = css`
|
||||
margin-bottom: .5em;
|
||||
|
||||
.workflow-node-id{
|
||||
color: #999;
|
||||
|
||||
&:before{
|
||||
content: "#"
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const nodeTitleClass = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
|
||||
.workflow-node-id{
|
||||
color: #999;
|
||||
}
|
||||
@ -234,4 +274,10 @@ export const nodeSubtreeClass = css`
|
||||
export const addButtonClass = css`
|
||||
flex-shrink: 0;
|
||||
padding: 2em 0;
|
||||
|
||||
> .ant-btn{
|
||||
&:disabled{
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Select } from 'antd';
|
||||
import { onFieldValueChange } from '@formily/core';
|
||||
import { observer, useForm, useFormEffects } from '@formily/react';
|
||||
import { observer, useForm } from '@formily/react';
|
||||
|
||||
import {
|
||||
SchemaInitializerItemOptions,
|
||||
@ -16,26 +15,17 @@ import { CollectionBlockInitializer } from '../components/CollectionBlockInitial
|
||||
import { CollectionFieldInitializers } from '../components/CollectionFieldInitializers';
|
||||
import { NAMESPACE, useWorkflowTranslation } from '../locale';
|
||||
|
||||
const FieldsSelect = observer((props) => {
|
||||
const FieldsSelect = observer((props: any) => {
|
||||
const { filter = () => true, ...others } = props;
|
||||
const compile = useCompile();
|
||||
const { getCollectionFields } = useCollectionManager();
|
||||
const { values, clearFormGraph, setValuesIn } = useForm();
|
||||
const fields = getCollectionFields(values?.config?.collection);
|
||||
useFormEffects(() => {
|
||||
onFieldValueChange('config.collection', (field) => {
|
||||
clearFormGraph('config.changed');
|
||||
setValuesIn('config.condition', null);
|
||||
});
|
||||
});
|
||||
const { values } = useForm();
|
||||
const fields = getCollectionFields(values?.collection);
|
||||
|
||||
return (
|
||||
<Select {...props}>
|
||||
<Select {...others}>
|
||||
{fields
|
||||
.filter(field => (
|
||||
!field.hidden
|
||||
&& (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true)
|
||||
&& !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type)
|
||||
))
|
||||
.filter(filter)
|
||||
.map(field => (
|
||||
<Select.Option key={field.name} value={field.name}>{compile(field.uiSchema?.title)}</Select.Option>
|
||||
))}
|
||||
@ -61,40 +51,33 @@ export default {
|
||||
title: `{{t("Collection event", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'collection',
|
||||
fieldset: {
|
||||
'config.collection': {
|
||||
collection: {
|
||||
...collection,
|
||||
['x-reactions']: [
|
||||
...collection['x-reactions'],
|
||||
{
|
||||
target: 'config.mode',
|
||||
target: 'changed',
|
||||
effects: ['onFieldValueChange'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
},
|
||||
value: []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 'config.changed',
|
||||
target: 'condition',
|
||||
effects: ['onFieldValueChange'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 'config.condition',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
},
|
||||
value: null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'config.mode': {
|
||||
mode: {
|
||||
type: 'number',
|
||||
title: `{{t("Trigger on", { ns: "${NAMESPACE}" })}}`,
|
||||
name: 'config.mode',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
@ -104,7 +87,15 @@ export default {
|
||||
required: true,
|
||||
'x-reactions': [
|
||||
{
|
||||
target: 'config.changed',
|
||||
dependencies: ['collection'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$deps[0]}}',
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 'changed',
|
||||
fulfill: {
|
||||
state: {
|
||||
disabled: `{{!($self.value & ${COLLECTION_TRIGGER_MODE.UPDATED})}}`,
|
||||
@ -113,23 +104,69 @@ export default {
|
||||
},
|
||||
]
|
||||
},
|
||||
'config.changed': {
|
||||
changed: {
|
||||
type: 'array',
|
||||
name: 'changed',
|
||||
title: `{{t("Changed fields", { ns: "${NAMESPACE}" })}}`,
|
||||
description: `{{t("Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'FieldsSelect',
|
||||
'x-component-props': {
|
||||
mode: 'multiple',
|
||||
placeholder: '{{t("Select Field")}}'
|
||||
}
|
||||
placeholder: '{{t("Select Field")}}',
|
||||
filter(field) {
|
||||
return (
|
||||
!field.hidden
|
||||
&& (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true)
|
||||
&& !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type)
|
||||
);
|
||||
}
|
||||
},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['collection'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$deps[0]}}',
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
'config.condition': {
|
||||
condition: {
|
||||
...filter,
|
||||
name: 'config.condition',
|
||||
title: `{{t("Only triggers when match conditions", { ns: "${NAMESPACE}" })}}`
|
||||
}
|
||||
title: `{{t("Only triggers when match conditions", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['collection'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$deps[0]}}',
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
// appends: {
|
||||
// type: 'array',
|
||||
// title: `{{t("Prefetch fields", { ns: "${NAMESPACE}" })}}`,
|
||||
// description: `{{t("Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.", { ns: "${NAMESPACE}" })}}`,
|
||||
// 'x-decorator': 'FormItem',
|
||||
// 'x-component': 'FieldsSelect',
|
||||
// 'x-component-props': {
|
||||
// mode: 'multiple',
|
||||
// placeholder: '{{t("Select Field")}}'
|
||||
// },
|
||||
// 'x-reactions': [
|
||||
// {
|
||||
// dependencies: ['collection'],
|
||||
// fulfill: {
|
||||
// state: {
|
||||
// visible: '{{!!$deps[0]}}',
|
||||
// },
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// },
|
||||
},
|
||||
scope: {
|
||||
useCollectionDataSource
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useState } from "react";
|
||||
import { css, cx } from "@emotion/css";
|
||||
import { ISchema, useForm } from "@formily/react";
|
||||
import { Registry } from "@nocobase/utils/client";
|
||||
import { message, Tag, Alert } from "antd";
|
||||
import React from "react";
|
||||
import { message, Tag, Alert, Button, Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InfoOutlined } from '@ant-design/icons';
|
||||
|
||||
import { SchemaComponent, SchemaInitializerItemOptions, useActionContext, useAPIClient, useCompile, useRequest, useResourceActionContext } from '@nocobase/client';
|
||||
import { ActionContext, SchemaComponent, SchemaInitializerItemOptions, useActionContext, useAPIClient, useCompile, useRequest, useResourceActionContext } from '@nocobase/client';
|
||||
|
||||
import { nodeCardClass, nodeHeaderClass, nodeMetaClass, nodeTitleClass } from "../style";
|
||||
import { nodeCardClass, nodeMetaClass, nodeTitleClass } from "../style";
|
||||
import { useFlowContext } from "../FlowContext";
|
||||
import collection from './collection';
|
||||
import schedule from "./schedule/";
|
||||
@ -16,7 +16,6 @@ import { lang, NAMESPACE } from "../locale";
|
||||
|
||||
|
||||
function useUpdateConfigAction() {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm();
|
||||
const api = useAPIClient();
|
||||
const { workflow } = useFlowContext() ?? {};
|
||||
@ -25,13 +24,15 @@ function useUpdateConfigAction() {
|
||||
return {
|
||||
async run() {
|
||||
if (workflow.executed) {
|
||||
message.error(t('Trigger in executed workflow cannot be modified'));
|
||||
message.error(lang('Trigger in executed workflow cannot be modified'));
|
||||
return;
|
||||
}
|
||||
await form.submit();
|
||||
await api.resource('workflows').update?.({
|
||||
filterByTk: workflow.id,
|
||||
values: form.values
|
||||
values: {
|
||||
config: form.values
|
||||
}
|
||||
});
|
||||
ctx.setVisible(false);
|
||||
refresh();
|
||||
@ -127,108 +128,151 @@ function TriggerExecution() {
|
||||
}
|
||||
|
||||
export const TriggerConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const api = useAPIClient();
|
||||
const compile = useCompile();
|
||||
const { workflow } = useFlowContext();
|
||||
const { workflow, refresh } = useFlowContext();
|
||||
if (!workflow || !workflow.type) {
|
||||
return null;
|
||||
}
|
||||
const { type, config, executed } = workflow;
|
||||
const { title, fieldset, scope, components } = triggers.get(type);
|
||||
const { title, type, config, executed } = workflow;
|
||||
const { title: typeTitle, fieldset, scope, components } = triggers.get(type);
|
||||
const detailText = executed ? '{{t("View")}}' : '{{t("Configure")}}';
|
||||
const titleText = `${lang('Trigger')}: ${compile(title)}`;
|
||||
const titleText = `${lang('Trigger')}: ${compile(typeTitle)}`;
|
||||
|
||||
const [editingTitle, setEditingTitle] = useState<string>(title ?? typeTitle);
|
||||
const [editingConfig, setEditingConfig] = useState(false);
|
||||
|
||||
async function onChangeTitle(next) {
|
||||
const t = next || typeTitle;
|
||||
setEditingTitle(t);
|
||||
if (t === title) {
|
||||
return;
|
||||
}
|
||||
await api.resource('workflows').update?.({
|
||||
filterByTk: workflow.id,
|
||||
values: {
|
||||
title: t
|
||||
}
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onOpenDrawer(ev) {
|
||||
if (ev.target === ev.currentTarget) {
|
||||
setEditingConfig(true);
|
||||
return;
|
||||
}
|
||||
const whiteSet = new Set(['workflow-node-meta', 'workflow-node-config-button', 'ant-input-disabled']);
|
||||
for (let el = ev.target; el && el !== ev.currentTarget; el = el.parentNode) {
|
||||
if (Array.from(el.classList).some((name: string) => whiteSet.has(name))) {
|
||||
setEditingConfig(true);
|
||||
ev.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(nodeCardClass)}>
|
||||
<div className={cx(nodeHeaderClass)}>
|
||||
<div className={cx(nodeMetaClass)}>
|
||||
<Tag color="gold">{lang('Trigger')}</Tag>
|
||||
</div>
|
||||
<h4>{compile(title)}</h4>
|
||||
<TriggerExecution />
|
||||
<div className={cx(nodeCardClass)} onClick={onOpenDrawer}>
|
||||
<div className={cx(nodeMetaClass, 'workflow-node-meta')}>
|
||||
<Tag color="gold">{titleText}</Tag>
|
||||
</div>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
title: detailText,
|
||||
'x-component': 'Action.Link',
|
||||
name: 'drawer',
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: titleText,
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
disabled: workflow.executed,
|
||||
useValues(options) {
|
||||
return useRequest(() => Promise.resolve({
|
||||
data: { config },
|
||||
}), options);
|
||||
<div>
|
||||
<Input.TextArea
|
||||
value={editingTitle}
|
||||
onChange={(ev) => setEditingTitle(ev.target.value)}
|
||||
onBlur={(ev) => onChangeTitle(ev.target.value)}
|
||||
autoSize
|
||||
/>
|
||||
</div>
|
||||
<TriggerExecution />
|
||||
<ActionContext.Provider value={{ visible: editingConfig, setVisible: setEditingConfig }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
config: {
|
||||
type: 'void',
|
||||
'x-content': detailText,
|
||||
'x-component': Button,
|
||||
'x-component-props': {
|
||||
type: 'link',
|
||||
className: 'workflow-node-config-button'
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
config: {
|
||||
type: 'void',
|
||||
name: 'config',
|
||||
'x-component': 'fieldset',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
.ant-select{
|
||||
width: auto;
|
||||
min-width: 6em;
|
||||
}
|
||||
`
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: titleText,
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
disabled: workflow.executed,
|
||||
useValues(options) {
|
||||
return useRequest(() => Promise.resolve({
|
||||
data: config,
|
||||
}), options);
|
||||
},
|
||||
properties: {
|
||||
...(executed ? {
|
||||
alert: {
|
||||
'x-component': Alert,
|
||||
'x-component-props': {
|
||||
type: 'warning',
|
||||
showIcon: true,
|
||||
message: `{{t("Trigger in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
|
||||
className: css`
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
margin-bottom: 2em;
|
||||
`
|
||||
},
|
||||
}
|
||||
} : {}),
|
||||
...fieldset
|
||||
}
|
||||
},
|
||||
actions: executed
|
||||
? null
|
||||
: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
properties: {
|
||||
...(executed ? {
|
||||
alert: {
|
||||
'x-component': Alert,
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
type: 'warning',
|
||||
showIcon: true,
|
||||
message: `{{t("Trigger in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
|
||||
className: css`
|
||||
width: 100%;
|
||||
font-size: 85%;
|
||||
margin-bottom: 2em;
|
||||
`
|
||||
},
|
||||
}
|
||||
} : {}),
|
||||
fieldset: {
|
||||
type: 'void',
|
||||
'x-component': 'fieldset',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
.ant-select{
|
||||
width: auto;
|
||||
min-width: 6em;
|
||||
}
|
||||
`
|
||||
},
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: useUpdateConfigAction
|
||||
properties: fieldset
|
||||
},
|
||||
actions: executed
|
||||
? null
|
||||
: {
|
||||
type: 'void',
|
||||
'x-component': 'Action.Drawer.Footer',
|
||||
properties: {
|
||||
cancel: {
|
||||
title: '{{t("Cancel")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ cm.useCancelAction }}',
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
title: '{{t("Submit")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: useUpdateConfigAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
scope={scope}
|
||||
components={components}
|
||||
/>
|
||||
}}
|
||||
scope={scope}
|
||||
components={components}
|
||||
/>
|
||||
</ActionContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export const DateFieldsSelect: React.FC<any> = observer((props) => {
|
||||
const compile = useCompile();
|
||||
const { getCollectionFields } = useCollectionManager();
|
||||
const { values } = useForm();
|
||||
const fields = getCollectionFields(values?.config?.collection);
|
||||
const fields = getCollectionFields(values?.collection);
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
@ -33,7 +33,7 @@ const ModeFieldsets = {
|
||||
'x-component': 'RepeatField',
|
||||
'x-reactions': [
|
||||
{
|
||||
target: 'config.endsOn',
|
||||
target: 'endsOn',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -41,7 +41,7 @@ const ModeFieldsets = {
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 'config.limit',
|
||||
target: 'limit',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -79,7 +79,7 @@ const ModeFieldsets = {
|
||||
...collection['x-reactions'],
|
||||
{
|
||||
// only full path works
|
||||
target: 'config.startsOn',
|
||||
target: 'startsOn',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -95,7 +95,7 @@ const ModeFieldsets = {
|
||||
'x-component': 'OnField',
|
||||
'x-reactions': [
|
||||
{
|
||||
target: 'config.repeat',
|
||||
target: 'repeat',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -113,7 +113,7 @@ const ModeFieldsets = {
|
||||
'x-component': 'RepeatField',
|
||||
'x-reactions': [
|
||||
{
|
||||
target: 'config.endsOn',
|
||||
target: 'endsOn',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -121,7 +121,7 @@ const ModeFieldsets = {
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 'config.limit',
|
||||
target: 'limit',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
@ -157,16 +157,15 @@ const scheduleModeOptions = [
|
||||
|
||||
export const ScheduleConfig = () => {
|
||||
const { values = {}, clearFormGraph } = useForm();
|
||||
const { config = {} } = values;
|
||||
const [mode, setMode] = useState(config.mode);
|
||||
const [mode, setMode] = useState(values.mode);
|
||||
useFormEffects(() => {
|
||||
onFieldValueChange('config.mode', (field) => {
|
||||
onFieldValueChange('mode', (field) => {
|
||||
setMode(field.value);
|
||||
clearFormGraph('config.collection');
|
||||
clearFormGraph('config.startsOn');
|
||||
clearFormGraph('config.repeat');
|
||||
clearFormGraph('config.endsOn');
|
||||
clearFormGraph('config.limit');
|
||||
clearFormGraph('collection');
|
||||
clearFormGraph('startsOn');
|
||||
clearFormGraph('repeat');
|
||||
clearFormGraph('endsOn');
|
||||
clearFormGraph('limit');
|
||||
});
|
||||
});
|
||||
|
||||
@ -182,7 +181,8 @@ export const ScheduleConfig = () => {
|
||||
'x-component-props': {
|
||||
options: scheduleModeOptions
|
||||
},
|
||||
required: true
|
||||
required: true,
|
||||
default: SCHEDULE_MODE.STATIC
|
||||
}}
|
||||
/>
|
||||
<SchemaComponent
|
||||
|
@ -14,8 +14,7 @@ export default {
|
||||
type: 'schedule',
|
||||
fieldset: {
|
||||
config: {
|
||||
type: 'object',
|
||||
name: 'config',
|
||||
type: 'void',
|
||||
'x-component': 'ScheduleConfig',
|
||||
'x-component-props': {
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user