@flowuent-org/diagramming-core 1.3.5 → 1.3.6
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 +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +7 -1
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +14 -4
- package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +4 -3
- package/packages/diagrams/src/lib/contexts/diagramStoreTypes.tsx +1 -0
- package/packages/diagrams/src/lib/contexts/onDragStart.ts +17 -0
- package/packages/diagrams/src/lib/contexts/onNodeDragEnd.ts +109 -15
- package/packages/diagrams/src/lib/contexts/onNodesChange.ts +32 -11
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeTypeDisplay.tsx +103 -15
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/WorkflowNodeIcons.tsx +23 -0
- package/packages/diagrams/src/lib/templates/DiagramContent.tsx +153 -30
- package/packages/molecules/src/index.ts +23 -4
- package/packages/molecules/src/lib/SvgIcons/icons.tsx +291 -0
- package/packages/molecules/src/lib/SvgIcons/index.ts +25 -0
- package/packages/molecules/src/lib/SvgIcons/types.ts +7 -0
package/package.json
CHANGED
|
@@ -14,6 +14,7 @@ import { NodeActionButtons } from './NodeActionButtons';
|
|
|
14
14
|
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
15
15
|
import { useSearch } from '../../contexts/SearchContext';
|
|
16
16
|
import { getStatusColor } from './statusColors';
|
|
17
|
+
import { EndNodeIcon } from '@flowuent-labs/molecules';
|
|
17
18
|
|
|
18
19
|
interface AutomationEndNodeProps {
|
|
19
20
|
data: {
|
|
@@ -57,6 +58,7 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
57
58
|
|
|
58
59
|
// Get the icon component based on the iconName
|
|
59
60
|
const IconComponent = getIconByName(data.iconName);
|
|
61
|
+
const isEndNode = data.label === 'End' || data.label === 'End Node';
|
|
60
62
|
|
|
61
63
|
const handleJsonClick = () => {
|
|
62
64
|
if (nodeId) setSelectedNode(nodeId);
|
|
@@ -275,7 +277,11 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
275
277
|
justifyContent: 'center',
|
|
276
278
|
}}
|
|
277
279
|
>
|
|
278
|
-
|
|
280
|
+
{isEndNode ? (
|
|
281
|
+
<EndNodeIcon size={18} color="#FFFFFF" />
|
|
282
|
+
) : (
|
|
283
|
+
<IconComponent sx={{ color: 'white', fontSize: '18px' }} />
|
|
284
|
+
)}
|
|
279
285
|
</Box>
|
|
280
286
|
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
|
|
281
287
|
{highlightText(data.label)}
|
|
@@ -23,6 +23,7 @@ import { NodeActionButtons } from './NodeActionButtons';
|
|
|
23
23
|
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
24
24
|
import { useSearch } from '../../contexts/SearchContext';
|
|
25
25
|
import { getStatusColor } from './statusColors';
|
|
26
|
+
import { ArticleAnalyzerIcon } from '@flowuent-labs/molecules';
|
|
26
27
|
|
|
27
28
|
interface AutomationFormattingNodeProps {
|
|
28
29
|
data: {
|
|
@@ -80,8 +81,13 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
80
81
|
const nodes = useDiagram((state) => state.nodes);
|
|
81
82
|
const setNodes = useDiagram((state) => state.setNodes);
|
|
82
83
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
84
|
+
// Check if this is Article Analyzer node
|
|
85
|
+
const isArticleAnalyzer = data.label === 'Article Analyzer';
|
|
86
|
+
|
|
87
|
+
// Get the icon component based on the iconName or use ArticleAnalyzerIcon for Article Analyzer
|
|
88
|
+
const IconComponent = isArticleAnalyzer
|
|
89
|
+
? ArticleAnalyzerIcon
|
|
90
|
+
: getIconByName(data.iconName);
|
|
85
91
|
|
|
86
92
|
const handleJsonClick = () => {
|
|
87
93
|
if (nodeId) setSelectedNode(nodeId);
|
|
@@ -342,14 +348,18 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
342
348
|
sx={{
|
|
343
349
|
width: '32px',
|
|
344
350
|
height: '32px',
|
|
345
|
-
backgroundColor: '#
|
|
351
|
+
backgroundColor: '#1E3A8A',
|
|
346
352
|
borderRadius: '50%',
|
|
347
353
|
display: 'flex',
|
|
348
354
|
alignItems: 'center',
|
|
349
355
|
justifyContent: 'center',
|
|
350
356
|
}}
|
|
351
357
|
>
|
|
352
|
-
|
|
358
|
+
{isArticleAnalyzer ? (
|
|
359
|
+
<ArticleAnalyzerIcon size={18} color="#FFFFFF" />
|
|
360
|
+
) : (
|
|
361
|
+
<IconComponent size={18} color="#FFFFFF" />
|
|
362
|
+
)}
|
|
353
363
|
</Box>
|
|
354
364
|
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
|
|
355
365
|
{highlightText(data.label)}
|
|
@@ -4,7 +4,7 @@ import { Handle, Position, useNodeId } from '@xyflow/react';
|
|
|
4
4
|
import { Box, Typography, Chip, IconButton, Card, CardContent, Tooltip } from '@mui/material';
|
|
5
5
|
import {
|
|
6
6
|
AccessTime as AccessTimeIcon,
|
|
7
|
-
Navigation as
|
|
7
|
+
Navigation as MuiNavigationIcon,
|
|
8
8
|
OpenInBrowser as OpenInBrowserIcon,
|
|
9
9
|
TouchApp as TouchAppIcon,
|
|
10
10
|
Search as SearchIcon,
|
|
@@ -19,6 +19,7 @@ import { NodeActionButtons } from './NodeActionButtons';
|
|
|
19
19
|
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
20
20
|
import { useSearch } from '../../contexts/SearchContext';
|
|
21
21
|
import { getStatusColor } from './statusColors';
|
|
22
|
+
import { NavigationIcon } from '@flowuent-labs/molecules';
|
|
22
23
|
|
|
23
24
|
interface AutomationNavigationNodeProps {
|
|
24
25
|
data: {
|
|
@@ -212,13 +213,13 @@ export const AutomationNavigationNode: React.FC<AutomationNavigationNodeProps> =
|
|
|
212
213
|
case 'click':
|
|
213
214
|
return <TouchAppIcon sx={{ fontSize: '18px' }} />;
|
|
214
215
|
case 'scroll':
|
|
215
|
-
return <NavigationIcon
|
|
216
|
+
return <NavigationIcon size={18} color="#93C5FD" />;
|
|
216
217
|
case 'wait':
|
|
217
218
|
return <AccessTimeIcon sx={{ fontSize: '18px' }} />;
|
|
218
219
|
case 'extract':
|
|
219
220
|
return <SearchIcon sx={{ fontSize: '18px' }} />;
|
|
220
221
|
default:
|
|
221
|
-
return <NavigationIcon
|
|
222
|
+
return <NavigationIcon size={18} color="#93C5FD" />;
|
|
222
223
|
}
|
|
223
224
|
};
|
|
224
225
|
|
|
@@ -66,6 +66,7 @@ export interface DiagramState {
|
|
|
66
66
|
nodes: ICardNode[];
|
|
67
67
|
defaultNodes: ICardNode[];
|
|
68
68
|
dragStartPosition: DragStartPosition | null;
|
|
69
|
+
dragStartPositions: Record<string, { x: number; y: number }> | null;
|
|
69
70
|
edges: Edge[];
|
|
70
71
|
defaultEdges: Edge[];
|
|
71
72
|
history: HistoryEvent[];
|
|
@@ -20,9 +20,26 @@ export const onNodeDragStart =
|
|
|
20
20
|
positionAbsolute: cardNode.position,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
// Store initial positions for ALL selected nodes (for multi-node dragging)
|
|
24
|
+
// Get selected nodes from state.nodes (they should have selected property set by ReactFlow)
|
|
25
|
+
const selectedNodes = state.nodes.filter(n => (n as any).selected);
|
|
26
|
+
let dragStartPositions: Record<string, { x: number; y: number }> | null = null;
|
|
27
|
+
|
|
28
|
+
if (selectedNodes.length > 1) {
|
|
29
|
+
// Multiple nodes are selected - store all their initial positions
|
|
30
|
+
dragStartPositions = {};
|
|
31
|
+
selectedNodes.forEach(selectedNode => {
|
|
32
|
+
dragStartPositions![selectedNode.id] = {
|
|
33
|
+
x: selectedNode.position.x,
|
|
34
|
+
y: selectedNode.position.y,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
return {
|
|
24
40
|
...state,
|
|
25
41
|
dragStartPosition: changeForUndo,
|
|
42
|
+
dragStartPositions: dragStartPositions || null,
|
|
26
43
|
};
|
|
27
44
|
});
|
|
28
45
|
};
|
|
@@ -18,10 +18,101 @@ export const onNodeDragEnd =
|
|
|
18
18
|
const cardNode = node as ICardNode;
|
|
19
19
|
set((state: DiagramStore) => {
|
|
20
20
|
const dragStartPosition = state.dragStartPosition;
|
|
21
|
+
const dragStartPositions = state.dragStartPositions;
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
+
// Get all currently selected nodes from state (they should have selected property)
|
|
24
|
+
// Also get the current node's position from ReactFlow (cardNode parameter)
|
|
25
|
+
const selectedNodesInState = state.nodes.filter(n => (n as any).selected);
|
|
26
|
+
const isMultiDrag = dragStartPositions && Object.keys(dragStartPositions).length > 1;
|
|
27
|
+
|
|
28
|
+
// If multiple nodes were dragged, handle all of them
|
|
29
|
+
if (isMultiDrag && dragStartPositions) {
|
|
30
|
+
const historyEvents: HistoryEvent[] = [];
|
|
31
|
+
const updatedNodes = state.nodes.map((n) => {
|
|
32
|
+
// Check if this node was part of the multi-drag
|
|
33
|
+
if (dragStartPositions![n.id]) {
|
|
34
|
+
const startPos = dragStartPositions[n.id];
|
|
35
|
+
// For the dragged node, use position from ReactFlow's node parameter (most up-to-date)
|
|
36
|
+
// For other nodes, use position from state.nodes (updated by onNodesChange)
|
|
37
|
+
let endPos = n.position;
|
|
38
|
+
if (n.id === cardNode.id) {
|
|
39
|
+
// Use the position from ReactFlow for the node being dragged
|
|
40
|
+
endPos = cardNode.position;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if this node moved
|
|
44
|
+
if (startPos.x !== endPos.x || startPos.y !== endPos.y) {
|
|
45
|
+
// Create history event for this node
|
|
46
|
+
historyEvents.push({
|
|
47
|
+
title: `Node '${n.id}' moved from (${startPos.x.toFixed(1)}, ${startPos.y.toFixed(1)}) to (${endPos.x.toFixed(1)}, ${endPos.y.toFixed(1)})`,
|
|
48
|
+
backward: {
|
|
49
|
+
t: 'node-change',
|
|
50
|
+
type: 'position',
|
|
51
|
+
id: n.id,
|
|
52
|
+
message: `Revert: Node '${n.id}' position back to (${startPos.x.toFixed(1)}, ${startPos.y.toFixed(1)})`,
|
|
53
|
+
position: startPos,
|
|
54
|
+
positionAbsolute: startPos,
|
|
55
|
+
},
|
|
56
|
+
forward: {
|
|
57
|
+
t: 'node-change',
|
|
58
|
+
type: 'position',
|
|
59
|
+
id: n.id,
|
|
60
|
+
message: `Redo: Node '${n.id}' moved to (${endPos.x}, ${endPos.y})`,
|
|
61
|
+
position: endPos,
|
|
62
|
+
positionAbsolute: endPos,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Update node position - ensure it matches ReactFlow's current state
|
|
67
|
+
return {
|
|
68
|
+
...n,
|
|
69
|
+
position: endPos,
|
|
70
|
+
data: { ...n.data, isPinned: true },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return n;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Trigger onChange callbacks for all moved nodes
|
|
78
|
+
if (onChange && historyEvents.length > 0) {
|
|
79
|
+
historyEvents.forEach((historyEvent) => {
|
|
80
|
+
onChange({
|
|
81
|
+
t: 'node-change',
|
|
82
|
+
type: 'position',
|
|
83
|
+
id: historyEvent.forward.id,
|
|
84
|
+
position: historyEvent.forward.position,
|
|
85
|
+
positionAbsolute: historyEvent.forward.positionAbsolute,
|
|
86
|
+
message: historyEvent.title,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...addToHistory(state, historyEvents),
|
|
93
|
+
dragStartPosition: null,
|
|
94
|
+
dragStartPositions: null,
|
|
95
|
+
nodes: updatedNodes,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Single node drag handling (original logic)
|
|
23
100
|
if (!dragStartPosition) {
|
|
24
|
-
|
|
101
|
+
// Ensure the node's current position is persisted in state
|
|
102
|
+
const nodeInState = state.nodes.find((n) => n.id === cardNode.id);
|
|
103
|
+
if (nodeInState && (
|
|
104
|
+
nodeInState.position.x !== cardNode.position.x ||
|
|
105
|
+
nodeInState.position.y !== cardNode.position.y
|
|
106
|
+
)) {
|
|
107
|
+
// Update the node position to match the dragged position
|
|
108
|
+
const updatedNodes = state.nodes.map((n) =>
|
|
109
|
+
n.id === cardNode.id
|
|
110
|
+
? { ...n, position: cardNode.position, data: { ...n.data, isPinned: true } }
|
|
111
|
+
: n,
|
|
112
|
+
);
|
|
113
|
+
return { ...state, nodes: updatedNodes, dragStartPositions: null };
|
|
114
|
+
}
|
|
115
|
+
return { ...state, dragStartPositions: null };
|
|
25
116
|
}
|
|
26
117
|
|
|
27
118
|
// Check if the node has moved
|
|
@@ -29,17 +120,21 @@ export const onNodeDragEnd =
|
|
|
29
120
|
dragStartPosition.position.x !== cardNode.position.x ||
|
|
30
121
|
dragStartPosition.position.y !== cardNode.position.y
|
|
31
122
|
) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
123
|
+
// Update nodes array to ensure position is persisted
|
|
124
|
+
const updatedNodes = state.nodes.map((n) => {
|
|
125
|
+
if (n.id === cardNode.id) {
|
|
126
|
+
return {
|
|
127
|
+
...n,
|
|
128
|
+
position: cardNode.position,
|
|
129
|
+
data: { ...n.data, isPinned: true }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return n;
|
|
133
|
+
});
|
|
37
134
|
|
|
38
|
-
// Create the HistoryEvent entry
|
|
135
|
+
// Create the HistoryEvent entry
|
|
39
136
|
const historyEvent: HistoryEvent = {
|
|
40
137
|
title: `Node '${cardNode.id}' moved from (${dragStartPosition.position.x.toFixed(1)}, ${dragStartPosition.position.y.toFixed(1)}) to (${cardNode.position.x.toFixed(1)}, ${cardNode.position.y.toFixed(1)})`,
|
|
41
|
-
|
|
42
|
-
// Backward change for undo
|
|
43
138
|
backward: {
|
|
44
139
|
t: 'node-change',
|
|
45
140
|
type: 'position',
|
|
@@ -48,8 +143,6 @@ export const onNodeDragEnd =
|
|
|
48
143
|
position: dragStartPosition.position,
|
|
49
144
|
positionAbsolute: dragStartPosition.position,
|
|
50
145
|
},
|
|
51
|
-
|
|
52
|
-
// Forward change for redo
|
|
53
146
|
forward: {
|
|
54
147
|
t: 'node-change',
|
|
55
148
|
type: 'position',
|
|
@@ -74,12 +167,13 @@ export const onNodeDragEnd =
|
|
|
74
167
|
|
|
75
168
|
return {
|
|
76
169
|
...addToHistory(state, [historyEvent]),
|
|
77
|
-
dragStartPosition: null,
|
|
170
|
+
dragStartPosition: null,
|
|
171
|
+
dragStartPositions: null,
|
|
78
172
|
nodes: updatedNodes,
|
|
79
173
|
};
|
|
80
174
|
}
|
|
81
175
|
|
|
82
|
-
// If no movement occurred, just reset dragStartPosition
|
|
83
|
-
return { ...state, dragStartPosition: null };
|
|
176
|
+
// If no movement occurred, just reset dragStartPosition
|
|
177
|
+
return { ...state, dragStartPosition: null, dragStartPositions: null };
|
|
84
178
|
});
|
|
85
179
|
};
|
|
@@ -277,24 +277,44 @@ function handleElementChanges(
|
|
|
277
277
|
|
|
278
278
|
const currentElements = elementType === 'node' ? get().nodes : get().edges;
|
|
279
279
|
|
|
280
|
+
// Separate position changes from other changes
|
|
281
|
+
const positionChanges: NodePositionChange[] = [];
|
|
282
|
+
const dimensionChanges: NodeDimensionChange[] = [];
|
|
283
|
+
const otherChanges: (NodeChange | EdgeChange)[] = [];
|
|
284
|
+
|
|
280
285
|
changes.forEach((change) => {
|
|
281
|
-
if (change.type === 'position'
|
|
282
|
-
|
|
286
|
+
if (change.type === 'position') {
|
|
287
|
+
positionChanges.push(change as NodePositionChange);
|
|
288
|
+
} else if (change.type === 'dimensions') {
|
|
289
|
+
dimensionChanges.push(change as NodeDimensionChange);
|
|
283
290
|
} else if (change.type === 'replace') {
|
|
284
291
|
accumulatedResetChanges.push(change as ResetChange & { t: string });
|
|
285
292
|
} else {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
otherChanges.push(change);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Handle other changes (add, remove, etc.)
|
|
298
|
+
otherChanges.forEach((change) => {
|
|
299
|
+
const historyEvent = createHistoryEvent(
|
|
300
|
+
change,
|
|
301
|
+
currentElements,
|
|
302
|
+
elementType,
|
|
303
|
+
);
|
|
304
|
+
if (historyEvent) {
|
|
305
|
+
newChanges.push(historyEvent.forward);
|
|
306
|
+
newHistory.push(historyEvent);
|
|
295
307
|
}
|
|
296
308
|
});
|
|
297
309
|
|
|
310
|
+
// Handle position changes - apply them but don't add to history
|
|
311
|
+
// History will be handled by onNodeDragEnd for individual nodes
|
|
312
|
+
// For multiple nodes, we need to ensure positions are applied
|
|
313
|
+
if (positionChanges.length > 0 && elementType === 'node') {
|
|
314
|
+
// Apply position changes immediately to ensure they persist
|
|
315
|
+
// Don't add to history here as onNodeDragEnd will handle it
|
|
316
|
+
}
|
|
317
|
+
|
|
298
318
|
if (accumulatedResetChanges.length > 0) {
|
|
299
319
|
const [resetChangesToEmit, historyChanges] = handleAccumulatedResets(
|
|
300
320
|
accumulatedResetChanges,
|
|
@@ -310,6 +330,7 @@ function handleElementChanges(
|
|
|
310
330
|
newChanges.forEach((change) => onChange(change));
|
|
311
331
|
}
|
|
312
332
|
|
|
333
|
+
// Apply ALL changes including position changes to ensure they persist
|
|
313
334
|
const updatedElements =
|
|
314
335
|
elementType === 'node'
|
|
315
336
|
? applyNodeChanges(
|
|
@@ -1,25 +1,113 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Typography } from '@mui/material';
|
|
3
|
-
import { useNodeTitle, useNodeType } from '../../contexts/CardDataProvider';
|
|
2
|
+
import { Typography, Box } from '@mui/material';
|
|
3
|
+
import { useNodeTitle, useNodeType, useNodeVariant } from '../../contexts/CardDataProvider';
|
|
4
|
+
import { NodeVariant } from '../../types/card-node';
|
|
5
|
+
import {
|
|
6
|
+
ParallelIcon,
|
|
7
|
+
BranchIcon,
|
|
8
|
+
GroupIcon,
|
|
9
|
+
ReturnIcon,
|
|
10
|
+
LoopIcon,
|
|
11
|
+
SwitchIcon,
|
|
12
|
+
TryIcon,
|
|
13
|
+
CatchIcon,
|
|
14
|
+
TryCatchIcon,
|
|
15
|
+
FunctionIcon,
|
|
16
|
+
CallIcon,
|
|
17
|
+
LetIcon,
|
|
18
|
+
SetIcon,
|
|
19
|
+
EmptyNodeIcon,
|
|
20
|
+
EntityIcon,
|
|
21
|
+
} from '@flowuent-labs/molecules';
|
|
22
|
+
|
|
23
|
+
// Icon mapping for different node types
|
|
24
|
+
const getNodeIcon = (type: NodeVariant | string | undefined) => {
|
|
25
|
+
if (!type) return null;
|
|
26
|
+
|
|
27
|
+
const iconStyle = {
|
|
28
|
+
marginRight: '6px',
|
|
29
|
+
display: 'inline-block',
|
|
30
|
+
verticalAlign: 'middle',
|
|
31
|
+
flexShrink: 0,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'parallel':
|
|
36
|
+
return <ParallelIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
37
|
+
case 'branch':
|
|
38
|
+
return <BranchIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
39
|
+
case 'group':
|
|
40
|
+
return <GroupIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
41
|
+
case 'return':
|
|
42
|
+
return <ReturnIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
43
|
+
case 'loop':
|
|
44
|
+
case 'forLoop':
|
|
45
|
+
return <LoopIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
46
|
+
case 'switch':
|
|
47
|
+
return <SwitchIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
48
|
+
case 'try':
|
|
49
|
+
return <TryIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
50
|
+
case 'catch':
|
|
51
|
+
return <CatchIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
52
|
+
case 'tryCatch':
|
|
53
|
+
return <TryCatchIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
54
|
+
case 'function':
|
|
55
|
+
return <FunctionIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
56
|
+
case 'call':
|
|
57
|
+
return <CallIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
58
|
+
case 'let':
|
|
59
|
+
return <LetIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
60
|
+
case 'set':
|
|
61
|
+
return <SetIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
62
|
+
case 'emptyNode':
|
|
63
|
+
return <EmptyNodeIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
64
|
+
case 'entity':
|
|
65
|
+
return <EntityIcon size={15} color="#86EFAC" style={iconStyle} />;
|
|
66
|
+
default:
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
4
70
|
|
|
5
71
|
const NodeTypeDisplay = React.memo(() => {
|
|
6
72
|
const type = useNodeType();
|
|
73
|
+
const variant = useNodeVariant();
|
|
7
74
|
const title = useNodeTitle();
|
|
75
|
+
// Get type from data.type or data.variant as fallback
|
|
76
|
+
const nodeType = (type || variant) as NodeVariant | string | undefined;
|
|
77
|
+
const icon = getNodeIcon(nodeType as NodeVariant);
|
|
78
|
+
|
|
8
79
|
return (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
80
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'nowrap' }}>
|
|
81
|
+
{icon && (
|
|
82
|
+
<Box
|
|
83
|
+
component="span"
|
|
84
|
+
sx={{
|
|
85
|
+
display: 'inline-flex',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
justifyContent: 'center',
|
|
88
|
+
flexShrink: 0,
|
|
89
|
+
lineHeight: 1,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{icon}
|
|
93
|
+
</Box>
|
|
21
94
|
)}
|
|
22
|
-
|
|
95
|
+
<Typography
|
|
96
|
+
sx={{
|
|
97
|
+
textTransform: 'capitalize',
|
|
98
|
+
fontStyle: 'italic',
|
|
99
|
+
fontSize: 12,
|
|
100
|
+
color: '#FFFFFF',
|
|
101
|
+
display: 'inline-flex',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{`[${nodeType || 'unknown'}]`}
|
|
106
|
+
{(title === 'Then' || title === 'Else') && (
|
|
107
|
+
<span>{': ' + title}</span>
|
|
108
|
+
)}
|
|
109
|
+
</Typography>
|
|
110
|
+
</Box>
|
|
23
111
|
);
|
|
24
112
|
});
|
|
25
113
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Re-export all icons from molecules package
|
|
2
|
+
export {
|
|
3
|
+
ParallelIcon,
|
|
4
|
+
BranchIcon,
|
|
5
|
+
GroupIcon,
|
|
6
|
+
ReturnIcon,
|
|
7
|
+
LoopIcon,
|
|
8
|
+
SwitchIcon,
|
|
9
|
+
TryIcon,
|
|
10
|
+
CatchIcon,
|
|
11
|
+
TryCatchIcon,
|
|
12
|
+
FunctionIcon,
|
|
13
|
+
CallIcon,
|
|
14
|
+
LetIcon,
|
|
15
|
+
SetIcon,
|
|
16
|
+
EmptyNodeIcon,
|
|
17
|
+
EntityIcon,
|
|
18
|
+
StartNodeIcon,
|
|
19
|
+
NavigationIcon,
|
|
20
|
+
} from '@flowuent-labs/molecules';
|
|
21
|
+
|
|
22
|
+
// Re-export types for backward compatibility
|
|
23
|
+
export type { SvgIconProps as IconProps } from '@flowuent-labs/molecules';
|
|
@@ -87,13 +87,19 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
87
87
|
const canRedo = useCanRedo();
|
|
88
88
|
|
|
89
89
|
// Zoom hooks - get zoom functions from ReactFlow
|
|
90
|
-
const { zoomIn, zoomOut, fitView, setViewport, getViewport, getNodes } = useReactFlow();
|
|
90
|
+
const { zoomIn, zoomOut, fitView, setViewport, getViewport, getNodes, screenToFlowPosition } = useReactFlow();
|
|
91
91
|
|
|
92
92
|
// Search bar state
|
|
93
93
|
const [showSearchBar, setShowSearchBar] = useState(false);
|
|
94
94
|
|
|
95
|
-
// Clipboard state for copy/paste
|
|
96
|
-
const [
|
|
95
|
+
// Clipboard state for copy/paste - support multiple nodes
|
|
96
|
+
const [copiedNodes, setCopiedNodes] = useState<Node[]>([]);
|
|
97
|
+
|
|
98
|
+
// Store last click position on canvas for paste positioning
|
|
99
|
+
const [lastClickPosition, setLastClickPosition] = useState<{ x: number; y: number } | null>(null);
|
|
100
|
+
|
|
101
|
+
// Flag to prevent layout recalculation immediately after paste
|
|
102
|
+
const skipLayoutRef = useRef(false);
|
|
97
103
|
|
|
98
104
|
// Rename state
|
|
99
105
|
const [renamingNodeId, setRenamingNodeId] = useState<string | null>(null);
|
|
@@ -109,40 +115,111 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
109
115
|
setShowSearchBar(true);
|
|
110
116
|
}, []);
|
|
111
117
|
|
|
112
|
-
// Copy selected
|
|
118
|
+
// Copy selected nodes (single or multiple)
|
|
113
119
|
const handleCopyNode = useCallback(() => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
// Get all selected nodes from ReactFlow
|
|
121
|
+
const selectedNodesList = getNodes().filter(node => node.selected);
|
|
122
|
+
|
|
123
|
+
// If multiple nodes are selected, copy all of them
|
|
124
|
+
if (selectedNodesList.length > 0) {
|
|
125
|
+
setCopiedNodes(selectedNodesList);
|
|
126
|
+
} else {
|
|
127
|
+
// Fallback to single node selection (custom selectedNode state)
|
|
128
|
+
const nodeToCopy = visibleNodes.find(node => node.id === selectedNode);
|
|
129
|
+
if (nodeToCopy) {
|
|
130
|
+
setCopiedNodes([nodeToCopy]);
|
|
131
|
+
}
|
|
117
132
|
}
|
|
118
|
-
}, [visibleNodes, selectedNode]);
|
|
133
|
+
}, [visibleNodes, selectedNode, getNodes]);
|
|
119
134
|
|
|
120
|
-
// Paste
|
|
135
|
+
// Paste nodes (single or multiple) at the clicked position
|
|
121
136
|
const handlePasteNode = useCallback(() => {
|
|
122
|
-
if (
|
|
137
|
+
if (copiedNodes.length === 0) return;
|
|
123
138
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
// Determine paste position:
|
|
140
|
+
// 1. Use last click position on canvas if available
|
|
141
|
+
// 2. Fallback to offset from original position
|
|
142
|
+
let pasteX: number;
|
|
143
|
+
let pasteY: number;
|
|
144
|
+
|
|
145
|
+
if (lastClickPosition) {
|
|
146
|
+
// Use stored click position
|
|
147
|
+
pasteX = lastClickPosition.x;
|
|
148
|
+
pasteY = lastClickPosition.y;
|
|
149
|
+
} else {
|
|
150
|
+
// Fallback: use offset from original position
|
|
151
|
+
const minX = Math.min(...copiedNodes.map(node => node.position.x));
|
|
152
|
+
const minY = Math.min(...copiedNodes.map(node => node.position.y));
|
|
153
|
+
pasteX = minX + 50;
|
|
154
|
+
pasteY = minY + 50;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Find the minimum X and Y positions of copied nodes (reference point)
|
|
158
|
+
const minX = Math.min(...copiedNodes.map(node => node.position.x));
|
|
159
|
+
const minY = Math.min(...copiedNodes.map(node => node.position.y));
|
|
160
|
+
|
|
161
|
+
// Calculate offset from reference point to paste position
|
|
162
|
+
const offsetX = pasteX - minX;
|
|
163
|
+
const offsetY = pasteY - minY;
|
|
164
|
+
|
|
165
|
+
// Create new nodes with updated IDs and positions
|
|
166
|
+
const newNodes = copiedNodes.map((node, index) => {
|
|
167
|
+
// Calculate relative position from the minimum position
|
|
168
|
+
const relativeX = node.position.x - minX;
|
|
169
|
+
const relativeY = node.position.y - minY;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...node,
|
|
173
|
+
id: `${node.id}-copy-${Date.now()}-${index}`,
|
|
174
|
+
position: {
|
|
175
|
+
x: minX + relativeX + offsetX,
|
|
176
|
+
y: minY + relativeY + offsetY,
|
|
177
|
+
},
|
|
178
|
+
selected: false,
|
|
179
|
+
data: {
|
|
180
|
+
...node.data,
|
|
181
|
+
isPinned: true, // Mark as pinned to prevent auto-layout from moving it
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Prevent layout recalculation from moving pasted nodes
|
|
187
|
+
skipLayoutRef.current = true;
|
|
188
|
+
|
|
189
|
+
// Add all new nodes to the diagram
|
|
190
|
+
setNodes([...nodes, ...newNodes as any]);
|
|
133
191
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
192
|
+
// Select the first pasted node (or all if multiple)
|
|
193
|
+
if (newNodes.length === 1) {
|
|
194
|
+
setSelectedNode(newNodes[0].id);
|
|
195
|
+
} else {
|
|
196
|
+
// For multiple nodes, select the first one (ReactFlow will handle multi-selection)
|
|
197
|
+
setSelectedNode(newNodes[0].id);
|
|
198
|
+
}
|
|
199
|
+
}, [copiedNodes, nodes, setNodes, setSelectedNode, lastClickPosition]);
|
|
137
200
|
|
|
138
|
-
// Delete selected
|
|
201
|
+
// Delete selected nodes (single or multiple)
|
|
139
202
|
const handleDeleteNode = useCallback(() => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
203
|
+
// Get all selected nodes from ReactFlow
|
|
204
|
+
const selectedNodesList = getNodes().filter(node => node.selected);
|
|
205
|
+
|
|
206
|
+
if (selectedNodesList.length > 0) {
|
|
207
|
+
// Delete all selected nodes
|
|
208
|
+
const changes = selectedNodesList.map(node => ({
|
|
209
|
+
id: node.id,
|
|
210
|
+
type: 'remove' as const,
|
|
211
|
+
}));
|
|
212
|
+
onNodesChange(changes);
|
|
143
213
|
setSelectedNode(null);
|
|
214
|
+
} else {
|
|
215
|
+
// Fallback to single node deletion (custom selectedNode state)
|
|
216
|
+
const nodeToDelete = visibleNodes.find(node => node.id === selectedNode);
|
|
217
|
+
if (nodeToDelete && onNodesChange) {
|
|
218
|
+
onNodesChange([{ id: nodeToDelete.id, type: 'remove' }]);
|
|
219
|
+
setSelectedNode(null);
|
|
220
|
+
}
|
|
144
221
|
}
|
|
145
|
-
}, [visibleNodes, selectedNode, onNodesChange, setSelectedNode]);
|
|
222
|
+
}, [visibleNodes, selectedNode, onNodesChange, setSelectedNode, getNodes]);
|
|
146
223
|
|
|
147
224
|
// Rename node
|
|
148
225
|
const handleRenameNode = useCallback(() => {
|
|
@@ -388,6 +465,12 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
388
465
|
|
|
389
466
|
// Debounce layout recalculation to prevent continuous propagation
|
|
390
467
|
useEffect(() => {
|
|
468
|
+
// Skip layout recalculation if we just pasted nodes
|
|
469
|
+
if (skipLayoutRef.current) {
|
|
470
|
+
skipLayoutRef.current = false;
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
391
474
|
// Clear any pending layout recalculation
|
|
392
475
|
if (layoutTimeoutRef.current) {
|
|
393
476
|
clearTimeout(layoutTimeoutRef.current);
|
|
@@ -536,18 +619,35 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
536
619
|
onEdgesChange={onEdgesChange}
|
|
537
620
|
onReconnect={onReconnect}
|
|
538
621
|
onNodeClick={(event, node) => {
|
|
539
|
-
|
|
540
|
-
|
|
622
|
+
// Don't interfere with multi-selection - let ReactFlow handle it
|
|
623
|
+
// Only set our custom selectedNode if it's a single click without modifiers
|
|
624
|
+
if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
|
625
|
+
// Check if multiple nodes are selected - if so, don't override
|
|
626
|
+
const selectedNodes = getNodes().filter(n => n.selected);
|
|
627
|
+
if (selectedNodes.length <= 1) {
|
|
628
|
+
event.stopPropagation();
|
|
629
|
+
setSelectedNode(node.id);
|
|
630
|
+
}
|
|
631
|
+
// If multiple nodes are selected, let ReactFlow handle the drag
|
|
632
|
+
}
|
|
541
633
|
}}
|
|
542
634
|
onPaneClick={(event) => {
|
|
543
635
|
setSelectedNode(null);
|
|
544
636
|
closeModalsOnClickOutside(event);
|
|
637
|
+
// Store click position for paste operation
|
|
638
|
+
const flowPosition = screenToFlowPosition({
|
|
639
|
+
x: event.clientX,
|
|
640
|
+
y: event.clientY,
|
|
641
|
+
});
|
|
642
|
+
setLastClickPosition(flowPosition);
|
|
545
643
|
}}
|
|
546
644
|
nodesConnectable={true}
|
|
547
645
|
nodesFocusable={true}
|
|
548
646
|
edgesFocusable={true}
|
|
549
647
|
elementsSelectable={true}
|
|
550
648
|
selectNodesOnDrag={false}
|
|
649
|
+
selectionOnDrag={true}
|
|
650
|
+
selectionKeyCode={["Control", "Meta"]}
|
|
551
651
|
defaultEdgeOptions={{
|
|
552
652
|
reconnectable: true,
|
|
553
653
|
}}
|
|
@@ -672,7 +772,30 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
672
772
|
connectionMode={ConnectionMode.Strict}
|
|
673
773
|
suppressContentEditableWarning
|
|
674
774
|
suppressHydrationWarning
|
|
675
|
-
onPaneClick={
|
|
775
|
+
onPaneClick={(event) => {
|
|
776
|
+
closeModalsOnClickOutside(event);
|
|
777
|
+
// Store click position for paste operation
|
|
778
|
+
const flowPosition = screenToFlowPosition({
|
|
779
|
+
x: event.clientX,
|
|
780
|
+
y: event.clientY,
|
|
781
|
+
});
|
|
782
|
+
setLastClickPosition(flowPosition);
|
|
783
|
+
}}
|
|
784
|
+
onNodeClick={(event, node) => {
|
|
785
|
+
// Don't interfere with multi-selection - let ReactFlow handle it
|
|
786
|
+
// Only set our custom selectedNode if it's a single click without modifiers
|
|
787
|
+
if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
|
788
|
+
// Check if multiple nodes are selected - if so, don't override
|
|
789
|
+
const selectedNodes = getNodes().filter(n => n.selected);
|
|
790
|
+
if (selectedNodes.length <= 1) {
|
|
791
|
+
event.stopPropagation();
|
|
792
|
+
setSelectedNode(node.id);
|
|
793
|
+
}
|
|
794
|
+
// If multiple nodes are selected, let ReactFlow handle the drag
|
|
795
|
+
}
|
|
796
|
+
}}
|
|
797
|
+
selectionOnDrag={true}
|
|
798
|
+
selectionKeyCode={["Control", "Meta"]}
|
|
676
799
|
{...props}
|
|
677
800
|
/>
|
|
678
801
|
{props.children}
|
|
@@ -6,7 +6,29 @@ import ParamsActionField from './lib/ParamsActionField/ParamsActionField';
|
|
|
6
6
|
import EditorTab from './lib/EditorTab/EditorTab';
|
|
7
7
|
import CloseableButton from './lib/ClosableButton/CloseableButton';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Export SVG Icons
|
|
10
|
+
export {
|
|
11
|
+
ArticleAnalyzerIcon,
|
|
12
|
+
StartNodeIcon,
|
|
13
|
+
EndNodeIcon,
|
|
14
|
+
NavigationIcon,
|
|
15
|
+
ParallelIcon,
|
|
16
|
+
BranchIcon,
|
|
17
|
+
GroupIcon,
|
|
18
|
+
ReturnIcon,
|
|
19
|
+
LoopIcon,
|
|
20
|
+
SwitchIcon,
|
|
21
|
+
TryIcon,
|
|
22
|
+
CatchIcon,
|
|
23
|
+
TryCatchIcon,
|
|
24
|
+
FunctionIcon,
|
|
25
|
+
CallIcon,
|
|
26
|
+
LetIcon,
|
|
27
|
+
SetIcon,
|
|
28
|
+
EmptyNodeIcon,
|
|
29
|
+
EntityIcon,
|
|
30
|
+
} from './lib/SvgIcons';
|
|
31
|
+
export type { SvgIconProps } from './lib/SvgIcons';
|
|
10
32
|
|
|
11
33
|
export {
|
|
12
34
|
UserCard,
|
|
@@ -15,8 +37,5 @@ export {
|
|
|
15
37
|
HTTPVerbSelection,
|
|
16
38
|
ParamsActionField,
|
|
17
39
|
EditorTab,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
40
|
CloseableButton
|
|
22
41
|
};
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SvgIconProps } from './types';
|
|
3
|
+
|
|
4
|
+
// Base SVG icon component
|
|
5
|
+
const BaseIcon: React.FC<SvgIconProps & { children: React.ReactNode }> = ({
|
|
6
|
+
size = 15,
|
|
7
|
+
color = '#86EFAC',
|
|
8
|
+
style,
|
|
9
|
+
children
|
|
10
|
+
}) => (
|
|
11
|
+
<svg
|
|
12
|
+
width={size}
|
|
13
|
+
height={size}
|
|
14
|
+
viewBox="0 0 15 15"
|
|
15
|
+
fill="none"
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
style={style}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Article Analyzer Icon
|
|
24
|
+
export const ArticleAnalyzerIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
25
|
+
<svg
|
|
26
|
+
width={size || 12}
|
|
27
|
+
height={size ? (size * 15) / 12 : 15}
|
|
28
|
+
viewBox="0 0 12 15"
|
|
29
|
+
fill="none"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
style={style}
|
|
32
|
+
>
|
|
33
|
+
<path
|
|
34
|
+
d="M7.83333 0.5H1.83333C1.47971 0.5 1.14057 0.640476 0.890524 0.890524C0.640476 1.14057 0.5 1.47971 0.5 1.83333V12.5C0.5 12.8536 0.640476 13.1928 0.890524 13.4428C1.14057 13.6929 1.47971 13.8333 1.83333 13.8333H9.83333C10.187 13.8333 10.5261 13.6929 10.7761 13.4428C11.0262 13.1928 11.1667 12.8536 11.1667 12.5V3.83333L7.83333 0.5Z"
|
|
35
|
+
stroke={color || "#D8B4FE"}
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
strokeLinejoin="round"
|
|
38
|
+
/>
|
|
39
|
+
<path
|
|
40
|
+
d="M7.16797 0.5V3.16667C7.16797 3.52029 7.30844 3.85943 7.55849 4.10948C7.80854 4.35952 8.14768 4.5 8.5013 4.5H11.168"
|
|
41
|
+
stroke={color || "#D8B4FE"}
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
/>
|
|
45
|
+
<path
|
|
46
|
+
d="M4.5013 5.16699H3.16797"
|
|
47
|
+
stroke={color || "#D8B4FE"}
|
|
48
|
+
strokeLinecap="round"
|
|
49
|
+
strokeLinejoin="round"
|
|
50
|
+
/>
|
|
51
|
+
<path
|
|
52
|
+
d="M8.5013 7.83398H3.16797"
|
|
53
|
+
stroke={color || "#D8B4FE"}
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
/>
|
|
57
|
+
<path
|
|
58
|
+
d="M8.5013 10.5H3.16797"
|
|
59
|
+
stroke={color || "#D8B4FE"}
|
|
60
|
+
strokeLinecap="round"
|
|
61
|
+
strokeLinejoin="round"
|
|
62
|
+
/>
|
|
63
|
+
</svg>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Start Node Icon
|
|
67
|
+
export const StartNodeIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
68
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
69
|
+
<path
|
|
70
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
71
|
+
stroke={color}
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
strokeLinejoin="round"
|
|
74
|
+
/>
|
|
75
|
+
<path
|
|
76
|
+
d="M5.83203 4.5L9.83203 7.16667L5.83203 9.83333V4.5Z"
|
|
77
|
+
stroke={color}
|
|
78
|
+
strokeLinecap="round"
|
|
79
|
+
strokeLinejoin="round"
|
|
80
|
+
/>
|
|
81
|
+
</BaseIcon>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Navigation Icon
|
|
85
|
+
export const NavigationIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#93C5FD', style }) => (
|
|
86
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
87
|
+
<path
|
|
88
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
89
|
+
stroke={color}
|
|
90
|
+
strokeLinecap="round"
|
|
91
|
+
strokeLinejoin="round"
|
|
92
|
+
/>
|
|
93
|
+
<path
|
|
94
|
+
d="M7.16667 0.5C5.45482 2.29744 4.5 4.68449 4.5 7.16667C4.5 9.64884 5.45482 12.0359 7.16667 13.8333C8.87851 12.0359 9.83333 9.64884 9.83333 7.16667C9.83333 4.68449 8.87851 2.29744 7.16667 0.5Z"
|
|
95
|
+
stroke={color}
|
|
96
|
+
strokeLinecap="round"
|
|
97
|
+
strokeLinejoin="round"
|
|
98
|
+
/>
|
|
99
|
+
<path
|
|
100
|
+
d="M0.5 7.16699H13.8333"
|
|
101
|
+
stroke={color}
|
|
102
|
+
strokeLinecap="round"
|
|
103
|
+
strokeLinejoin="round"
|
|
104
|
+
/>
|
|
105
|
+
</BaseIcon>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Workflow node type icons
|
|
109
|
+
export const ParallelIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
110
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
111
|
+
<path
|
|
112
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
113
|
+
stroke={color}
|
|
114
|
+
strokeLinecap="round"
|
|
115
|
+
strokeLinejoin="round"
|
|
116
|
+
/>
|
|
117
|
+
</BaseIcon>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
export const BranchIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
121
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
122
|
+
<path
|
|
123
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
124
|
+
stroke={color}
|
|
125
|
+
strokeLinecap="round"
|
|
126
|
+
strokeLinejoin="round"
|
|
127
|
+
/>
|
|
128
|
+
</BaseIcon>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
export const GroupIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
132
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
133
|
+
<path
|
|
134
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
135
|
+
stroke={color}
|
|
136
|
+
strokeLinecap="round"
|
|
137
|
+
strokeLinejoin="round"
|
|
138
|
+
/>
|
|
139
|
+
</BaseIcon>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
export const ReturnIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
143
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
144
|
+
<path
|
|
145
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
146
|
+
stroke={color}
|
|
147
|
+
strokeLinecap="round"
|
|
148
|
+
strokeLinejoin="round"
|
|
149
|
+
/>
|
|
150
|
+
</BaseIcon>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
export const LoopIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
154
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
155
|
+
<path
|
|
156
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
157
|
+
stroke={color}
|
|
158
|
+
strokeLinecap="round"
|
|
159
|
+
strokeLinejoin="round"
|
|
160
|
+
/>
|
|
161
|
+
</BaseIcon>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
export const SwitchIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
165
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
166
|
+
<path
|
|
167
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
168
|
+
stroke={color}
|
|
169
|
+
strokeLinecap="round"
|
|
170
|
+
strokeLinejoin="round"
|
|
171
|
+
/>
|
|
172
|
+
</BaseIcon>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
export const TryIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
176
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
177
|
+
<path
|
|
178
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
179
|
+
stroke={color}
|
|
180
|
+
strokeLinecap="round"
|
|
181
|
+
strokeLinejoin="round"
|
|
182
|
+
/>
|
|
183
|
+
</BaseIcon>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
export const CatchIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
187
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
188
|
+
<path
|
|
189
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
190
|
+
stroke={color}
|
|
191
|
+
strokeLinecap="round"
|
|
192
|
+
strokeLinejoin="round"
|
|
193
|
+
/>
|
|
194
|
+
</BaseIcon>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
export const TryCatchIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
198
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
199
|
+
<path
|
|
200
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
201
|
+
stroke={color}
|
|
202
|
+
strokeLinecap="round"
|
|
203
|
+
strokeLinejoin="round"
|
|
204
|
+
/>
|
|
205
|
+
</BaseIcon>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
export const FunctionIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
209
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
210
|
+
<path
|
|
211
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
212
|
+
stroke={color}
|
|
213
|
+
strokeLinecap="round"
|
|
214
|
+
strokeLinejoin="round"
|
|
215
|
+
/>
|
|
216
|
+
</BaseIcon>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
export const CallIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
220
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
221
|
+
<path
|
|
222
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
223
|
+
stroke={color}
|
|
224
|
+
strokeLinecap="round"
|
|
225
|
+
strokeLinejoin="round"
|
|
226
|
+
/>
|
|
227
|
+
</BaseIcon>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
export const LetIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
231
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
232
|
+
<path
|
|
233
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
234
|
+
stroke={color}
|
|
235
|
+
strokeLinecap="round"
|
|
236
|
+
strokeLinejoin="round"
|
|
237
|
+
/>
|
|
238
|
+
</BaseIcon>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
export const SetIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
242
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
243
|
+
<path
|
|
244
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
245
|
+
stroke={color}
|
|
246
|
+
strokeLinecap="round"
|
|
247
|
+
strokeLinejoin="round"
|
|
248
|
+
/>
|
|
249
|
+
</BaseIcon>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
export const EmptyNodeIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
253
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
254
|
+
<path
|
|
255
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
256
|
+
stroke={color}
|
|
257
|
+
strokeLinecap="round"
|
|
258
|
+
strokeLinejoin="round"
|
|
259
|
+
/>
|
|
260
|
+
</BaseIcon>
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
export const EntityIcon: React.FC<SvgIconProps> = ({ size = 15, color = '#86EFAC', style }) => (
|
|
264
|
+
<BaseIcon size={size} color={color} style={style}>
|
|
265
|
+
<path
|
|
266
|
+
d="M7.16667 13.8333C10.8486 13.8333 13.8333 10.8486 13.8333 7.16667C13.8333 3.48477 10.8486 0.5 7.16667 0.5C3.48477 0.5 0.5 3.48477 0.5 7.16667C0.5 10.8486 3.48477 13.8333 7.16667 13.8333Z"
|
|
267
|
+
stroke={color}
|
|
268
|
+
strokeLinecap="round"
|
|
269
|
+
strokeLinejoin="round"
|
|
270
|
+
/>
|
|
271
|
+
</BaseIcon>
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// End Node Icon
|
|
275
|
+
export const EndNodeIcon: React.FC<SvgIconProps> = ({ size = 13, color = '#FCA5A5', style }) => (
|
|
276
|
+
<svg
|
|
277
|
+
width={size}
|
|
278
|
+
height={size}
|
|
279
|
+
viewBox="0 0 13 13"
|
|
280
|
+
fill="none"
|
|
281
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
282
|
+
style={style}
|
|
283
|
+
>
|
|
284
|
+
<path
|
|
285
|
+
d="M11.1667 0.5H1.83333C1.09695 0.5 0.5 1.09695 0.5 1.83333V11.1667C0.5 11.903 1.09695 12.5 1.83333 12.5H11.1667C11.903 12.5 12.5 11.903 12.5 11.1667V1.83333C12.5 1.09695 11.903 0.5 11.1667 0.5Z"
|
|
286
|
+
stroke={color}
|
|
287
|
+
strokeLinecap="round"
|
|
288
|
+
strokeLinejoin="round"
|
|
289
|
+
/>
|
|
290
|
+
</svg>
|
|
291
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Export types
|
|
2
|
+
export type { SvgIconProps } from './types';
|
|
3
|
+
|
|
4
|
+
// Export all icons from single icons file
|
|
5
|
+
export {
|
|
6
|
+
ArticleAnalyzerIcon,
|
|
7
|
+
StartNodeIcon,
|
|
8
|
+
EndNodeIcon,
|
|
9
|
+
NavigationIcon,
|
|
10
|
+
ParallelIcon,
|
|
11
|
+
BranchIcon,
|
|
12
|
+
GroupIcon,
|
|
13
|
+
ReturnIcon,
|
|
14
|
+
LoopIcon,
|
|
15
|
+
SwitchIcon,
|
|
16
|
+
TryIcon,
|
|
17
|
+
CatchIcon,
|
|
18
|
+
TryCatchIcon,
|
|
19
|
+
FunctionIcon,
|
|
20
|
+
CallIcon,
|
|
21
|
+
LetIcon,
|
|
22
|
+
SetIcon,
|
|
23
|
+
EmptyNodeIcon,
|
|
24
|
+
EntityIcon,
|
|
25
|
+
} from './icons';
|