Compare commits

...

1 Commits
main ... v1.2.2

Author SHA1 Message Date
github-actions[bot] 51eb6063a4
Feat: refactor the editing mode of the workflow (#342)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 3563dbe98d)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-01-25 18:41:39 +08:00
15 changed files with 487 additions and 371 deletions

View File

@ -42,10 +42,6 @@
"@alifd/meet-react": "^2.0.0", "@alifd/meet-react": "^2.0.0",
"@antv/g6": "4.3.11", "@antv/g6": "4.3.11",
"@b-design/ui": "^1.0.63", "@b-design/ui": "^1.0.63",
"@types/js-yaml": "^4.0.1",
"@types/lodash": "^4.14.176",
"@types/react": "^16.3.0",
"@types/react-cookies": "^0.1.0",
"ansi-to-react": "^6.1.6", "ansi-to-react": "^6.1.6",
"axios": "0.24.0", "axios": "0.24.0",
"diagram-maker": "^1.3.0", "diagram-maker": "^1.3.0",
@ -72,7 +68,8 @@
"redux": "4.1.2", "redux": "4.1.2",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"tiny-pubsub": "^1.1.0", "tiny-pubsub": "^1.1.0",
"tsx-control-statements": "4.1.1" "tsx-control-statements": "4.1.1",
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.10", "@babel/core": "^7.12.10",
@ -87,6 +84,11 @@
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
"@types/react-copy-to-clipboard": "^5.0.2", "@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"@types/js-yaml": "^4.0.1",
"@types/lodash": "^4.14.176",
"@types/react": "^16.3.0",
"@types/react-cookies": "^0.1.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^4.0.0", "@typescript-eslint/eslint-plugin": "^4.0.0",
"@typescript-eslint/parser": "^4.0.0", "@typescript-eslint/parser": "^4.0.0",
"@umijs/fabric": "2.8.1", "@umijs/fabric": "2.8.1",

View File

@ -196,6 +196,7 @@ class UISchema extends Component<Props, State> {
<Switch <Switch
id={switchResult.id} id={switchResult.id}
onChange={switchResult.onChange} onChange={switchResult.onChange}
size="medium"
checked={switchResult.value ? true : false} checked={switchResult.value ? true : false}
/> />
</Form.Item> </Form.Item>

View File

@ -260,3 +260,10 @@ export interface Trigger {
createTime?: string; createTime?: string;
updateTime?: string; updateTime?: string;
} }
export interface WorkflowStep {
name: string;
alias: string;
description?: string;
type: string;
}

View File

@ -48,7 +48,7 @@ class TopBar extends Component<Props, State> {
} }
componentDidMount() { componentDidMount() {
this.loadSystemInfo(); //this.loadSystemInfo();
} }
loadSystemInfo = () => { loadSystemInfo = () => {
@ -183,13 +183,13 @@ class TopBar extends Component<Props, State> {
<Icon size={14} type="help1" /> <Icon size={14} type="help1" />
</a> </a>
</div> </div>
<div className="vela-item"> {/* <div className="vela-item">
<Icon <Icon
onClick={this.showUserExperienceImprovementPlan} onClick={this.showUserExperienceImprovementPlan}
size={14} size={14}
type="exclamation-circle" type="exclamation-circle"
/> />
</div> </div> */}
</div> </div>
</Row> </Row>
<Dialog <Dialog

View File

@ -105,29 +105,34 @@ export default {
workflow.default = option.default; workflow.default = option.default;
const { nodes, edges } = data; const { nodes, edges } = data;
const nodeIndex = {}; const nodeIndex = {};
let index = 0; Object.keys(nodes).map((key) => {
const steps = Object.keys(nodes).map((key) => {
if (nodes[key]) { if (nodes[key]) {
nodeIndex[nodes[key].id] = index; let nodeConfig = Object.assign({}, nodes[key].consumerData);
index++;
const nodeConfig = nodes[key].consumerData;
if (nodeConfig && nodeConfig.properties && typeof nodeConfig.properties != 'string') { if (nodeConfig && nodeConfig.properties && typeof nodeConfig.properties != 'string') {
return Object.assign(nodeConfig, { properties: JSON.stringify(nodeConfig.properties) }); nodeConfig = Object.assign(nodeConfig, {
properties: JSON.stringify(nodeConfig.properties),
});
} }
return nodeConfig; nodeIndex[nodes[key].id] = nodeConfig;
} }
}); });
let next = edges.prev;
let steps = [];
let srcMap = {};
Object.keys(edges).map((key) => { Object.keys(edges).map((key) => {
const edge = edges[key]; if (edges[key].src == 'prev') {
if (nodeIndex[edge.src] > nodeIndex[edge.dest]) { next = edges[key];
const i = steps[nodeIndex[edge.src]];
const oldIndex = nodeIndex[edge.src];
steps[nodeIndex[edge.src]] = steps[nodeIndex[edge.dest]];
steps[nodeIndex[edge.dest]] = i;
nodeIndex[edge.src] = nodeIndex[edge.dest];
nodeIndex[edge.dest] = oldIndex;
} }
srcMap[edges[key].src] = edges[key];
}); });
while (next != undefined) {
if (next.dest && next.dest != 'next') {
steps.push(nodeIndex[next.dest]);
next = srcMap[next.dest];
} else {
next = undefined;
}
}
workflow.steps = steps; workflow.steps = steps;
yield call(createWorkFlow, workflow); yield call(createWorkFlow, workflow);
if (action.callback) { if (action.callback) {
@ -141,7 +146,7 @@ function transData(workflowList = [], appName) {
const newWorkflows = _.cloneDeep(workflowList); const newWorkflows = _.cloneDeep(workflowList);
if (newWorkflows && newWorkflows.length != 0) { if (newWorkflows && newWorkflows.length != 0) {
newWorkflows.forEach((workflow) => { newWorkflows.forEach((workflow) => {
convertWorkflowStep(workflow, appName, 32); convertWorkflowStep(workflow, appName, 0);
}); });
return newWorkflows; return newWorkflows;
} else { } else {
@ -149,18 +154,39 @@ function transData(workflowList = [], appName) {
} }
} }
export function convertWorkflowStep(workflow, appName, initPosition = 32) { export function convertWorkflowStep(workflow, appName, initPosition = 32, edit = false) {
const nodeWidth = 140; const nodeWidth = 200;
const nodeHeight = 40; const nodeHeight = 80;
const nodeInterval = 80; const nodeInterval = 120;
const nodes = {}; const nodes = {};
const edges = {}; const edges = {};
let position = initPosition; let position = initPosition;
if (edit) {
nodes.prev = {
id: 'prev',
typeId: 'prev',
diagramMakerData: {
position: {
x: position,
y: 130,
},
size: {
width: nodeWidth,
height: nodeHeight,
},
selected: false,
},
};
position += nodeWidth + nodeInterval;
}
if (workflow.steps) { if (workflow.steps) {
workflow.steps.forEach((item, index, array) => { workflow.steps.forEach((item, index, array) => {
edges[item.name] = {}; edges[item.name] = {};
edges[item.name].dest = if (workflow.steps && workflow.steps[index + 1]) {
workflow.steps && workflow.steps[index + 1] && workflow.steps[index + 1].name; edges[item.name].dest = workflow.steps[index + 1].name;
} else if (edit) {
edges[item.name].dest = 'next';
}
edges[item.name].diagramMakerData = { edges[item.name].diagramMakerData = {
selected: false, selected: false,
}; };
@ -169,7 +195,7 @@ export function convertWorkflowStep(workflow, appName, initPosition = 32) {
nodes[item.name] = {}; nodes[item.name] = {};
nodes[item.name].id = item.name; nodes[item.name].id = item.name;
nodes[item.name].typeId = item.type; nodes[item.name].typeId = 'common';
nodes[item.name].consumerData = { nodes[item.name].consumerData = {
alias: item.alias || '', alias: item.alias || '',
dependsOn: null, dependsOn: null,
@ -191,8 +217,32 @@ export function convertWorkflowStep(workflow, appName, initPosition = 32) {
}; };
position += nodeWidth + nodeInterval; position += nodeWidth + nodeInterval;
}); });
edges.prev = {
dest: workflow.steps[0].name,
src: 'prev',
id: 'prev',
diagramMakerData: {
selected: false,
},
};
}
if (edit) {
nodes.next = {
id: 'next',
typeId: 'next',
diagramMakerData: {
position: {
x: position,
y: 130,
},
size: {
width: nodeWidth,
height: nodeHeight,
},
selected: false,
},
};
} }
workflow.appName = appName; workflow.appName = appName;
workflow.option = { workflow.option = {
edit: false, edit: false,

View File

@ -2,31 +2,6 @@ import type { DiagramMakerNodes, DiagramMakerEdges } from 'diagram-maker';
export const WORFLOW_NODE_WIDTH = 120; export const WORFLOW_NODE_WIDTH = 120;
export const WORKFLOW_COMMON_PANNEL = {
p1: {
id: 'p1',
position: {
x: 10,
y: 10,
},
size: {
width: 230,
height: 280,
},
},
p2: {
id: 'p2',
position: {
x: 10,
y: 10,
},
size: {
width: 420,
height: 40,
},
},
};
export type WorkFlowOption = { export type WorkFlowOption = {
default: boolean; default: boolean;
edit: boolean; edit: boolean;

View File

@ -1,13 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import { Button } from '@b-design/ui';
import { connect } from 'dva'; import { connect } from 'dva';
import WrokflowComponent from './workflow-component'; import WorkflowComponent from './workflow-component';
import type { WorkFlowData } from './entity'; import type { WorkFlowData } from './entity';
import { getWorkFlowDefinitions } from '../../api/workflows'; import { getWorkFlowDefinitions } from '../../api/workflows';
import './index.less'; import './index.less';
import Translation from '../../components/Translation';
type Props = { type Props = {
workflowList: WorkFlowData[]; workflowList: WorkFlowData[];
@ -72,19 +70,19 @@ class Workflow extends Component<Props, State> {
return ( return (
<div style={{ height: '100%' }} className="workflow-wraper"> <div style={{ height: '100%' }} className="workflow-wraper">
<If condition={workflowList.length === 0}> <If condition={workflowList.length === 0}>
<div className="empty-container"> {/* <div className="empty-container">
<div className="empty-word"> <div className="empty-word">
<Translation>Please create workflow</Translation> <Translation>Please create workflow</Translation>
</div> </div>
<Button type="primary" onClick={this.addWrokflow}> <Button type="primary" onClick={this.addWrokflow}>
<Translation>New Workflow</Translation> <Translation>New Workflow</Translation>
</Button> </Button>
</div> </div> */}
</If> </If>
<If condition={workflowList.length > 0}> <If condition={workflowList.length > 0}>
<React.Fragment> <React.Fragment>
{workflowList.map((workflow: WorkFlowData) => ( {workflowList.map((workflow: WorkFlowData) => (
<WrokflowComponent <WorkflowComponent
key={workflow.name + params.appName + workflow.steps} key={workflow.name + params.appName + workflow.steps}
appName={params.appName} appName={params.appName}
data={workflow} data={workflow}

View File

@ -62,12 +62,11 @@ export type NodeItem = {
typeId: string; typeId: string;
}; };
class WorkFlowComponent extends Component<Props, State> { class WorkflowComponent extends Component<Props, State> {
field; field;
workflowItem: any; workflowItem: any;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
errorFocus: false, errorFocus: false,
loading: false, loading: false,
@ -115,33 +114,52 @@ class WorkFlowComponent extends Component<Props, State> {
} }
const { data } = this.props; const { data } = this.props;
const workflowData = this.workflowItem.getValues(); const workflowData = this.workflowItem.getValues();
const { nodes } = workflowData; const { nodes, edges } = workflowData;
if (nodes && Object.keys(nodes).length === 0) { if (edges && !edges.prev) {
Message.error('plase add workflow item'); let next = undefined;
Object.keys(edges).map((key) => {
if (edges[key].src == 'prev') {
next = edges[key];
}
});
if (!next) {
Message.warning('Please specify the first step');
return;
}
}
const reallyNodes: any = {};
Object.keys(nodes).map((nodeKey) => {
if (nodes[nodeKey].typeId !== 'next' && nodes[nodeKey].typeId !== 'prev') {
reallyNodes[nodeKey] = nodes[nodeKey];
}
});
if (reallyNodes && Object.keys(reallyNodes).length === 0) {
Message.warning('Please add at least one workflow step');
this.setState({ this.setState({
errorFocus: true, errorFocus: true,
}); });
return; return;
} }
const nodeArr: NodeItem[] = Object.values(nodes); const nodeArr: NodeItem[] = Object.values(reallyNodes);
const find = nodeArr.find((item) => !item.consumerData); const find = nodeArr.find((item) => !item.consumerData || item.consumerData.name == '');
if (find) { if (find) {
return Message.error('please enter workflow step name and type'); return Message.warning('Please set the workflow step name and type');
} }
this.setState({ loading: true }); this.setState({ loading: true });
const { name, alias, description } = values; const { name, alias, description } = values;
data.appName = data.appName || this.props.appName; data.appName = data.appName || this.props.appName;
data.name = name; data.name = name;
data.alias = alias; data.alias = alias;
data.description = description; data.description = description;
data.data = workflowData; data.data = Object.assign(workflowData, { nodes: reallyNodes });
this.props.dispatch({ this.props.dispatch({
type: 'workflow/saveWorkflow', type: 'workflow/saveWorkflow',
payload: data, payload: data,
callback: () => { callback: () => {
Message.success('save workflow success'); Message.success('Save the workflow success');
this.props.getWorkflow(); this.props.getWorkflow();
this.setState({ loading: false }); this.setState({ loading: false });
}, },
@ -170,14 +188,14 @@ class WorkFlowComponent extends Component<Props, State> {
<Translation>{option.default ? 'Cancel Default' : 'Set As Default'}</Translation> <Translation>{option.default ? 'Cancel Default' : 'Set As Default'}</Translation>
</Menu.Item> </Menu.Item>
</If> </If>
<Menu.Item onClick={() => this.deleteWorkflow(data.name)}> {/* <Menu.Item onClick={() => this.deleteWorkflow(data.name)}>
<Translation>Remove</Translation> <Translation>Remove</Translation>
</Menu.Item> </Menu.Item> */}
</Menu> </Menu>
); );
const { init } = this.field; const { init } = this.field;
const newData = _.cloneDeep(data); const newData = _.cloneDeep(data);
convertWorkflowStep(newData, data.appName, option.edit ? 250 : 32); convertWorkflowStep(newData, data.appName, 32, option.edit);
const workflowData = newData.data || { nodes: {}, edges: {} }; const workflowData = newData.data || { nodes: {}, edges: {} };
return ( return (
<Loading visible={loading} style={{ width: '100%' }}> <Loading visible={loading} style={{ width: '100%' }}>
@ -266,13 +284,15 @@ class WorkFlowComponent extends Component<Props, State> {
</a> </a>
</If> </If>
<div className="option-item"> <div className="option-item">
<Dropdown <If condition={option.edit}>
trigger={<Icon type="ellipsis" className="options-icon" />} <Dropdown
triggerType={['click']} trigger={<Icon type="ellipsis" className="options-icon" />}
className="workflow-more" triggerType={['click']}
> className="workflow-more"
{menu} >
</Dropdown> {menu}
</Dropdown>
</If>
</div> </div>
</div> </div>
</div> </div>
@ -292,4 +312,4 @@ class WorkFlowComponent extends Component<Props, State> {
} }
} }
export default WorkFlowComponent; export default WorkflowComponent;

View File

@ -1,8 +1,8 @@
.workflow-edge-container { .workflow-edge-container {
// height: 10px; width: 14px;
// background-color: green; height: 14px;
// width: 10px; color: #fff;
// line-height: 10px; line-height: 14px;
color: white;
text-align: center; text-align: center;
background-color: #1b58f4;
} }

View File

@ -1,10 +1,19 @@
import { Balloon } from '@b-design/ui';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Translation from '../../../components/Translation';
import './index.less'; import './index.less';
export interface EdgeData {
dest: string;
id: string;
src: string;
}
type Props = { type Props = {
id: string; id: string;
data?: any; data: EdgeData;
editMode?: boolean;
addNode: () => void;
}; };
type State = {}; type State = {};
@ -12,14 +21,27 @@ type State = {};
class WorkFlowEdge extends Component<Props, State> { class WorkFlowEdge extends Component<Props, State> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = {}; this.state = {};
} }
componentDidMount() {} componentDidMount() {}
render() { render() {
return <div className="workflow-edge-container">{/* + */}</div>; const { editMode } = this.props;
if (editMode) {
return (
<Balloon
trigger={
<div onClick={this.props.addNode} className="workflow-edge-container">
+
</div>
}
>
<Translation>Click to add Workflow Step</Translation>
</Balloon>
);
}
return <div />;
} }
} }

View File

@ -1,12 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { v4 as uuid } from 'uuid';
import { Message } from '@b-design/ui'; import { Message } from '@b-design/ui';
import type { import type { DiagramMakerData, DiagramMakerNode, DiagramMakerEdge } from 'diagram-maker';
DiagramMakerData,
DiagramMakerNode,
DiagramMakerEdge,
DiagramMakerPanel,
} from 'diagram-maker';
import { import {
DiagramMaker, DiagramMaker,
ConnectorPlacement, ConnectorPlacement,
@ -17,17 +13,15 @@ import {
NodeActions, NodeActions,
} from 'diagram-maker'; } from 'diagram-maker';
import WorkFlowNode from '../workflow-node'; import WorkFlowNode from '../workflow-node';
import type { EdgeData } from '../workflow-edge';
import WorkFlowEdge from '../workflow-edge'; import WorkFlowEdge from '../workflow-edge';
import WorkFlowPannel from '../workflow-panel';
import WorkFlowToolTip from '../workflow-tooltip';
import type { EdgesAndNodes, WorkFlowNodeType, WorkFlowEdgeType } from '../entity'; import type { EdgesAndNodes, WorkFlowNodeType, WorkFlowEdgeType } from '../entity';
import { WORKFLOW_COMMON_PANNEL } from '../entity';
import WorkflowForm from './workflow-form'; import WorkflowForm from './workflow-form';
import 'diagram-maker/dist/diagramMaker.css'; import 'diagram-maker/dist/diagramMaker.css';
import './index.less'; import './index.less';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import type { NodeItem } from '../workflow-component'; import type { NodeItem } from '../workflow-component';
import type { Definition } from '../../../interface/addon'; import type { WorkflowStep } from '../../../interface/application';
type WorkFlowItemProps = { type WorkFlowItemProps = {
workflowId: string; workflowId: string;
@ -57,9 +51,9 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
} }
const { data, edit } = this.props; const { data, edit } = this.props;
const containerWidth = this.container.clientWidth; const containerWidth = this.container.clientWidth;
const nodeWidth = Object.keys(data.nodes).length * 200 + 300; const nodeWidth = Object.keys(data.nodes).length * 360;
const basicePlatformConfig = { const basicPlatformConfig = {
panels: WORKFLOW_COMMON_PANNEL, panels: {},
workspace: { workspace: {
position: { position: {
x: 0, x: 0,
@ -82,20 +76,31 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
const initialData: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType> = Object.assign( const initialData: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType> = Object.assign(
{}, {},
data, data,
basicePlatformConfig, basicPlatformConfig,
); );
const { workFlowDefinitions } = this.props; const nodeTypeConfigs: any = {
const nodeTypeConfigs: any = {}; prev: {
workFlowDefinitions.map((definition: Definition) => {
nodeTypeConfigs[definition.name] = {
size: { size: {
width: 140, width: 200,
height: 40, height: 80,
},
visibleConnectorTypes: VisibleConnectorTypes.OUTPUT_ONLY,
},
next: {
size: {
width: 200,
height: 80,
},
visibleConnectorTypes: VisibleConnectorTypes.INPUT_ONLY,
},
common: {
size: {
width: 200,
height: 80,
}, },
definition: definition,
visibleConnectorTypes: VisibleConnectorTypes.BOTH, visibleConnectorTypes: VisibleConnectorTypes.BOTH,
}; },
}); };
this.diagramMaker = new DiagramMaker( this.diagramMaker = new DiagramMaker(
this.container, this.container,
{ {
@ -108,24 +113,34 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
ReactDOM.render( ReactDOM.render(
<WorkFlowNode <WorkFlowNode
id={node.id} id={node.id}
data={node.consumerData} data={node.consumerData as WorkflowStep}
selected={node.diagramMakerData.selected} selected={node.diagramMakerData.selected}
workflowId={this.props.workflowId} workflowId={this.props.workflowId}
showDetail={this.showStepDetail}
editMode={edit}
deleteNode={this.deleteNode}
typeId={node.typeId} typeId={node.typeId}
/>, />,
container, container,
); );
}, },
edge: (edge: DiagramMakerEdge<{}>, container: HTMLElement) => { edge: (edge: DiagramMakerEdge<{}>, container: HTMLElement) => {
ReactDOM.render(<WorkFlowEdge id={edge.id} data={edge.consumerData} />, container); ReactDOM.render(
<WorkFlowEdge
editMode={edit}
id={edge.id}
addNode={() => {
this.newNode(edge.src);
}}
data={edge as EdgeData}
/>,
container,
);
}, },
destroy: (container: HTMLElement) => { destroy: (container: HTMLElement) => {
ReactDOM.unmountComponentAtNode(container); ReactDOM.unmountComponentAtNode(container);
}, },
panels: { panels: {},
p1: this.renderLeftPannel,
p2: this.renderTopPannel,
},
}, },
nodeTypeConfig: nodeTypeConfigs, nodeTypeConfig: nodeTypeConfigs,
actionInterceptor: (action: any, dispatch, getState) => { actionInterceptor: (action: any, dispatch, getState) => {
@ -144,27 +159,34 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
} }
} }
} }
dispatch(action); if (action.type == NodeActions.NODE_DRAG) {
if (action.type == 'NODE_CREATE') { if (action.payload.workspaceRectangle.size.height > 400) {
this.setState({ this.diagramMaker.api.resetZoom();
currentSelectedNodeData: Object.assign(action.payload, { }
consumerData: { type: action.payload.typeId },
}),
visible: true,
});
} }
if (action.type == 'DELETE_ITEMS') {
if (Array.isArray(action.payload.edgeIds) && action.payload.edgeIds.length > 0) {
Message.warning(
'If remove the edge and save the workflow, the nodes that after this edge will be removed.',
);
}
}
dispatch(action);
}, },
}, },
{ {
initialData, initialData,
consumerRootReducer: (state: any, action: any) => { consumerRootReducer: (state: any, action: any) => {
switch (action.type) { switch (action.type) {
case 'UPDATENODE': case 'UPDATE_NODE':
const newNode: any = {}; const newNode: any = {};
newNode[action.payload.id] = action.payload; newNode[action.payload.id] = action.payload;
const newNodes = Object.assign({}, state.nodes, newNode); const newNodes = Object.assign({}, state.nodes, newNode);
const newState = Object.assign({}, state, { nodes: newNodes }); return Object.assign({}, state, { nodes: newNodes });
return newState; case 'UPDATE_NODES':
return Object.assign({}, state, { nodes: action.payload });
case 'UPDATE_EDGES':
return Object.assign({}, state, { edges: action.payload });
default: default:
return state; return state;
} }
@ -172,24 +194,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
eventListener: () => {}, eventListener: () => {},
}, },
); );
this.diagramMaker.store.subscribe(() => {
const { nodes } = this.diagramMaker.store.getState();
let currentNode: any = null;
Object.keys(nodes).forEach((key) => {
const obj = nodes[key];
if (obj && obj.diagramMakerData && obj.diagramMakerData.selected) {
currentNode = obj;
}
});
const { visible } = this.state;
if (currentNode && !visible) {
this.setState({
currentSelectedNodeData: currentNode,
visible: true,
});
}
});
} }
fit = () => { fit = () => {
@ -204,66 +208,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
}); });
}; };
undo = () => {
this.diagramMaker.api.undo();
};
redo = () => {
this.diagramMaker.api.redo();
};
zoomIn = () => {
this.diagramMaker.api.zoomIn();
};
zoomOut = () => {
this.diagramMaker.api.zoomOut();
};
renderLeftPannel = (
panel: DiagramMakerPanel,
state: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType>,
diagramMakerContainer: HTMLElement,
) => {
const { workFlowDefinitions, edit } = this.props;
const parentContainer = diagramMakerContainer.parentElement;
if (parentContainer) {
parentContainer.style.display = 'block';
}
if (!edit && parentContainer) {
parentContainer.style.display = 'none';
return;
}
ReactDOM.render(
<WorkFlowPannel
definitions={workFlowDefinitions}
id={panel.id}
workflowId={this.props.workflowId}
/>,
diagramMakerContainer,
);
};
renderTopPannel = (
panel: DiagramMakerPanel,
state: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType>,
diagramMakerContainer: HTMLElement,
) => {
ReactDOM.render(
<WorkFlowToolTip
id={panel.id}
edit={this.props.edit}
undo={this.undo}
redo={this.redo}
autoLayout={this.autoLayout}
zoomIn={this.zoomIn}
zoomOut={this.zoomOut}
fit={this.fit}
/>,
diagramMakerContainer,
);
};
componentWillReceiveProps = (nextProps: WorkFlowItemProps) => { componentWillReceiveProps = (nextProps: WorkFlowItemProps) => {
if (nextProps.edit !== this.props.edit) { if (nextProps.edit !== this.props.edit) {
if (nextProps.edit) { if (nextProps.edit) {
@ -278,6 +222,142 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
return { edges, nodes }; return { edges, nodes };
}; };
deleteNode = (id: string) => {
const { edges, nodes } = this.diagramMaker.store.getState();
const newNodes: any = {};
const deleteNode = nodes[id];
if (!deleteNode) {
return;
}
Object.keys(nodes).map((key: string) => {
if (key == id) {
return;
}
const step: NodeItem = nodes[key];
let stepNew: NodeItem = Object.assign({}, step);
if (stepNew.diagramMakerData.position.x >= deleteNode.diagramMakerData.position.x) {
stepNew = Object.assign(stepNew, {
diagramMakerData: {
selected: stepNew.diagramMakerData.selected,
size: stepNew.diagramMakerData.size,
position: {
x: stepNew.diagramMakerData.position.x - 320,
y: stepNew.diagramMakerData.position.y,
},
},
});
}
newNodes[stepNew.id] = stepNew;
});
const newEdges: any = {};
let preEdge: EdgeData = { id: '', dest: '', src: '' };
let dest = '';
Object.keys(edges).map((key: string) => {
const edge: EdgeData = edges[key];
if (edge.src == id) {
dest = edge.dest;
return;
}
if (edge.dest == id) {
preEdge = Object.assign({}, edge);
return;
}
newEdges[key] = edge;
});
preEdge.dest = dest;
newEdges[preEdge.id] = preEdge;
this.diagramMaker.store.dispatch({
type: 'UPDATE_NODES',
payload: newNodes,
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_EDGES',
payload: newEdges,
});
};
newNode = (source: string) => {
const { nodes, edges } = this.diagramMaker.store.getState();
const sourceNode = nodes[source];
let newNode: NodeItem = Object.assign({}, sourceNode);
newNode = Object.assign(newNode, {
consumerData: { name: '', properties: '', type: '' },
id: uuid(),
typeId: 'common',
diagramMakerData: {
selected: newNode.diagramMakerData.selected,
size: newNode.diagramMakerData.size,
position: {
x: newNode.diagramMakerData.position.x + 320,
y: newNode.diagramMakerData.position.y,
},
},
});
const newNodes: any = {};
Object.keys(nodes).map((key: string) => {
const step: NodeItem = nodes[key];
let stepNew: NodeItem = Object.assign({}, step);
if (stepNew.diagramMakerData.position.x >= newNode.diagramMakerData.position.x) {
stepNew = Object.assign(stepNew, {
diagramMakerData: {
selected: stepNew.diagramMakerData.selected,
size: stepNew.diagramMakerData.size,
position: {
x: stepNew.diagramMakerData.position.x + 320,
y: stepNew.diagramMakerData.position.y,
},
},
});
}
newNodes[stepNew.id] = stepNew;
});
newNodes[newNode.id] = newNode;
const newEdges: any = {};
Object.keys(edges).map((key: string) => {
const edge: EdgeData = edges[key];
if (edge.src == source) {
const pre = Object.assign({}, edge);
const next = Object.assign({}, edge);
pre.dest = newNode.id;
pre.id = pre.src;
next.src = newNode.id;
next.id = next.src;
newEdges[pre.id] = pre;
newEdges[next.id] = next;
return;
}
newEdges[key] = edge;
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_NODES',
payload: newNodes,
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_EDGES',
payload: newEdges,
});
this.setState({
currentSelectedNodeData: newNode,
visible: true,
});
};
showStepDetail = (id: string) => {
const { edit } = this.props;
if (!edit) {
return;
}
const { nodes } = this.diagramMaker.store.getState();
const consumerData: NodeItem = nodes[id];
this.setState({
currentSelectedNodeData: consumerData,
visible: true,
});
};
openDrawer = () => { openDrawer = () => {
this.setState({ this.setState({
visible: true, visible: true,
@ -296,20 +376,24 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
createOrUpdateNode = (values: any) => { createOrUpdateNode = (values: any) => {
const { nodes } = this.diagramMaker.store.getState(); const { nodes } = this.diagramMaker.store.getState();
const { currentSelectedNodeData } = this.state; const { currentSelectedNodeData } = this.state;
let consumerData: NodeItem = nodes[currentSelectedNodeData.id]; if (currentSelectedNodeData) {
consumerData = Object.assign({}, consumerData, { let consumerData: NodeItem = nodes[currentSelectedNodeData.id];
consumerData: values, if (consumerData) {
diagramMakerData: { consumerData = Object.assign({}, consumerData, {
selected: false, consumerData: values,
size: consumerData.diagramMakerData.size, diagramMakerData: {
position: consumerData.diagramMakerData.position, selected: false,
}, size: consumerData.diagramMakerData.size,
}); position: consumerData.diagramMakerData.position,
this.diagramMaker.store.dispatch({ },
type: 'UPDATENODE', });
payload: consumerData, this.diagramMaker.store.dispatch({
}); type: 'UPDATE_NODE',
this.closeDrawer(); payload: consumerData,
});
this.closeDrawer();
}
}
}; };
onDelete = (values: NodeItem) => { onDelete = (values: NodeItem) => {
@ -335,7 +419,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
/> />
<If condition={visible}> <If condition={visible}>
<WorkflowForm <WorkflowForm
onDelete={() => this.onDelete(currentSelectedNodeData)}
createOrUpdateNode={this.createOrUpdateNode} createOrUpdateNode={this.createOrUpdateNode}
data={currentSelectedNodeData} data={currentSelectedNodeData}
checkStepName={(name: string) => { checkStepName={(name: string) => {

View File

@ -21,13 +21,12 @@ import locale from '../../../utils/locale';
type Props = { type Props = {
createOrUpdateNode: (data: any) => void; createOrUpdateNode: (data: any) => void;
data: DiagramMakerNode<WorkFlowNodeType>; data?: DiagramMakerNode<WorkFlowNodeType>;
workFlowDefinitions: []; workFlowDefinitions: [];
appName?: string; appName?: string;
componentName?: string; componentName?: string;
closeDrawer: () => void; closeDrawer: () => void;
checkStepName: (name: string) => boolean; checkStepName: (name: string) => boolean;
onDelete: () => void;
dispatch?: ({}) => {}; dispatch?: ({}) => {};
t: (key: string) => {}; t: (key: string) => {};
}; };
@ -56,14 +55,16 @@ class WorkflowForm extends Component<Props, State> {
} }
componentDidMount = () => { componentDidMount = () => {
const { consumerData } = this.props.data; if (this.props.data) {
this.field.setValues(consumerData || ''); const { consumerData } = this.props.data;
let properties = consumerData && consumerData.properties; this.field.setValues(consumerData || '');
if (properties && typeof properties === 'string') { let properties = consumerData && consumerData.properties;
properties = JSON.parse(properties); if (properties && typeof properties === 'string') {
properties = JSON.parse(properties);
}
this.field.setValues({ properties: properties });
this.onDetailsComponeDefinition((consumerData && consumerData.type) || '');
} }
this.field.setValues({ properties: properties });
this.onDetailsComponeDefinition((consumerData && consumerData.type) || '');
}; };
setValues = (values: any | null) => { setValues = (values: any | null) => {
@ -83,13 +84,6 @@ class WorkflowForm extends Component<Props, State> {
}); });
}; };
onDelete = () => {
const { onDelete } = this.props;
if (onDelete) {
onDelete();
}
};
transDefinitions() { transDefinitions() {
const { workFlowDefinitions } = this.props; const { workFlowDefinitions } = this.props;
return (workFlowDefinitions || []).map((item: { name: string }) => ({ return (workFlowDefinitions || []).map((item: { name: string }) => ({
@ -129,20 +123,22 @@ class WorkflowForm extends Component<Props, State> {
}; };
const checkWorkflowStepName = (rule: Rule, value: any, callback: (error?: string) => void) => { const checkWorkflowStepName = (rule: Rule, value: any, callback: (error?: string) => void) => {
const { consumerData } = this.props.data; if (data) {
if (checkStepName(value)) { const { consumerData } = data;
if (consumerData?.name && value == consumerData?.name) { if (checkStepName(value)) {
callback(); if (consumerData?.name && value == consumerData?.name) {
return; callback();
return;
}
callback('name is exist');
} }
callback('name is exist');
} }
callback(); callback();
}; };
const edit = data && data.consumerData?.name != undefined && data.consumerData?.name != '';
return ( return (
<DrawerWithFooter <DrawerWithFooter
title={<Translation>Edit Workflow Step</Translation>} title={<Translation>{edit ? 'Edit Workflow Step' : 'Add Workflow Step'}</Translation>}
placement="right" placement="right"
width={800} width={800}
onClose={closeDrawer} onClose={closeDrawer}
@ -151,26 +147,12 @@ class WorkflowForm extends Component<Props, State> {
<Button key={'cancel'} style={{ marginRight: '16px' }} onClick={closeDrawer}> <Button key={'cancel'} style={{ marginRight: '16px' }} onClick={closeDrawer}>
Cancel Cancel
</Button>, </Button>,
<Button
key={'delete'}
style={{ marginRight: '16px' }}
onClick={() => {
this.onDelete();
}}
title="Delete this step"
>
Delete
</Button>,
]} ]}
> >
<Form field={this.field}> <Form field={this.field}>
<Row> <Row>
<Col span={24} style={{ padding: '0 8px' }}> <Col span={24} style={{ padding: '0 8px' }}>
<FormItem <FormItem label={<Translation>Workflow Type</Translation>} required disabled={edit}>
label={<Translation>Workflow Type</Translation>}
required
disabled={this.field.getValue('type')}
>
<Select <Select
locale={locale.Select} locale={locale.Select}
className="select" className="select"
@ -202,7 +184,7 @@ class WorkflowForm extends Component<Props, State> {
maxLength={32} maxLength={32}
placeholder={t('Please enter').toString()} placeholder={t('Please enter').toString()}
{...init('name', { {...init('name', {
initValue: data.consumerData?.type, initValue: data && data.consumerData?.type,
rules: [ rules: [
{ {
required: true, required: true,

View File

@ -1,63 +1,27 @@
@primarycolor: #1b58f4; @primarycolor: #1b58f4;
.workflow-node-container { .workflow-node-container {
width: auto; width: 200px;
height: 40px; height: 80px;
padding: 16px;
color: black; color: black;
line-height: 40px;
white-space: nowrap; white-space: nowrap;
text-align: center; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 12px; border-radius: 12px;
cursor: pointer;
&.active { &.active {
border: 1px solid @primarycolor; border: 1px solid @primarycolor;
} }
} .workflow-step-type {
padding-top: 8px;
.workflow-switch-node-container { color: #a6a6a6;
position: relative; }
width: 80px; .workflow-step-delete {
height: 80px; position: absolute;
border: none; top: 8px;
&.active { right: 8px;
border: none; :hover {
.rhombus-container { color: @primarycolor;
border: 1px solid @primarycolor;
} }
} }
.rhombus-container {
position: absolute;
top: 11px;
left: 11px;
width: 57px;
height: 57px;
border: 1px solid #ccc;
transform: rotate(45deg);
}
.content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.start-connector {
position: absolute;
top: 31px;
left: -4px;
width: 14px;
height: 14px;
background-color: blue;
border-radius: 50%;
}
.end-connector {
position: absolute;
top: 31px;
right: -4px;
width: 14px;
height: 14px;
background-color: blue;
border-radius: 50%;
}
} }

View File

@ -1,15 +1,20 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { DiagramMakerComponents } from 'diagram-maker'; import { DiagramMakerComponents } from 'diagram-maker';
import { Icon } from '@b-design/ui';
import './index.less'; import './index.less';
import type { WorkflowStep } from '../../../interface/application';
import { Balloon, Icon } from '@b-design/ui';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import Translation from '../../../components/Translation'; import Translation from '../../../components/Translation';
type Props = { type Props = {
id: string; id: string;
typeId?: string; typeId?: string;
workflowId: string; workflowId: string;
selected?: boolean; selected?: boolean;
data?: any; showDetail: (id: string) => void;
deleteNode: (id: string) => void;
data?: WorkflowStep;
editMode?: boolean;
}; };
type State = {}; type State = {};
@ -17,69 +22,66 @@ type State = {};
class WorkFlowNode extends Component<Props, State> { class WorkFlowNode extends Component<Props, State> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = {}; this.state = {};
} }
componentDidMount() {} componentDidMount() {}
render() { render() {
const { data = {}, selected, id, workflowId, typeId } = this.props; const { data, selected, id, workflowId, editMode, typeId } = this.props;
const showName =
data && data.name ? (
data.alias || data.name
) : (
<Translation>Click to config Workflow Step</Translation>
);
if (typeId == 'prev' || typeId == 'next') {
return <React.Fragment />;
}
return ( return (
<React.Fragment> <React.Fragment>
<If condition={typeId !== 'switch'}> <div
<div className={selected ? 'workflow-node-container active' : 'workflow-node-container'}
className={selected ? 'workflow-node-container active' : 'workflow-node-container'} id={id}
id={id} style={{
workflow-id={workflowId} cursor: editMode ? 'pointer' : 'auto',
> }}
{data.alias || data.name || <Translation>Click Edit</Translation>} onClick={() => this.props.showDetail(id)}
<div workflow-id={workflowId}
data-event-target="true" >
data-dropzone="true" <If condition={editMode}>
data-type={DiagramMakerComponents.NODE_CONNECTOR} <div className="workflow-step-delete">
data-id={id} <Balloon
/> trigger={
<div <Icon
data-event-target="true" size={14}
data-draggable="true" onClick={(event: any) => {
data-type={DiagramMakerComponents.NODE_CONNECTOR} event.stopPropagation();
data-id={id} this.props.deleteNode(id);
/> }}
</div> type="delete"
</If> />
<If condition={typeId === 'switch'}> }
<div >
className={ <Translation>Click to remove Workflow Step</Translation>
selected </Balloon>
? 'workflow-node-container workflow-switch-node-container active'
: 'workflow-node-container workflow-switch-node-container'
}
id={id}
workflow-id={workflowId}
title={data.text}
>
<div className="rhombus-container" />
<div className="content">
{data.text || <Translation>Click Edit</Translation>}
<Icon type="ashbin1" />
</div> </div>
<div </If>
className="start-connector" {showName}
data-event-target="true" <div className="workflow-step-type">{data?.type}</div>
data-dropzone="true" <div
data-type={DiagramMakerComponents.NODE_CONNECTOR} data-event-target="true"
data-id={id} data-dropzone="false"
/> data-type={DiagramMakerComponents.NODE_CONNECTOR}
<div data-id={id}
className="end-connector" />
data-event-target="true" <div
data-draggable="true" data-event-target="true"
data-type={DiagramMakerComponents.NODE_CONNECTOR} data-draggable="false"
data-id={id} data-type={DiagramMakerComponents.NODE_CONNECTOR}
/> data-id={id}
</div> />
</If> </div>
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -1993,6 +1993,11 @@
resolved "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Funist%2Fdownload%2F%40types%2Funist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" resolved "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Funist%2Fdownload%2F%40types%2Funist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha1-JQp7FsO5H2cqJFUuxkZ47rHToI0= integrity sha1-JQp7FsO5H2cqJFUuxkZ47rHToI0=
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/webpack-sources@*": "@types/webpack-sources@*":
version "3.2.0" version "3.2.0"
resolved "https://registry.nlark.com/@types/webpack-sources/download/@types/webpack-sources-3.2.0.tgz?cache=0&sync_timestamp=1629709718286&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fwebpack-sources%2Fdownload%2F%40types%2Fwebpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" resolved "https://registry.nlark.com/@types/webpack-sources/download/@types/webpack-sources-3.2.0.tgz?cache=0&sync_timestamp=1629709718286&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fwebpack-sources%2Fdownload%2F%40types%2Fwebpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b"
@ -12210,6 +12215,11 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.npmmirror.com/uuid/download/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" resolved "https://registry.npmmirror.com/uuid/download/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4= integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uvu@^0.5.0: uvu@^0.5.0:
version "0.5.2" version "0.5.2"
resolved "https://registry.npmmirror.com/uvu/download/uvu-0.5.2.tgz#c145e7f4b5becf80099cf22fd8a4a05f0112b2c0" resolved "https://registry.npmmirror.com/uvu/download/uvu-0.5.2.tgz#c145e7f4b5becf80099cf22fd8a4a05f0112b2c0"