@flowuent-org/diagramming-core 1.1.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +116 -116
- package/packages/diagrams/src/index.ts +1 -0
- package/packages/diagrams/src/lib/atoms/ConnectionPoints.tsx +149 -0
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +794 -650
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +606 -449
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +831 -687
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +420 -275
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +1118 -974
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +509 -344
- package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -0
- package/packages/diagrams/src/lib/components/automation/NodeActionButtons.tsx +146 -0
- package/packages/diagrams/src/lib/components/automation/index.ts +20 -11
- package/packages/diagrams/src/lib/molecules/SideHandles.tsx +177 -12
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +10 -5
- package/packages/diagrams/src/lib/styles.css +53 -0
- package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +59 -0
- package/packages/diagrams/src/lib/templates/Diagramming.tsx +25 -24
- package/packages/diagrams/src/lib/types/edge-types.ts +17 -0
- package/packages/diagrams/src/lib/utils/generateEdgesFromNodeOrder.ts +113 -0
- package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -0
|
@@ -7,24 +7,93 @@ import { useSelectedNode } from '../contexts/DiagramProvider';
|
|
|
7
7
|
// Define the props type for the component
|
|
8
8
|
interface SideHandlesProps {
|
|
9
9
|
className?: string;
|
|
10
|
+
/** Show all handles regardless of selection state */
|
|
11
|
+
alwaysShow?: boolean;
|
|
12
|
+
/** Which positions to show handles on */
|
|
13
|
+
positions?: ('top' | 'bottom' | 'left' | 'right')[];
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
// Connection point handle styles
|
|
17
|
+
const handleBaseStyle: React.CSSProperties = {
|
|
18
|
+
width: '14px',
|
|
19
|
+
height: '14px',
|
|
20
|
+
borderRadius: '50%',
|
|
21
|
+
border: '3px solid #10B981',
|
|
22
|
+
backgroundColor: '#1a1a2e',
|
|
23
|
+
transition: 'all 0.2s ease-in-out',
|
|
24
|
+
cursor: 'crosshair',
|
|
25
|
+
zIndex: 10,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Hidden handle style for the duplicate (allows bidirectional but only shows one visual handle)
|
|
29
|
+
const hiddenHandleStyle: React.CSSProperties = {
|
|
30
|
+
...handleBaseStyle,
|
|
31
|
+
opacity: 0,
|
|
32
|
+
pointerEvents: 'all',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleHoverStyle = `
|
|
36
|
+
.connection-handle:hover {
|
|
37
|
+
transform: scale(1.3);
|
|
38
|
+
background-color: #10B981 !important;
|
|
39
|
+
box-shadow: 0 0 12px rgba(16, 185, 129, 0.6);
|
|
40
|
+
}
|
|
41
|
+
.connection-handle:active {
|
|
42
|
+
transform: scale(1.1);
|
|
43
|
+
background-color: #059669 !important;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
12
47
|
export const SideHandles: React.FC<SideHandlesProps> = React.memo(
|
|
13
|
-
({ className }) => {
|
|
48
|
+
({ className, alwaysShow = false, positions = ['top', 'bottom', 'left', 'right'] }) => {
|
|
14
49
|
const sideHandles = useNodeSideHandles();
|
|
15
50
|
const nodeId = useNodeId();
|
|
16
51
|
const selectedNode = useSelectedNode();
|
|
52
|
+
|
|
53
|
+
const isSelected = selectedNode === nodeId;
|
|
54
|
+
const shouldShow = alwaysShow || isSelected;
|
|
55
|
+
|
|
56
|
+
// Inject hover styles
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
const styleId = 'connection-handle-styles';
|
|
59
|
+
if (!document.getElementById(styleId)) {
|
|
60
|
+
const styleSheet = document.createElement('style');
|
|
61
|
+
styleSheet.id = styleId;
|
|
62
|
+
styleSheet.textContent = handleHoverStyle;
|
|
63
|
+
document.head.appendChild(styleSheet);
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
17
66
|
|
|
18
67
|
if (sideHandles) {
|
|
19
68
|
return (
|
|
20
69
|
<div>
|
|
70
|
+
{/* Left - Bidirectional */}
|
|
21
71
|
<Handle
|
|
22
72
|
type="source"
|
|
73
|
+
id={`${nodeId}-left-source`}
|
|
74
|
+
position={Position.Left}
|
|
75
|
+
className="connection-handle"
|
|
76
|
+
style={handleBaseStyle}
|
|
77
|
+
/>
|
|
78
|
+
<Handle
|
|
79
|
+
type="target"
|
|
80
|
+
id={`${nodeId}-left-target`}
|
|
23
81
|
position={Position.Left}
|
|
82
|
+
style={hiddenHandleStyle}
|
|
24
83
|
/>
|
|
84
|
+
{/* Right - Bidirectional */}
|
|
25
85
|
<Handle
|
|
26
86
|
type="source"
|
|
87
|
+
id={`${nodeId}-right-source`}
|
|
88
|
+
position={Position.Right}
|
|
89
|
+
className="connection-handle"
|
|
90
|
+
style={handleBaseStyle}
|
|
91
|
+
/>
|
|
92
|
+
<Handle
|
|
93
|
+
type="target"
|
|
94
|
+
id={`${nodeId}-right-target`}
|
|
27
95
|
position={Position.Right}
|
|
96
|
+
style={hiddenHandleStyle}
|
|
28
97
|
/>
|
|
29
98
|
</div>
|
|
30
99
|
);
|
|
@@ -32,18 +101,114 @@ export const SideHandles: React.FC<SideHandlesProps> = React.memo(
|
|
|
32
101
|
|
|
33
102
|
return (
|
|
34
103
|
<Box
|
|
35
|
-
|
|
104
|
+
sx={{
|
|
105
|
+
opacity: shouldShow ? 1 : 0,
|
|
106
|
+
transition: 'opacity 0.2s ease-in-out',
|
|
107
|
+
pointerEvents: shouldShow ? 'auto' : 'none',
|
|
108
|
+
'& .react-flow__handle': {
|
|
109
|
+
visibility: shouldShow ? 'visible' : 'hidden',
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
36
112
|
>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
113
|
+
{/* Top Handle - Bidirectional (source + target) */}
|
|
114
|
+
{positions.includes('top') && (
|
|
115
|
+
<>
|
|
116
|
+
<Handle
|
|
117
|
+
type="source"
|
|
118
|
+
id={`${nodeId}-top-source`}
|
|
119
|
+
position={Position.Top}
|
|
120
|
+
className="connection-handle"
|
|
121
|
+
style={{
|
|
122
|
+
...handleBaseStyle,
|
|
123
|
+
top: '-8px',
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
<Handle
|
|
127
|
+
type="target"
|
|
128
|
+
id={`${nodeId}-top-target`}
|
|
129
|
+
position={Position.Top}
|
|
130
|
+
style={{
|
|
131
|
+
...hiddenHandleStyle,
|
|
132
|
+
top: '-8px',
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{/* Bottom Handle - Bidirectional (source + target) */}
|
|
139
|
+
{positions.includes('bottom') && (
|
|
140
|
+
<>
|
|
141
|
+
<Handle
|
|
142
|
+
type="source"
|
|
143
|
+
id={`${nodeId}-bottom-source`}
|
|
144
|
+
position={Position.Bottom}
|
|
145
|
+
className="connection-handle"
|
|
146
|
+
style={{
|
|
147
|
+
...handleBaseStyle,
|
|
148
|
+
bottom: '-8px',
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
<Handle
|
|
152
|
+
type="target"
|
|
153
|
+
id={`${nodeId}-bottom-target`}
|
|
154
|
+
position={Position.Bottom}
|
|
155
|
+
style={{
|
|
156
|
+
...hiddenHandleStyle,
|
|
157
|
+
bottom: '-8px',
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Left Handle - Bidirectional (source + target) */}
|
|
164
|
+
{positions.includes('left') && (
|
|
165
|
+
<>
|
|
166
|
+
<Handle
|
|
167
|
+
type="source"
|
|
168
|
+
id={`${nodeId}-left-source`}
|
|
169
|
+
position={Position.Left}
|
|
170
|
+
className="connection-handle"
|
|
171
|
+
style={{
|
|
172
|
+
...handleBaseStyle,
|
|
173
|
+
left: '-8px',
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
<Handle
|
|
177
|
+
type="target"
|
|
178
|
+
id={`${nodeId}-left-target`}
|
|
179
|
+
position={Position.Left}
|
|
180
|
+
style={{
|
|
181
|
+
...hiddenHandleStyle,
|
|
182
|
+
left: '-8px',
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
</>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{/* Right Handle - Bidirectional (source + target) */}
|
|
189
|
+
{positions.includes('right') && (
|
|
190
|
+
<>
|
|
191
|
+
<Handle
|
|
192
|
+
type="source"
|
|
193
|
+
id={`${nodeId}-right-source`}
|
|
194
|
+
position={Position.Right}
|
|
195
|
+
className="connection-handle"
|
|
196
|
+
style={{
|
|
197
|
+
...handleBaseStyle,
|
|
198
|
+
right: '-8px',
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
<Handle
|
|
202
|
+
type="target"
|
|
203
|
+
id={`${nodeId}-right-target`}
|
|
204
|
+
position={Position.Right}
|
|
205
|
+
style={{
|
|
206
|
+
...hiddenHandleStyle,
|
|
207
|
+
right: '-8px',
|
|
208
|
+
}}
|
|
209
|
+
/>
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
47
212
|
</Box>
|
|
48
213
|
);
|
|
49
214
|
},
|
|
@@ -94,21 +94,26 @@ export const CustomEdgeGenerator = (type: EdgeTypes = EdgeTypes.Default) => {
|
|
|
94
94
|
onDoubleClick={createBendPoint}
|
|
95
95
|
style={{
|
|
96
96
|
...style,
|
|
97
|
-
strokeWidth: (Number(style?.strokeWidth) ||
|
|
98
|
-
strokeOpacity: selectedEdgeId === id ? 0.
|
|
97
|
+
strokeWidth: (Number(style?.strokeWidth) || 2) * 4,
|
|
98
|
+
strokeOpacity: selectedEdgeId === id ? 0.2 : 0.05,
|
|
99
|
+
stroke: style?.stroke || '#10B981',
|
|
99
100
|
}}
|
|
100
101
|
fill="none"
|
|
101
102
|
/>
|
|
102
103
|
|
|
103
|
-
{/* Main path for the edge */}
|
|
104
|
+
{/* Main path for the edge - always visible */}
|
|
104
105
|
<path
|
|
105
106
|
id={id}
|
|
106
107
|
d={edgePath}
|
|
107
108
|
onClick={() => isErDiagram && toggleModal(setSelectedEdgeId)}
|
|
108
109
|
onDoubleClick={createBendPoint}
|
|
109
|
-
style={{
|
|
110
|
+
style={{
|
|
111
|
+
...style,
|
|
112
|
+
stroke: style?.stroke || '#10B981',
|
|
113
|
+
strokeWidth: style?.strokeWidth || 2,
|
|
114
|
+
}}
|
|
110
115
|
markerStart={isErDiagram ? `url(#${id}-marker-start)` : markerStart}
|
|
111
|
-
markerEnd={isErDiagram ? `url(#${id}-marker-end)` : markerEnd}
|
|
116
|
+
markerEnd={isErDiagram ? `url(#${id}-marker-end)` : (markerEnd || 'url(#arrow)')}
|
|
112
117
|
fill="none"
|
|
113
118
|
/>
|
|
114
119
|
</g>
|
|
@@ -1,3 +1,56 @@
|
|
|
1
1
|
.react-flow__edges {
|
|
2
2
|
z-index: 20 !important;
|
|
3
3
|
}
|
|
4
|
+
|
|
5
|
+
/* Connection Handle Styles */
|
|
6
|
+
.react-flow__handle {
|
|
7
|
+
transition: all 0.2s ease-in-out;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.react-flow__handle.connection-handle {
|
|
11
|
+
width: 14px;
|
|
12
|
+
height: 14px;
|
|
13
|
+
border-radius: 50%;
|
|
14
|
+
border: 3px solid #10B981;
|
|
15
|
+
background-color: #1a1a2e;
|
|
16
|
+
cursor: crosshair;
|
|
17
|
+
z-index: 10;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.react-flow__handle.connection-handle:hover {
|
|
21
|
+
transform: scale(1.3);
|
|
22
|
+
background-color: #10B981;
|
|
23
|
+
box-shadow: 0 0 12px rgba(16, 185, 129, 0.6);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.react-flow__handle.connection-handle:active,
|
|
27
|
+
.react-flow__handle.connection-handle.connecting {
|
|
28
|
+
transform: scale(1.1);
|
|
29
|
+
background-color: #059669;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Pulse animation for selected node handles */
|
|
33
|
+
@keyframes handle-pulse {
|
|
34
|
+
0%, 100% {
|
|
35
|
+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
|
|
36
|
+
}
|
|
37
|
+
50% {
|
|
38
|
+
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.react-flow__node.selected .connection-handle {
|
|
43
|
+
animation: handle-pulse 1.5s ease-in-out infinite;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Connection line styles when drawing */
|
|
47
|
+
.react-flow__connectionline {
|
|
48
|
+
stroke: #10B981;
|
|
49
|
+
stroke-width: 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Valid connection target highlight */
|
|
53
|
+
.react-flow__handle.connectionindicator {
|
|
54
|
+
background-color: #10B981;
|
|
55
|
+
border-color: #fff;
|
|
56
|
+
}
|
|
@@ -8,9 +8,68 @@ interface DiagramContainerProps
|
|
|
8
8
|
showAutomationExecutionPanel?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Default arrow marker definitions for edges
|
|
12
|
+
const EdgeMarkerDefinitions = () => (
|
|
13
|
+
<svg style={{ position: 'absolute', width: 0, height: 0 }}>
|
|
14
|
+
<defs>
|
|
15
|
+
{/* Arrow marker - Green */}
|
|
16
|
+
<marker
|
|
17
|
+
id="arrow"
|
|
18
|
+
viewBox="0 0 10 10"
|
|
19
|
+
refX="8"
|
|
20
|
+
refY="5"
|
|
21
|
+
markerWidth="6"
|
|
22
|
+
markerHeight="6"
|
|
23
|
+
orient="auto-start-reverse"
|
|
24
|
+
>
|
|
25
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#10B981" />
|
|
26
|
+
</marker>
|
|
27
|
+
|
|
28
|
+
{/* Arrow marker - Blue */}
|
|
29
|
+
<marker
|
|
30
|
+
id="arrow-blue"
|
|
31
|
+
viewBox="0 0 10 10"
|
|
32
|
+
refX="8"
|
|
33
|
+
refY="5"
|
|
34
|
+
markerWidth="6"
|
|
35
|
+
markerHeight="6"
|
|
36
|
+
orient="auto-start-reverse"
|
|
37
|
+
>
|
|
38
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#4858E9" />
|
|
39
|
+
</marker>
|
|
40
|
+
|
|
41
|
+
{/* Arrow marker - White */}
|
|
42
|
+
<marker
|
|
43
|
+
id="arrow-white"
|
|
44
|
+
viewBox="0 0 10 10"
|
|
45
|
+
refX="8"
|
|
46
|
+
refY="5"
|
|
47
|
+
markerWidth="6"
|
|
48
|
+
markerHeight="6"
|
|
49
|
+
orient="auto-start-reverse"
|
|
50
|
+
>
|
|
51
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#ffffff" />
|
|
52
|
+
</marker>
|
|
53
|
+
|
|
54
|
+
{/* Circle marker */}
|
|
55
|
+
<marker
|
|
56
|
+
id="circle"
|
|
57
|
+
viewBox="0 0 10 10"
|
|
58
|
+
refX="5"
|
|
59
|
+
refY="5"
|
|
60
|
+
markerWidth="5"
|
|
61
|
+
markerHeight="5"
|
|
62
|
+
>
|
|
63
|
+
<circle cx="5" cy="5" r="4" fill="#10B981" />
|
|
64
|
+
</marker>
|
|
65
|
+
</defs>
|
|
66
|
+
</svg>
|
|
67
|
+
);
|
|
68
|
+
|
|
11
69
|
export const DiagramContainer: React.FC<DiagramContainerProps> = (props) => {
|
|
12
70
|
return (
|
|
13
71
|
<ReactFlowProvider>
|
|
72
|
+
<EdgeMarkerDefinitions />
|
|
14
73
|
<DiagramContent {...props} />
|
|
15
74
|
</ReactFlowProvider>
|
|
16
75
|
);
|
|
@@ -45,6 +45,10 @@ export interface DiagrammingPageRef {
|
|
|
45
45
|
getEdges: () => any[];
|
|
46
46
|
// Get ordered nodes based on edge connections (arrow direction)
|
|
47
47
|
getNodeOrder: () => NodeOrderResult;
|
|
48
|
+
// Get current state for saving
|
|
49
|
+
getCurrentState: () => { nodes: any[]; edges: any[] };
|
|
50
|
+
// Set nodes and edges (for restoring saved state)
|
|
51
|
+
setNodesAndEdges: (nodes: any[], edges: any[]) => void;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
// Internal component that exposes undo/redo functionality
|
|
@@ -63,52 +67,42 @@ const DiagrammingPageInternal = forwardRef<DiagrammingPageRef, {
|
|
|
63
67
|
const edges = useDiagram((state) => state.edges);
|
|
64
68
|
const selectedNode = useDiagram((state) => state.selectedNode);
|
|
65
69
|
const setNodes = useDiagram((state) => state.setNodes);
|
|
70
|
+
const setEdges = useDiagram((state) => state.setEdges);
|
|
66
71
|
|
|
67
72
|
// Send node order to parent project whenever nodes/edges change
|
|
68
73
|
useEffect(() => {
|
|
69
74
|
if (edges.length > 0) {
|
|
70
|
-
// Get only nodes that are connected by edges
|
|
71
|
-
const nodeOrder = orderNodesByEdges(nodes, edges, { includeOrphanNodes: false });
|
|
72
|
-
|
|
73
|
-
// Get only connected nodes (nodes that appear in edges)
|
|
75
|
+
// Get only nodes that are connected by edges
|
|
74
76
|
const connectedNodeIds = new Set<string>();
|
|
75
77
|
edges.forEach((edge) => {
|
|
76
78
|
connectedNodeIds.add(edge.source);
|
|
77
79
|
connectedNodeIds.add(edge.target);
|
|
78
80
|
});
|
|
79
81
|
|
|
80
|
-
// Filter
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// Filter nodes to only include connected ones
|
|
83
|
+
const connectedNodes = nodes.filter((node) => connectedNodeIds.has(node.id));
|
|
84
|
+
|
|
85
|
+
// Get node order for connected nodes only
|
|
86
|
+
const nodeOrder = orderNodesByEdges(connectedNodes, edges, { includeOrphanNodes: false });
|
|
84
87
|
|
|
85
|
-
// Print
|
|
86
|
-
console.log('\n========== NODE ORDER
|
|
87
|
-
console.log('\n📋
|
|
88
|
-
|
|
88
|
+
// Print only connected nodes to console
|
|
89
|
+
console.log('\n========== NODE ORDER (Connected Only) ==========');
|
|
90
|
+
console.log('\n📋 EXECUTION ORDER:');
|
|
91
|
+
nodeOrder.orderedNodes.forEach((node, index) => {
|
|
89
92
|
console.log(` ${index + 1}. [${node.type || 'unknown'}] ${node.id}`);
|
|
90
93
|
});
|
|
91
94
|
|
|
92
|
-
console.log('\n🟢 START NODE:', nodeOrder.startNodes[0]?.id || '(none)');
|
|
93
|
-
console.log('🔴 END NODE:', nodeOrder.endNodes[0]?.id || '(none)');
|
|
94
|
-
|
|
95
95
|
console.log('\n🔗 EDGE ORDER:');
|
|
96
96
|
edges.forEach((edge, index) => {
|
|
97
97
|
console.log(` ${index + 1}. ${edge.source} ──→ ${edge.target}`);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
console.log('\n📊 TOTAL
|
|
101
|
-
console.log('
|
|
102
|
-
|
|
103
|
-
// Create updated result with only connected nodes
|
|
104
|
-
const connectedNodeOrder: NodeOrderResult = {
|
|
105
|
-
...nodeOrder,
|
|
106
|
-
orderedNodes: connectedOrderedNodes,
|
|
107
|
-
};
|
|
100
|
+
console.log('\n📊 TOTAL:', nodeOrder.orderedNodes.length, 'nodes,', edges.length, 'edges');
|
|
101
|
+
console.log('=================================================\n');
|
|
108
102
|
|
|
109
103
|
// Send to parent project if callback provided
|
|
110
104
|
if (onNodeOrderChange) {
|
|
111
|
-
onNodeOrderChange(
|
|
105
|
+
onNodeOrderChange(nodeOrder);
|
|
112
106
|
}
|
|
113
107
|
}
|
|
114
108
|
}, [nodes, edges, onNodeOrderChange]);
|
|
@@ -143,6 +137,13 @@ const DiagrammingPageInternal = forwardRef<DiagrammingPageRef, {
|
|
|
143
137
|
getEdges: () => edges,
|
|
144
138
|
// Get ordered nodes based on edge connections
|
|
145
139
|
getNodeOrder: () => orderNodesByEdges(nodes, edges),
|
|
140
|
+
// Get current state for saving
|
|
141
|
+
getCurrentState: () => ({ nodes: [...nodes], edges: [...edges] }),
|
|
142
|
+
// Set nodes and edges (for restoring saved state)
|
|
143
|
+
setNodesAndEdges: (newNodes: any[], newEdges: any[]) => {
|
|
144
|
+
setNodes(newNodes);
|
|
145
|
+
setEdges(newEdges);
|
|
146
|
+
},
|
|
146
147
|
}));
|
|
147
148
|
|
|
148
149
|
return (
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MarkerType } from '@xyflow/react';
|
|
1
2
|
import { Markers } from '../assets/markers/markers.type';
|
|
2
3
|
import { CustomEdgeGenerator } from '../organisms/CustomEdge/custom-edge-generator';
|
|
3
4
|
import { EdgeTypes } from '../organisms/CustomEdge/custom-edge.type';
|
|
@@ -13,6 +14,22 @@ export default {
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const defaultEdgeOptions = {
|
|
17
|
+
style: {
|
|
18
|
+
strokeWidth: 2,
|
|
19
|
+
stroke: '#10B981', // Green color for better visibility
|
|
20
|
+
},
|
|
21
|
+
// Arrow marker at the end of the edge
|
|
22
|
+
markerEnd: {
|
|
23
|
+
type: MarkerType.ArrowClosed,
|
|
24
|
+
width: 20,
|
|
25
|
+
height: 20,
|
|
26
|
+
color: '#10B981',
|
|
27
|
+
},
|
|
28
|
+
animated: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ER Diagram edge options (for entity relationship diagrams)
|
|
32
|
+
export const erDiagramEdgeOptions = {
|
|
16
33
|
style: {
|
|
17
34
|
strokeWidth: 2,
|
|
18
35
|
stroke: '#4858E9',
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Edge, MarkerType } from '@xyflow/react';
|
|
2
|
+
import { ICardNode } from '../types/card-node';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates edges based on the order of nodes in the array.
|
|
6
|
+
* Creates a linear connection from first node to last node.
|
|
7
|
+
*
|
|
8
|
+
* @param nodes - Array of nodes in the desired order
|
|
9
|
+
* @param options - Configuration options for edge generation
|
|
10
|
+
* @returns Array of edges connecting nodes in sequence
|
|
11
|
+
*/
|
|
12
|
+
export interface GenerateEdgesOptions {
|
|
13
|
+
/** Edge style stroke color */
|
|
14
|
+
strokeColor?: string;
|
|
15
|
+
/** Edge stroke width */
|
|
16
|
+
strokeWidth?: number;
|
|
17
|
+
/** Whether to add arrow marker at the end */
|
|
18
|
+
markerEnd?: boolean;
|
|
19
|
+
/** Whether edges should be animated */
|
|
20
|
+
animated?: boolean;
|
|
21
|
+
/** Source handle ID pattern (e.g., 'right-source', 'bottom-source') */
|
|
22
|
+
sourceHandleId?: string;
|
|
23
|
+
/** Target handle ID pattern (e.g., 'left-target', 'top-target') */
|
|
24
|
+
targetHandleId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const defaultOptions: GenerateEdgesOptions = {
|
|
28
|
+
strokeColor: '#10B981',
|
|
29
|
+
strokeWidth: 2,
|
|
30
|
+
markerEnd: true,
|
|
31
|
+
animated: false,
|
|
32
|
+
sourceHandleId: 'right-source',
|
|
33
|
+
targetHandleId: 'left-target',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function generateEdgesFromNodeOrder(
|
|
37
|
+
nodes: ICardNode[],
|
|
38
|
+
options: GenerateEdgesOptions = {}
|
|
39
|
+
): Edge[] {
|
|
40
|
+
const opts = { ...defaultOptions, ...options };
|
|
41
|
+
const edges: Edge[] = [];
|
|
42
|
+
|
|
43
|
+
// Need at least 2 nodes to create edges
|
|
44
|
+
if (nodes.length < 2) {
|
|
45
|
+
return edges;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create edges between consecutive nodes
|
|
49
|
+
for (let i = 0; i < nodes.length - 1; i++) {
|
|
50
|
+
const sourceNode = nodes[i];
|
|
51
|
+
const targetNode = nodes[i + 1];
|
|
52
|
+
|
|
53
|
+
const edge: Edge = {
|
|
54
|
+
id: `edge-${sourceNode.id}-${targetNode.id}`,
|
|
55
|
+
source: sourceNode.id,
|
|
56
|
+
target: targetNode.id,
|
|
57
|
+
sourceHandle: opts.sourceHandleId,
|
|
58
|
+
targetHandle: opts.targetHandleId,
|
|
59
|
+
type: 'default',
|
|
60
|
+
animated: opts.animated,
|
|
61
|
+
style: {
|
|
62
|
+
stroke: opts.strokeColor,
|
|
63
|
+
strokeWidth: opts.strokeWidth,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Add arrow marker if enabled
|
|
68
|
+
if (opts.markerEnd) {
|
|
69
|
+
edge.markerEnd = {
|
|
70
|
+
type: MarkerType.ArrowClosed,
|
|
71
|
+
width: 20,
|
|
72
|
+
height: 20,
|
|
73
|
+
color: opts.strokeColor,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
edges.push(edge);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return edges;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generates edges based on node order for automation diagrams (horizontal layout)
|
|
85
|
+
*/
|
|
86
|
+
export function generateAutomationEdges(nodes: ICardNode[]): Edge[] {
|
|
87
|
+
return generateEdgesFromNodeOrder(nodes, {
|
|
88
|
+
strokeColor: '#10B981',
|
|
89
|
+
strokeWidth: 2,
|
|
90
|
+
markerEnd: true,
|
|
91
|
+
animated: false,
|
|
92
|
+
sourceHandleId: 'right-source',
|
|
93
|
+
targetHandleId: 'left-target',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generates edges based on node order for vertical layout diagrams
|
|
99
|
+
*/
|
|
100
|
+
export function generateVerticalEdges(nodes: ICardNode[]): Edge[] {
|
|
101
|
+
return generateEdgesFromNodeOrder(nodes, {
|
|
102
|
+
strokeColor: '#10B981',
|
|
103
|
+
strokeWidth: 2,
|
|
104
|
+
markerEnd: true,
|
|
105
|
+
animated: false,
|
|
106
|
+
sourceHandleId: 'bottom-source',
|
|
107
|
+
targetHandleId: 'top-target',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default generateEdgesFromNodeOrder;
|
|
112
|
+
|
|
113
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration utility for Node AI Assistant
|
|
3
|
+
* This allows setting a global API endpoint and headers for all node AI assistants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let globalApiEndpoint: string | undefined = undefined;
|
|
7
|
+
let globalApiHeaders: Record<string, string> = {};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set the global API endpoint for Node AI Assistant
|
|
11
|
+
* @param endpoint - The API endpoint URL (e.g., 'https://api.example.com/chat')
|
|
12
|
+
*/
|
|
13
|
+
export const setNodeAIAssistantEndpoint = (endpoint: string | undefined) => {
|
|
14
|
+
globalApiEndpoint = endpoint;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set global API headers for Node AI Assistant requests
|
|
19
|
+
* @param headers - Headers object (e.g., { 'Authorization': 'Bearer token' })
|
|
20
|
+
*/
|
|
21
|
+
export const setNodeAIAssistantHeaders = (headers: Record<string, string>) => {
|
|
22
|
+
globalApiHeaders = headers;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the global API endpoint
|
|
27
|
+
*/
|
|
28
|
+
export const getNodeAIAssistantEndpoint = (): string | undefined => {
|
|
29
|
+
return globalApiEndpoint;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the global API headers
|
|
34
|
+
*/
|
|
35
|
+
export const getNodeAIAssistantHeaders = (): Record<string, string> => {
|
|
36
|
+
return globalApiHeaders;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configure Node AI Assistant with endpoint and headers
|
|
41
|
+
* @param config - Configuration object
|
|
42
|
+
*/
|
|
43
|
+
export const configureNodeAIAssistant = (config: {
|
|
44
|
+
endpoint?: string;
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
}) => {
|
|
47
|
+
if (config.endpoint !== undefined) {
|
|
48
|
+
globalApiEndpoint = config.endpoint;
|
|
49
|
+
}
|
|
50
|
+
if (config.headers) {
|
|
51
|
+
globalApiHeaders = { ...globalApiHeaders, ...config.headers };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|