150 lines
3.7 KiB
TypeScript
150 lines
3.7 KiB
TypeScript
|
import React, { useContext } from 'react';
|
||
|
import { Dropdown, Menu, Button } from 'antd';
|
||
|
import { PlusOutlined } from '@ant-design/icons';
|
||
|
import { cx } from '@emotion/css';
|
||
|
import { addButtonClass, branchBlockClass, branchClass, nodeBlockClass, nodeCardClass, nodeHeaderClass, nodeTitleClass } from './style';
|
||
|
|
||
|
import {
|
||
|
useCollection,
|
||
|
useResourceActionContext
|
||
|
} from '..';
|
||
|
import { Instruction, instructions, Node } from './nodes';
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
function makeNodes(nodes): void {
|
||
|
const nodesMap = new Map();
|
||
|
nodes.forEach(item => nodesMap.set(item.id, item));
|
||
|
for (let node of nodesMap.values()) {
|
||
|
if (node.upstreamId) {
|
||
|
node.upstream = nodesMap.get(node.upstreamId);
|
||
|
}
|
||
|
|
||
|
if (node.downstreamId) {
|
||
|
node.downstream = nodesMap.get(node.downstreamId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const FlowContext = React.createContext(null);
|
||
|
|
||
|
export function useFlowContext() {
|
||
|
return useContext(FlowContext);
|
||
|
}
|
||
|
|
||
|
export function WorkflowCanvas() {
|
||
|
const { data, refresh, loading } = useResourceActionContext();
|
||
|
|
||
|
if (!data?.data && !loading) {
|
||
|
return <div>加载失败</div>;
|
||
|
}
|
||
|
|
||
|
const { nodes = [] } = data?.data ?? {};
|
||
|
|
||
|
makeNodes(nodes);
|
||
|
|
||
|
const entry = nodes.find(item => !item.upstream);
|
||
|
|
||
|
return (
|
||
|
<FlowContext.Provider value={{
|
||
|
nodes,
|
||
|
onNodeAdded: refresh,
|
||
|
onNodeRemoved: refresh
|
||
|
}}>
|
||
|
<div className={branchBlockClass}>
|
||
|
<Branch entry={entry} />
|
||
|
</div>
|
||
|
<div className={cx(nodeCardClass)}>结束</div>
|
||
|
</FlowContext.Provider>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
export function Branch({
|
||
|
from = null,
|
||
|
entry = null,
|
||
|
branchIndex = null,
|
||
|
controller = null
|
||
|
}) {
|
||
|
const list = [];
|
||
|
for (let node = entry; node; node = node.downstream) {
|
||
|
list.push(node);
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div className={cx(branchClass)}>
|
||
|
<div className="workflow-branch-lines" />
|
||
|
{controller}
|
||
|
<AddButton upstream={from} branchIndex={branchIndex} />
|
||
|
<div className="workflow-node-list">
|
||
|
{list.map(item => {
|
||
|
return (
|
||
|
<div key={item.id} className={cx(nodeBlockClass)}>
|
||
|
<Node data={item} />
|
||
|
<AddButton upstream={item} />
|
||
|
</div>
|
||
|
);
|
||
|
})}
|
||
|
</div>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// TODO(bug): useless observable
|
||
|
// const instructionsList = observable(Array.from(instructions.getValues()));
|
||
|
|
||
|
interface AddButtonProps {
|
||
|
upstream;
|
||
|
branchIndex?: number;
|
||
|
};
|
||
|
|
||
|
function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||
|
const { resource } = useCollection();
|
||
|
const { data } = useResourceActionContext();
|
||
|
const { onNodeAdded } = useFlowContext();
|
||
|
|
||
|
async function onCreate({ keyPath }) {
|
||
|
const type = keyPath.pop();
|
||
|
const config = {};
|
||
|
const [optionKey] = keyPath;
|
||
|
if (optionKey) {
|
||
|
const { value } = instructions.get(type).options.find(item => item.key === optionKey);
|
||
|
Object.assign(config, value);
|
||
|
}
|
||
|
|
||
|
const { data: { data: node } } = await resource.create({
|
||
|
values: {
|
||
|
type,
|
||
|
workflowId: data.data.id,
|
||
|
upstreamId: upstream?.id ?? null,
|
||
|
branchIndex,
|
||
|
config
|
||
|
}
|
||
|
});
|
||
|
|
||
|
onNodeAdded(node);
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div className={cx(addButtonClass)}>
|
||
|
<Dropdown trigger={['click']} overlay={
|
||
|
<Menu onClick={ev => onCreate(ev)}>
|
||
|
{(Array.from(instructions.getValues()) as Instruction[]).map(item => item.options
|
||
|
? (
|
||
|
<Menu.SubMenu key={item.type} title={item.title}>
|
||
|
{item.options.map(option => (
|
||
|
<Menu.Item key={option.key}>{option.label}</Menu.Item>
|
||
|
))}
|
||
|
</Menu.SubMenu>
|
||
|
)
|
||
|
: (
|
||
|
<Menu.Item key={item.type}>{item.title}</Menu.Item>
|
||
|
))}
|
||
|
</Menu>
|
||
|
}>
|
||
|
<Button shape="circle" icon={<PlusOutlined />} />
|
||
|
</Dropdown>
|
||
|
</div>
|
||
|
);
|
||
|
};
|