增加了动态生成跟节点验证。
This commit is contained in:
parent
6e45e3b502
commit
80de1149d3
@ -0,0 +1,69 @@
|
|||||||
|
import { NodeConfig } from '../types';
|
||||||
|
|
||||||
|
export const NODE_CONFIG: Record<string, NodeConfig> = {
|
||||||
|
START: {
|
||||||
|
size: { width: 80, height: 80 },
|
||||||
|
shape: 'circle',
|
||||||
|
theme: {
|
||||||
|
fill: '#f6ffed',
|
||||||
|
stroke: '#52c41a'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
END: {
|
||||||
|
size: { width: 80, height: 80 },
|
||||||
|
shape: 'circle',
|
||||||
|
theme: {
|
||||||
|
fill: '#fff1f0',
|
||||||
|
stroke: '#ff4d4f'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SHELL: {
|
||||||
|
size: { width: 200, height: 80 },
|
||||||
|
shape: 'rect',
|
||||||
|
theme: {
|
||||||
|
fill: '#e6f7ff',
|
||||||
|
stroke: '#1890ff'
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
rx: 4,
|
||||||
|
ry: 4,
|
||||||
|
icon: {
|
||||||
|
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTYwIDI1NnY1MTJoNzA0VjI1NkgxNjB6IG02NDAgNDQ4SDE5MlYyODhoNjA4djQxNnpNMjI0IDQ4MGwxMjgtMTI4IDQ1LjMgNDUuMy04Mi43IDgyLjcgODIuNyA4Mi43TDM1MiA2MDhsLTEyOC0xMjh6IG0yMzEuMSAxOTJsLTQ1LjMtNDUuMyAxNTItMTUyIDQ1LjMgNDUuM2wtMTUyIDE1MnoiIGZpbGw9IiMxODkwZmYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
x: 16,
|
||||||
|
y: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TIMER: {
|
||||||
|
size: { width: 200, height: 80 },
|
||||||
|
shape: 'rect',
|
||||||
|
theme: {
|
||||||
|
fill: '#fff7e6',
|
||||||
|
stroke: '#fa8c16'
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
rx: 4,
|
||||||
|
ry: 4,
|
||||||
|
icon: {
|
||||||
|
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NjE2NTM2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjUzNTgiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6IG0wIDgyMGMtMjA1LjQgMC0zNzItMTY2LjYtMzcyLTM3MnMxNjYuNi0zNzIgMzcyLTM3MiAzNzIgMTY2LjYgMzcyIDM3Mi0xNjYuNiAzNzItMzcyIDM3MnogbS00NC44LTM3Mi4zaDIzNS45djUzLjhIMzk4LjdWMjk3LjZoNjguNXYyMTQuMXoiIGZpbGw9IiNmYThjMTYiIHAtaWQ9IjUzNTkiPjwvcGF0aD48L3N2Zz4=',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
x: 16,
|
||||||
|
y: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GATEWAY: {
|
||||||
|
size: { width: 80, height: 80 },
|
||||||
|
shape: 'polygon',
|
||||||
|
theme: {
|
||||||
|
fill: '#f9f0ff',
|
||||||
|
stroke: '#722ed1'
|
||||||
|
},
|
||||||
|
extras: {
|
||||||
|
refPoints: '0,10 10,0 20,10 10,20'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -21,6 +21,10 @@ import {NodeType, getNodeTypes} from './service';
|
|||||||
import {DeleteOutlined, CopyOutlined, SettingOutlined, ClearOutlined, FullscreenOutlined} from '@ant-design/icons';
|
import {DeleteOutlined, CopyOutlined, SettingOutlined, ClearOutlined, FullscreenOutlined} from '@ant-design/icons';
|
||||||
import EdgeConfig from './components/EdgeConfig';
|
import EdgeConfig from './components/EdgeConfig';
|
||||||
import { validateFlow, hasCycle } from './validate';
|
import { validateFlow, hasCycle } from './validate';
|
||||||
|
import { generateNodeStyle, generatePorts, calculateNodePosition, calculateCanvasPosition } from './utils/nodeUtils';
|
||||||
|
import { Position } from './types';
|
||||||
|
import { NODE_CONFIG } from './configs/nodeConfig';
|
||||||
|
import { isWorkflowError } from './utils/errors';
|
||||||
|
|
||||||
const {Sider, Content} = Layout;
|
const {Sider, Content} = Layout;
|
||||||
|
|
||||||
@ -507,134 +511,37 @@ const FlowDesigner: React.FC = () => {
|
|||||||
const handleDrop = (e: DragEvent) => {
|
const handleDrop = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const nodeType = draggedNodeRef.current;
|
const nodeType = draggedNodeRef.current;
|
||||||
if (!nodeType || !graphRef.current || !containerRef.current) return;
|
if (!nodeType || !graphRef.current || !containerRef.current) {
|
||||||
|
message.error('无效的节点类型或画布未初始化');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取画布相对位置
|
try {
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const point = {
|
|
||||||
x: e.clientX - rect.left,
|
|
||||||
y: e.clientY - rect.top,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取画布缩放和平移信息
|
|
||||||
const matrix = graphRef.current.matrix();
|
const matrix = graphRef.current.matrix();
|
||||||
const scale = matrix.a;
|
|
||||||
const offsetX = matrix.e;
|
|
||||||
const offsetY = matrix.f;
|
|
||||||
|
|
||||||
// 计算实际位置(考虑缩放和平移)
|
// 使用新的工具函数计算画布位置
|
||||||
const position = {
|
const dropPosition = calculateCanvasPosition(
|
||||||
x: (point.x - offsetX) / scale,
|
e.clientX,
|
||||||
y: (point.y - offsetY) / scale,
|
e.clientY,
|
||||||
};
|
rect,
|
||||||
|
{
|
||||||
|
scale: matrix.a,
|
||||||
|
offsetX: matrix.e,
|
||||||
|
offsetY: matrix.f,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建节点配置
|
||||||
|
const position = calculateNodePosition(nodeType.code, dropPosition);
|
||||||
|
const nodeStyle = generateNodeStyle(nodeType.code);
|
||||||
|
const ports = generatePorts(nodeType.code);
|
||||||
|
|
||||||
// 创建节点
|
// 创建节点
|
||||||
const node = graphRef.current.addNode({
|
const node = graphRef.current.addNode({
|
||||||
x: position.x - (nodeType.code === 'SHELL' ? 100 : 40), // Shell节点宽度的一半,其他节点40px
|
...position,
|
||||||
y: position.y - 40,
|
...nodeStyle,
|
||||||
width: nodeType.code === 'SHELL' ? 200 : 80,
|
ports,
|
||||||
height: 80,
|
|
||||||
shape: nodeType.code === 'GATEWAY' ? 'polygon' : (nodeType.code === 'SHELL' ? 'rect' : 'circle'),
|
|
||||||
attrs: {
|
|
||||||
body: {
|
|
||||||
fill: nodeType.code === 'START' ? '#f6ffed' :
|
|
||||||
nodeType.code === 'END' ? '#fff1f0' :
|
|
||||||
nodeType.code === 'SHELL' ? '#e6f7ff' : '#f9f0ff',
|
|
||||||
stroke: nodeType.code === 'START' ? '#52c41a' :
|
|
||||||
nodeType.code === 'END' ? '#ff4d4f' :
|
|
||||||
nodeType.code === 'SHELL' ? '#1890ff' : '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
...(nodeType.code === 'SHELL' ? { rx: 4, ry: 4 } : {}),
|
|
||||||
...(nodeType.code === 'GATEWAY' ? { refPoints: '0,10 10,0 20,10 10,20' } : {})
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
text: nodeType.name,
|
|
||||||
fill: '#000000',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: 500,
|
|
||||||
...(nodeType.code === 'SHELL' ? {
|
|
||||||
refX: 0.5, // 水平居中
|
|
||||||
refY: 0.5, // 垂直居中
|
|
||||||
textAnchor: 'middle', // 文本水平居中
|
|
||||||
textVerticalAnchor: 'middle' // 文本垂直居中
|
|
||||||
} : {})
|
|
||||||
},
|
|
||||||
...(nodeType.code === 'SHELL' ? {
|
|
||||||
image: {
|
|
||||||
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTYwIDI1NnY1MTJoNzA0VjI1NkgxNjB6IG02NDAgNDQ4SDE5MlYyODhoNjA4djQxNnpNMjI0IDQ4MGwxMjgtMTI4IDQ1LjMgNDUuMy04Mi43IDgyLjcgODIuNyA4Mi43TDM1MiA2MDhsLTEyOC0xMjh6IG0yMzEuMSAxOTJsLTQ1LjMtNDUuMyAxNTItMTUyIDQ1LjMgNDUuM2wtMTUyIDE1MnoiIGZpbGw9IiMxODkwZmYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
x: 16,
|
|
||||||
y: 24
|
|
||||||
}
|
|
||||||
} : {})
|
|
||||||
},
|
|
||||||
ports: {
|
|
||||||
groups: {
|
|
||||||
top: {
|
|
||||||
position: 'top',
|
|
||||||
attrs: {
|
|
||||||
circle: {
|
|
||||||
r: 4,
|
|
||||||
magnet: true,
|
|
||||||
stroke: nodeType.code === 'START' ? '#52c41a' :
|
|
||||||
nodeType.code === 'END' ? '#ff4d4f' :
|
|
||||||
nodeType.code === 'SHELL' ? '#1890ff' : '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
fill: '#fff'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
position: 'right',
|
|
||||||
attrs: {
|
|
||||||
circle: {
|
|
||||||
r: 4,
|
|
||||||
magnet: true,
|
|
||||||
stroke: nodeType.code === 'START' ? '#52c41a' :
|
|
||||||
nodeType.code === 'END' ? '#ff4d4f' :
|
|
||||||
nodeType.code === 'SHELL' ? '#1890ff' : '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
fill: '#fff'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bottom: {
|
|
||||||
position: 'bottom',
|
|
||||||
attrs: {
|
|
||||||
circle: {
|
|
||||||
r: 4,
|
|
||||||
magnet: true,
|
|
||||||
stroke: nodeType.code === 'START' ? '#52c41a' :
|
|
||||||
nodeType.code === 'END' ? '#ff4d4f' :
|
|
||||||
nodeType.code === 'SHELL' ? '#1890ff' : '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
fill: '#fff'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
left: {
|
|
||||||
position: 'left',
|
|
||||||
attrs: {
|
|
||||||
circle: {
|
|
||||||
r: 4,
|
|
||||||
magnet: true,
|
|
||||||
stroke: nodeType.code === 'START' ? '#52c41a' :
|
|
||||||
nodeType.code === 'END' ? '#ff4d4f' :
|
|
||||||
nodeType.code === 'SHELL' ? '#1890ff' : '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
fill: '#fff'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{ group: 'top' },
|
|
||||||
{ group: 'right' },
|
|
||||||
{ group: 'bottom' },
|
|
||||||
{ group: 'left' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
data: {
|
data: {
|
||||||
type: nodeType.code,
|
type: nodeType.code,
|
||||||
name: nodeType.name,
|
name: nodeType.name,
|
||||||
@ -642,17 +549,29 @@ const FlowDesigner: React.FC = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 选中新创建的节点
|
// 选中新创建的节点并打开配置
|
||||||
graphRef.current.cleanSelection();
|
graphRef.current.cleanSelection();
|
||||||
graphRef.current.select(node);
|
graphRef.current.select(node);
|
||||||
|
|
||||||
// 打开配置抽屉
|
|
||||||
setCurrentNode(node);
|
setCurrentNode(node);
|
||||||
setCurrentNodeType(nodeType);
|
setCurrentNodeType(nodeType);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({ name: nodeType.name });
|
||||||
name: nodeType.name,
|
|
||||||
});
|
|
||||||
setConfigVisible(true);
|
setConfigVisible(true);
|
||||||
|
|
||||||
|
message.success('节点创建成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating node:', error);
|
||||||
|
|
||||||
|
if (isWorkflowError(error)) {
|
||||||
|
message.error(`创建节点失败:${error.message}`);
|
||||||
|
} else {
|
||||||
|
message.error('创建节点失败:未知错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理状态
|
||||||
|
setCurrentNode(undefined);
|
||||||
|
setCurrentNodeType(undefined);
|
||||||
|
setConfigVisible(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证Shell节点配置
|
// 验证Shell节点配置
|
||||||
|
|||||||
28
frontend/src/pages/Workflow/Definition/Designer/types.ts
Normal file
28
frontend/src/pages/Workflow/Definition/Designer/types.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export interface Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeConfig {
|
||||||
|
size: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
shape: 'circle' | 'rect' | 'polygon';
|
||||||
|
theme: {
|
||||||
|
fill: string;
|
||||||
|
stroke: string;
|
||||||
|
};
|
||||||
|
extras?: {
|
||||||
|
rx?: number;
|
||||||
|
ry?: number;
|
||||||
|
icon?: {
|
||||||
|
'xlink:href': string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
refPoints?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
export class WorkflowError extends Error {
|
||||||
|
constructor(message: string, public code: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'WorkflowError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeConfigError extends WorkflowError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 'NODE_CONFIG_ERROR');
|
||||||
|
this.name = 'NodeConfigError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeCreationError extends WorkflowError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, 'NODE_CREATION_ERROR');
|
||||||
|
this.name = 'NodeCreationError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isWorkflowError = (error: unknown): error is WorkflowError => {
|
||||||
|
return error instanceof WorkflowError;
|
||||||
|
};
|
||||||
@ -0,0 +1,174 @@
|
|||||||
|
import { Position, NodeConfig } from '../types';
|
||||||
|
import { NODE_CONFIG } from '../configs/nodeConfig';
|
||||||
|
import { NodeConfigError } from './errors';
|
||||||
|
|
||||||
|
interface CanvasMatrix {
|
||||||
|
scale: number;
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const calculateCanvasPosition = (
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
containerRect: DOMRect,
|
||||||
|
matrix: CanvasMatrix
|
||||||
|
): Position => {
|
||||||
|
const point: Position = {
|
||||||
|
x: clientX - containerRect.left,
|
||||||
|
y: clientY - containerRect.top,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: (point.x - matrix.offsetX) / matrix.scale,
|
||||||
|
y: (point.y - matrix.offsetY) / matrix.scale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNodeConfig = (nodeType: string): NodeConfig => {
|
||||||
|
const config = NODE_CONFIG[nodeType];
|
||||||
|
if (!config) {
|
||||||
|
throw new NodeConfigError(`No configuration found for node type: ${nodeType}`);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface NodeStyle {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
shape: 'circle' | 'rect' | 'polygon';
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
fill: string;
|
||||||
|
stroke: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
rx?: number;
|
||||||
|
ry?: number;
|
||||||
|
refPoints?: string;
|
||||||
|
};
|
||||||
|
label: {
|
||||||
|
text: string;
|
||||||
|
fill: string;
|
||||||
|
fontSize: number;
|
||||||
|
fontWeight: number;
|
||||||
|
refX?: number;
|
||||||
|
refY?: number;
|
||||||
|
textAnchor?: string;
|
||||||
|
textVerticalAnchor?: string;
|
||||||
|
};
|
||||||
|
image?: {
|
||||||
|
'xlink:href': string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateNodeStyle = (nodeType: string): NodeStyle => {
|
||||||
|
try {
|
||||||
|
const config = getNodeConfig(nodeType);
|
||||||
|
return {
|
||||||
|
width: config.size.width,
|
||||||
|
height: config.size.height,
|
||||||
|
shape: config.shape,
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
fill: config.theme.fill,
|
||||||
|
stroke: config.theme.stroke,
|
||||||
|
strokeWidth: 2,
|
||||||
|
...(config.extras || {})
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: nodeType,
|
||||||
|
fill: '#000000',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
...(nodeType === 'SHELL' ? {
|
||||||
|
refX: 0.5,
|
||||||
|
refY: 0.5,
|
||||||
|
textAnchor: 'middle',
|
||||||
|
textVerticalAnchor: 'middle'
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
...(nodeType === 'SHELL' && config.extras?.icon ? {
|
||||||
|
image: config.extras.icon
|
||||||
|
} : {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NodeConfigError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new NodeConfigError(`Failed to generate node style for type: ${nodeType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PortConfig {
|
||||||
|
groups: {
|
||||||
|
[key: string]: {
|
||||||
|
position: string;
|
||||||
|
attrs: {
|
||||||
|
circle: {
|
||||||
|
r: number;
|
||||||
|
magnet: boolean;
|
||||||
|
stroke: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
fill: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
items: Array<{ group: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generatePorts = (nodeType: string): PortConfig => {
|
||||||
|
try {
|
||||||
|
const config = getNodeConfig(nodeType);
|
||||||
|
return {
|
||||||
|
groups: ['top', 'right', 'bottom', 'left'].reduce((acc, position) => ({
|
||||||
|
...acc,
|
||||||
|
[position]: {
|
||||||
|
position,
|
||||||
|
attrs: {
|
||||||
|
circle: {
|
||||||
|
r: 4,
|
||||||
|
magnet: true,
|
||||||
|
stroke: config.theme.stroke,
|
||||||
|
strokeWidth: 2,
|
||||||
|
fill: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), {}),
|
||||||
|
items: [
|
||||||
|
{ group: 'top' },
|
||||||
|
{ group: 'right' },
|
||||||
|
{ group: 'bottom' },
|
||||||
|
{ group: 'left' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NodeConfigError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new NodeConfigError(`Failed to generate ports for node type: ${nodeType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const calculateNodePosition = (nodeType: string, dropPosition: Position): Position => {
|
||||||
|
try {
|
||||||
|
const config = getNodeConfig(nodeType);
|
||||||
|
const { width, height } = config.size;
|
||||||
|
return {
|
||||||
|
x: dropPosition.x - width / 2,
|
||||||
|
y: dropPosition.y - height / 2
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NodeConfigError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new NodeConfigError(`Failed to calculate position for node type: ${nodeType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user