@flowuent-org/diagramming-core 1.1.3 → 1.1.5
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/AutomationApiNode.tsx +11 -7
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +11 -7
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +11 -7
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +50 -14
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +11 -7
- package/packages/diagrams/src/lib/contexts/DiagramProvider.tsx +17 -0
- package/packages/diagrams/src/lib/contexts/diagramStoreTypes.tsx +1 -0
- package/packages/diagrams/src/lib/contexts/onReconnect.ts +59 -0
- package/packages/diagrams/src/lib/templates/DiagramContent.tsx +548 -338
package/package.json
CHANGED
|
@@ -295,12 +295,12 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
|
|
|
295
295
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
296
296
|
'@keyframes pulse-glow': {
|
|
297
297
|
'0%, 100%': {
|
|
298
|
-
boxShadow: '0 0 20px rgba(
|
|
299
|
-
borderColor: 'rgba(
|
|
298
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
299
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
300
300
|
},
|
|
301
301
|
'50%': {
|
|
302
|
-
boxShadow: '0 0 30px rgba(
|
|
303
|
-
borderColor: 'rgba(
|
|
302
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
303
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
304
304
|
},
|
|
305
305
|
},
|
|
306
306
|
}),
|
|
@@ -345,19 +345,23 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
|
|
|
345
345
|
</Typography>
|
|
346
346
|
</Box>
|
|
347
347
|
<Chip
|
|
348
|
-
label={data.status}
|
|
348
|
+
label={data.status || 'Ready'}
|
|
349
349
|
size="small"
|
|
350
350
|
sx={{
|
|
351
351
|
backgroundColor: data.status === 'Completed'
|
|
352
352
|
? 'rgba(37, 99, 235, 0.1)'
|
|
353
353
|
: data.status === 'Running'
|
|
354
354
|
? 'rgba(251, 191, 36, 0.1)'
|
|
355
|
-
:
|
|
355
|
+
: data.status === 'Error'
|
|
356
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
357
|
+
: 'rgba(16, 185, 129, 0.1)',
|
|
356
358
|
color: data.status === 'Completed'
|
|
357
359
|
? '#93C5FD'
|
|
358
360
|
: data.status === 'Running'
|
|
359
361
|
? '#FCD34D'
|
|
360
|
-
: '
|
|
362
|
+
: data.status === 'Error'
|
|
363
|
+
? '#FCA5A5'
|
|
364
|
+
: '#86EFAC',
|
|
361
365
|
fontWeight: 500,
|
|
362
366
|
fontSize: '12px',
|
|
363
367
|
height: '24px',
|
|
@@ -224,12 +224,12 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
224
224
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
225
225
|
'@keyframes pulse-glow': {
|
|
226
226
|
'0%, 100%': {
|
|
227
|
-
boxShadow: '0 0 20px rgba(
|
|
228
|
-
borderColor: 'rgba(
|
|
227
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
228
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
229
229
|
},
|
|
230
230
|
'50%': {
|
|
231
|
-
boxShadow: '0 0 30px rgba(
|
|
232
|
-
borderColor: 'rgba(
|
|
231
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
232
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
233
233
|
},
|
|
234
234
|
},
|
|
235
235
|
}),
|
|
@@ -274,19 +274,23 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
274
274
|
</Typography>
|
|
275
275
|
</Box>
|
|
276
276
|
<Chip
|
|
277
|
-
label={data.status}
|
|
277
|
+
label={data.status || 'Ready'}
|
|
278
278
|
size="small"
|
|
279
279
|
sx={{
|
|
280
280
|
backgroundColor: data.status === 'Completed'
|
|
281
281
|
? 'rgba(37, 99, 235, 0.1)'
|
|
282
282
|
: data.status === 'Running'
|
|
283
283
|
? 'rgba(251, 191, 36, 0.1)'
|
|
284
|
-
:
|
|
284
|
+
: data.status === 'Error'
|
|
285
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
286
|
+
: 'rgba(16, 185, 129, 0.1)',
|
|
285
287
|
color: data.status === 'Completed'
|
|
286
288
|
? '#93C5FD'
|
|
287
289
|
: data.status === 'Running'
|
|
288
290
|
? '#FCD34D'
|
|
289
|
-
: '
|
|
291
|
+
: data.status === 'Error'
|
|
292
|
+
? '#FCA5A5'
|
|
293
|
+
: '#86EFAC',
|
|
290
294
|
fontWeight: 500,
|
|
291
295
|
fontSize: '12px',
|
|
292
296
|
height: '24px',
|
|
@@ -298,12 +298,12 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
298
298
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
299
299
|
'@keyframes pulse-glow': {
|
|
300
300
|
'0%, 100%': {
|
|
301
|
-
boxShadow: '0 0 20px rgba(
|
|
302
|
-
borderColor: 'rgba(
|
|
301
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
302
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
303
303
|
},
|
|
304
304
|
'50%': {
|
|
305
|
-
boxShadow: '0 0 30px rgba(
|
|
306
|
-
borderColor: 'rgba(
|
|
305
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
306
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
307
307
|
},
|
|
308
308
|
},
|
|
309
309
|
}),
|
|
@@ -348,19 +348,23 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
348
348
|
</Typography>
|
|
349
349
|
</Box>
|
|
350
350
|
<Chip
|
|
351
|
-
label={data.status}
|
|
351
|
+
label={data.status || 'Ready'}
|
|
352
352
|
size="small"
|
|
353
353
|
sx={{
|
|
354
354
|
backgroundColor: data.status === 'Completed'
|
|
355
355
|
? 'rgba(37, 99, 235, 0.1)'
|
|
356
356
|
: data.status === 'Running'
|
|
357
357
|
? 'rgba(251, 191, 36, 0.1)'
|
|
358
|
-
:
|
|
358
|
+
: data.status === 'Error'
|
|
359
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
360
|
+
: 'rgba(16, 185, 129, 0.1)',
|
|
359
361
|
color: data.status === 'Completed'
|
|
360
362
|
? '#93C5FD'
|
|
361
363
|
: data.status === 'Running'
|
|
362
364
|
? '#FCD34D'
|
|
363
|
-
: '
|
|
365
|
+
: data.status === 'Error'
|
|
366
|
+
? '#FCA5A5'
|
|
367
|
+
: '#86EFAC',
|
|
364
368
|
fontWeight: 500,
|
|
365
369
|
fontSize: '12px',
|
|
366
370
|
height: '24px',
|
|
@@ -722,6 +722,19 @@ Data: ${JSON.stringify(data, null, 2)}
|
|
|
722
722
|
transition: 'all 0.2s ease',
|
|
723
723
|
cursor: 'pointer',
|
|
724
724
|
overflow: 'hidden',
|
|
725
|
+
...(data.status === 'Running' && {
|
|
726
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
727
|
+
'@keyframes pulse-glow': {
|
|
728
|
+
'0%, 100%': {
|
|
729
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
730
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
731
|
+
},
|
|
732
|
+
'50%': {
|
|
733
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
734
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
}),
|
|
725
738
|
}}
|
|
726
739
|
onClick={handleJsonClick}
|
|
727
740
|
>
|
|
@@ -752,21 +765,44 @@ Data: ${JSON.stringify(data, null, 2)}
|
|
|
752
765
|
</Typography>
|
|
753
766
|
</Box>
|
|
754
767
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (status === '
|
|
763
|
-
|
|
764
|
-
if (status === 'Success') return t('automation.sheetsNode.status.success');
|
|
765
|
-
if (status === 'Ready') return t('automation.sheetsNode.status.ready');
|
|
766
|
-
if (status === 'Mixed Status') return t('automation.sheetsNode.status.mixed');
|
|
767
|
-
return status;
|
|
768
|
+
{/* Status Chip */}
|
|
769
|
+
<Chip
|
|
770
|
+
label={(() => {
|
|
771
|
+
// Map node status to standard status values
|
|
772
|
+
const nodeStatus = getNodeStatus();
|
|
773
|
+
if (nodeStatus === 'Failed' || data.status === 'Error') return 'Error';
|
|
774
|
+
if (nodeStatus === 'Running' || data.status === 'Running') return 'Running';
|
|
775
|
+
if (nodeStatus === 'Success' || data.status === 'Completed') return 'Completed';
|
|
776
|
+
return data.status || 'Ready';
|
|
768
777
|
})()}
|
|
769
|
-
|
|
778
|
+
size="small"
|
|
779
|
+
sx={{
|
|
780
|
+
backgroundColor: (() => {
|
|
781
|
+
const status = data.status || 'Ready';
|
|
782
|
+
return status === 'Completed'
|
|
783
|
+
? 'rgba(37, 99, 235, 0.1)'
|
|
784
|
+
: status === 'Running'
|
|
785
|
+
? 'rgba(251, 191, 36, 0.1)'
|
|
786
|
+
: status === 'Error'
|
|
787
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
788
|
+
: 'rgba(16, 185, 129, 0.1)';
|
|
789
|
+
})(),
|
|
790
|
+
color: (() => {
|
|
791
|
+
const status = data.status || 'Ready';
|
|
792
|
+
return status === 'Completed'
|
|
793
|
+
? '#93C5FD'
|
|
794
|
+
: status === 'Running'
|
|
795
|
+
? '#FCD34D'
|
|
796
|
+
: status === 'Error'
|
|
797
|
+
? '#FCA5A5'
|
|
798
|
+
: '#86EFAC';
|
|
799
|
+
})(),
|
|
800
|
+
fontWeight: 500,
|
|
801
|
+
fontSize: '12px',
|
|
802
|
+
height: '24px',
|
|
803
|
+
borderRadius: '12px',
|
|
804
|
+
}}
|
|
805
|
+
/>
|
|
770
806
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
771
807
|
<AccessTimeIcon sx={{ fontSize: '12px', color: '#94a3b8' }} />
|
|
772
808
|
<Typography variant="body2" sx={{ color: '#94a3b8', fontSize: '11px' }}>
|
|
@@ -212,12 +212,12 @@ export const AutomationStartNode: React.FC<AutomationStartNodeProps> = ({ data,
|
|
|
212
212
|
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
213
213
|
'@keyframes pulse-glow': {
|
|
214
214
|
'0%, 100%': {
|
|
215
|
-
boxShadow: '0 0 20px rgba(
|
|
216
|
-
borderColor: 'rgba(
|
|
215
|
+
boxShadow: '0 0 20px rgba(59, 130, 246, 0.4), 0 0 40px rgba(59, 130, 246, 0.2)',
|
|
216
|
+
borderColor: 'rgba(59, 130, 246, 0.6)',
|
|
217
217
|
},
|
|
218
218
|
'50%': {
|
|
219
|
-
boxShadow: '0 0 30px rgba(
|
|
220
|
-
borderColor: 'rgba(
|
|
219
|
+
boxShadow: '0 0 30px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.3)',
|
|
220
|
+
borderColor: 'rgba(59, 130, 246, 0.9)',
|
|
221
221
|
},
|
|
222
222
|
},
|
|
223
223
|
}),
|
|
@@ -262,19 +262,23 @@ export const AutomationStartNode: React.FC<AutomationStartNodeProps> = ({ data,
|
|
|
262
262
|
</Typography>
|
|
263
263
|
</Box>
|
|
264
264
|
<Chip
|
|
265
|
-
label={data.status}
|
|
265
|
+
label={data.status || 'Ready'}
|
|
266
266
|
size="small"
|
|
267
267
|
sx={{
|
|
268
268
|
backgroundColor: data.status === 'Completed'
|
|
269
269
|
? 'rgba(37, 99, 235, 0.1)'
|
|
270
270
|
: data.status === 'Running'
|
|
271
271
|
? 'rgba(251, 191, 36, 0.1)'
|
|
272
|
-
:
|
|
272
|
+
: data.status === 'Error'
|
|
273
|
+
? 'rgba(239, 68, 68, 0.1)'
|
|
274
|
+
: 'rgba(16, 185, 129, 0.1)',
|
|
273
275
|
color: data.status === 'Completed'
|
|
274
276
|
? '#93C5FD'
|
|
275
277
|
: data.status === 'Running'
|
|
276
278
|
? '#FCD34D'
|
|
277
|
-
: '
|
|
279
|
+
: data.status === 'Error'
|
|
280
|
+
? '#FCA5A5'
|
|
281
|
+
: '#86EFAC',
|
|
278
282
|
fontWeight: 500,
|
|
279
283
|
fontSize: '12px',
|
|
280
284
|
height: '24px',
|
|
@@ -8,6 +8,7 @@ import { ICardNode } from '../types/card-node';
|
|
|
8
8
|
import { setPannable } from './setPannable';
|
|
9
9
|
import { setSelectedNode } from './setSelectedNode';
|
|
10
10
|
import { onConnect } from './onConnect';
|
|
11
|
+
import { onReconnect } from './onReconnect';
|
|
11
12
|
import { setNodes } from './setNodes';
|
|
12
13
|
import { setEdges } from './setEdges';
|
|
13
14
|
import { setContentHeight } from './setContentHeight';
|
|
@@ -99,6 +100,9 @@ const createDiagramStore = ({
|
|
|
99
100
|
goToHistoryIndex(set, get as () => DiagramStore, index),
|
|
100
101
|
onConnect: (connection: Connection) =>
|
|
101
102
|
onConnect(set, onChange)(connection),
|
|
103
|
+
onReconnect: (oldEdge: Edge, newConnection: Connection) => {
|
|
104
|
+
onReconnect(set, onChange)(oldEdge, newConnection);
|
|
105
|
+
},
|
|
102
106
|
setNodes: (nodes: ICardNode[]) => {
|
|
103
107
|
setNodes(set)(nodes);
|
|
104
108
|
},
|
|
@@ -373,3 +377,16 @@ export const useEditFunction = (): ((func: FunctionSignature) => void) =>
|
|
|
373
377
|
|
|
374
378
|
export const useDeleteFunction = (): ((id: string) => void) =>
|
|
375
379
|
useDiagram((state) => state.deleteFunction);
|
|
380
|
+
|
|
381
|
+
// Undo/Redo hooks
|
|
382
|
+
export const useUndo = (): (() => void) =>
|
|
383
|
+
useDiagram((state) => state.undo);
|
|
384
|
+
|
|
385
|
+
export const useRedo = (): (() => void) =>
|
|
386
|
+
useDiagram((state) => state.redo);
|
|
387
|
+
|
|
388
|
+
export const useCanUndo = (): boolean =>
|
|
389
|
+
useDiagram((state) => state.historyIndex >= 0);
|
|
390
|
+
|
|
391
|
+
export const useCanRedo = (): boolean =>
|
|
392
|
+
useDiagram((state) => state.historyIndex < state.history.length - 1);
|
|
@@ -90,6 +90,7 @@ export interface DiagramActions {
|
|
|
90
90
|
onNodesChange: (changes: NodeChange[]) => void;
|
|
91
91
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
|
92
92
|
onConnect: (connection: Connection) => void;
|
|
93
|
+
onReconnect: (oldEdge: Edge, newConnection: Connection) => void;
|
|
93
94
|
setNodes: (nodes: ICardNode[]) => void;
|
|
94
95
|
setEdges: (edges: Edge[]) => void;
|
|
95
96
|
setContentHeight: (id: string, height: number) => void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// onReconnect.ts
|
|
2
|
+
import { Edge, Connection } from '@xyflow/react';
|
|
3
|
+
import { DiagramStore, OnChangeEventHandler } from './diagramStoreTypes';
|
|
4
|
+
|
|
5
|
+
export const onReconnect =
|
|
6
|
+
(
|
|
7
|
+
set: (fn: (state: DiagramStore) => DiagramStore) => void,
|
|
8
|
+
onChange?: OnChangeEventHandler,
|
|
9
|
+
) =>
|
|
10
|
+
(oldEdge: Edge, newConnection: Connection) => {
|
|
11
|
+
if (!newConnection.source || !newConnection.target) {
|
|
12
|
+
// If connection is invalid, remove the edge
|
|
13
|
+
set((state) => {
|
|
14
|
+
const updatedEdges = state.edges.filter((edge) => edge.id !== oldEdge.id);
|
|
15
|
+
return { ...state, edges: updatedEdges };
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (onChange) {
|
|
19
|
+
onChange({
|
|
20
|
+
t: 'edge-change',
|
|
21
|
+
type: 'remove',
|
|
22
|
+
id: oldEdge.id,
|
|
23
|
+
message: 'Edge disconnected',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
set((state) => {
|
|
30
|
+
// Manually update the edge with new connection
|
|
31
|
+
const updatedEdges = state.edges.map((edge) => {
|
|
32
|
+
if (edge.id === oldEdge.id) {
|
|
33
|
+
return {
|
|
34
|
+
...edge,
|
|
35
|
+
source: newConnection.source,
|
|
36
|
+
target: newConnection.target,
|
|
37
|
+
sourceHandle: newConnection.sourceHandle || edge.sourceHandle,
|
|
38
|
+
targetHandle: newConnection.targetHandle || edge.targetHandle,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return edge;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const reconnectedEdge = updatedEdges.find((edge) => edge.id === oldEdge.id);
|
|
45
|
+
|
|
46
|
+
if (onChange && reconnectedEdge) {
|
|
47
|
+
onChange({
|
|
48
|
+
t: 'edge-change',
|
|
49
|
+
type: 'replace',
|
|
50
|
+
id: reconnectedEdge.id,
|
|
51
|
+
item: reconnectedEdge,
|
|
52
|
+
message: 'Edge reconnected',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { ...state, edges: updatedEdges };
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
@@ -1,338 +1,548 @@
|
|
|
1
|
-
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
|
-
import { ConnectionMode, ReactFlow, ReactFlowProps } from '@xyflow/react';
|
|
3
|
-
import { useTriggerEvent } from '../utils/event-hooks';
|
|
4
|
-
import { IBlock, ICardNodeData } from '../types/card-node';
|
|
5
|
-
import {
|
|
6
|
-
DndContext,
|
|
7
|
-
DragOverlay,
|
|
8
|
-
PointerSensor,
|
|
9
|
-
useDroppable,
|
|
10
|
-
useSensor,
|
|
11
|
-
useSensors,
|
|
12
|
-
} from '@dnd-kit/core';
|
|
13
|
-
|
|
14
|
-
import { Box, Fab } from '@mui/material';
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
import
|
|
38
|
-
import
|
|
39
|
-
import
|
|
40
|
-
import
|
|
41
|
-
import
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
event.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
1
|
+
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
|
2
|
+
import { ConnectionMode, ReactFlow, ReactFlowProps, NodeChange, applyNodeChanges } from '@xyflow/react';
|
|
3
|
+
import { useTriggerEvent } from '../utils/event-hooks';
|
|
4
|
+
import { IBlock, ICardNodeData } from '../types/card-node';
|
|
5
|
+
import {
|
|
6
|
+
DndContext,
|
|
7
|
+
DragOverlay,
|
|
8
|
+
PointerSensor,
|
|
9
|
+
useDroppable,
|
|
10
|
+
useSensor,
|
|
11
|
+
useSensors,
|
|
12
|
+
} from '@dnd-kit/core';
|
|
13
|
+
|
|
14
|
+
import { Box, Fab, IconButton, Tooltip } from '@mui/material';
|
|
15
|
+
import UndoIcon from '@mui/icons-material/Undo';
|
|
16
|
+
import RedoIcon from '@mui/icons-material/Redo';
|
|
17
|
+
import { CardDataProvider } from '../contexts/CardDataProvider';
|
|
18
|
+
import BlockWrapper from '../molecules/BlockWrapper';
|
|
19
|
+
import useDragCallbacks from '../hooks/useDragCallbacks';
|
|
20
|
+
import { useElk } from '../utils/elkLayout';
|
|
21
|
+
import { useModalControls } from '../hooks/useModalControls';
|
|
22
|
+
import { useWorkflowNodeAction } from '../hooks/useWorkflowNodeActiont';
|
|
23
|
+
import {
|
|
24
|
+
useContentHeights,
|
|
25
|
+
useDiagram,
|
|
26
|
+
useDiagramEdges,
|
|
27
|
+
useDiagramNodes,
|
|
28
|
+
useDiagramType,
|
|
29
|
+
useLayoutDirection,
|
|
30
|
+
useSetLayoutDirection,
|
|
31
|
+
usePannable,
|
|
32
|
+
useUndo,
|
|
33
|
+
useRedo,
|
|
34
|
+
useCanUndo,
|
|
35
|
+
useCanRedo,
|
|
36
|
+
} from '../contexts/DiagramProvider';
|
|
37
|
+
import { useAutoRegisterFunctions } from '../hooks/useAutoRegisterFunctions';
|
|
38
|
+
import SequenceDiagram from './sequence/SequenceDiagram';
|
|
39
|
+
import ArchDiagram from './arch/ArchDiagram';
|
|
40
|
+
import StateMachineDiagram from './stateMachine/StateMachineDiagram';
|
|
41
|
+
import SystemFlowDiagram from './systemFlow/SystemFlowDiagram';
|
|
42
|
+
import CloudArchitectureDiagram from './cloud-arch/components/CloudArchitectureDiagram';
|
|
43
|
+
import UseCaseDiagram from './useCaseDiagram/UseCaseDiagram';
|
|
44
|
+
import CollaborationDiagram from './collaborationDiagram/CollaborationDiagram';
|
|
45
|
+
import Header from '../components/Header';
|
|
46
|
+
import { PaneMouseHandler } from '../utils/event-store';
|
|
47
|
+
import { AutomationExecutionPanel } from '../components/automation/AutomationExecutionPanel';
|
|
48
|
+
|
|
49
|
+
interface DiagramContentProps
|
|
50
|
+
extends Omit<ReactFlowProps, 'nodes' | 'edges' | 'onChange'> {
|
|
51
|
+
className?: string;
|
|
52
|
+
showAutomationExecutionPanel?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
56
|
+
className = '',
|
|
57
|
+
showAutomationExecutionPanel = true,
|
|
58
|
+
...props
|
|
59
|
+
}) => {
|
|
60
|
+
// Auto-register function nodes when they are created
|
|
61
|
+
useAutoRegisterFunctions();
|
|
62
|
+
|
|
63
|
+
const handleOnPaneClick = useTriggerEvent('onPaneClick');
|
|
64
|
+
|
|
65
|
+
// Use specific hooks where available
|
|
66
|
+
const nodes = useDiagramNodes();
|
|
67
|
+
const edges = useDiagramEdges();
|
|
68
|
+
const onConnect = useDiagram((state) => state.onConnect);
|
|
69
|
+
const onReconnect = useDiagram((state) => state.onReconnect);
|
|
70
|
+
const onEdgesChange = useDiagram((state) => state.onEdgesChange);
|
|
71
|
+
const onNodeDragEnd = useDiagram((state) => state.onNodeDragEnd);
|
|
72
|
+
const onNodeDragStart = useDiagram((state) => state.onNodeDragStart);
|
|
73
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
74
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
75
|
+
const diagramType = useDiagramType();
|
|
76
|
+
const contentHeights = useContentHeights();
|
|
77
|
+
const setShowEditEdge = useDiagram((state) => state.setSelectedEdge);
|
|
78
|
+
const selectedNode = useDiagram((state) => state.selectedNode);
|
|
79
|
+
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
80
|
+
const isPannable = usePannable();
|
|
81
|
+
|
|
82
|
+
// Undo/Redo hooks
|
|
83
|
+
const undo = useUndo();
|
|
84
|
+
const redo = useRedo();
|
|
85
|
+
const canUndo = useCanUndo();
|
|
86
|
+
const canRedo = useCanRedo();
|
|
87
|
+
|
|
88
|
+
// Handle keyboard shortcuts for undo/redo
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
91
|
+
// Don't trigger if user is typing in an input field
|
|
92
|
+
if (
|
|
93
|
+
event.target instanceof HTMLInputElement ||
|
|
94
|
+
event.target instanceof HTMLTextAreaElement ||
|
|
95
|
+
(event.target as any)?.isContentEditable
|
|
96
|
+
) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Ctrl+Z or Cmd+Z for Undo
|
|
101
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
if (canUndo) {
|
|
104
|
+
undo();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ctrl+Y or Cmd+Shift+Z for Redo
|
|
109
|
+
if (
|
|
110
|
+
((event.ctrlKey || event.metaKey) && event.key === 'y') ||
|
|
111
|
+
((event.ctrlKey || event.metaKey) && event.key === 'z' && event.shiftKey)
|
|
112
|
+
) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
if (canRedo) {
|
|
115
|
+
redo();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
121
|
+
return () => {
|
|
122
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
123
|
+
};
|
|
124
|
+
}, [undo, redo, canUndo, canRedo]);
|
|
125
|
+
|
|
126
|
+
// Filter out hidden nodes for ReactFlow rendering
|
|
127
|
+
const visibleNodes = useMemo(() => {
|
|
128
|
+
return nodes.filter(node => !node.data?.isHidden);
|
|
129
|
+
}, [nodes]);
|
|
130
|
+
|
|
131
|
+
// Filter out edges connected to hidden nodes
|
|
132
|
+
const visibleEdges = useMemo(() => {
|
|
133
|
+
const visibleNodeIds = new Set(visibleNodes.map(node => node.id));
|
|
134
|
+
return edges.filter(edge =>
|
|
135
|
+
visibleNodeIds.has(edge.source) && visibleNodeIds.has(edge.target)
|
|
136
|
+
);
|
|
137
|
+
}, [edges, visibleNodes]);
|
|
138
|
+
|
|
139
|
+
const { addNode } = useWorkflowNodeAction({
|
|
140
|
+
isReplacing: false,
|
|
141
|
+
variant: 'addParallelCol',
|
|
142
|
+
addInMiddle: false,
|
|
143
|
+
closeModal: () => setOpen(false),
|
|
144
|
+
direction: 'left',
|
|
145
|
+
});
|
|
146
|
+
const [activeItem, setActiveItem] = useState<IBlock | null>(null);
|
|
147
|
+
const [activeCard, setActiveCard] = useState<ICardNodeData | null>(null);
|
|
148
|
+
const { setNodeRef } = useDroppable({ id: 'main' });
|
|
149
|
+
const { open, onClick, setOpen } = useModalControls();
|
|
150
|
+
|
|
151
|
+
const closeModalsOnClickOutside: PaneMouseHandler = () => {
|
|
152
|
+
handleOnPaneClick();
|
|
153
|
+
setShowEditEdge(null);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const sensors = useSensors(
|
|
157
|
+
useSensor(PointerSensor, {
|
|
158
|
+
activationConstraint: {
|
|
159
|
+
delay: 250,
|
|
160
|
+
distance: 5,
|
|
161
|
+
},
|
|
162
|
+
onActivation({ event }) {
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const { getLayoutedElements } = useElk();
|
|
169
|
+
const layoutDirection = useLayoutDirection();
|
|
170
|
+
const setLayoutDirection = useSetLayoutDirection();
|
|
171
|
+
const layoutTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
172
|
+
|
|
173
|
+
// Set layout direction to RIGHT for automation diagrams
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (diagramType === 'automation' && layoutDirection !== 'RIGHT') {
|
|
176
|
+
setLayoutDirection('RIGHT');
|
|
177
|
+
}
|
|
178
|
+
}, [diagramType, layoutDirection, setLayoutDirection]);
|
|
179
|
+
|
|
180
|
+
// Debounce layout recalculation to prevent continuous propagation
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
// Clear any pending layout recalculation
|
|
183
|
+
if (layoutTimeoutRef.current) {
|
|
184
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Debounce the layout recalculation
|
|
188
|
+
layoutTimeoutRef.current = setTimeout(() => {
|
|
189
|
+
getLayoutedElements();
|
|
190
|
+
}, 300); // 300ms debounce delay
|
|
191
|
+
|
|
192
|
+
return () => {
|
|
193
|
+
if (layoutTimeoutRef.current) {
|
|
194
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}, [nodes.length, edges.length, contentHeights, diagramType, layoutDirection, getLayoutedElements]);
|
|
198
|
+
|
|
199
|
+
// Listen for layout direction changes
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const handleLayoutDirectionChange = () => {
|
|
202
|
+
getLayoutedElements();
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
window.addEventListener('layoutDirectionChanged', handleLayoutDirectionChange);
|
|
206
|
+
|
|
207
|
+
return () => {
|
|
208
|
+
window.removeEventListener('layoutDirectionChanged', handleLayoutDirectionChange);
|
|
209
|
+
};
|
|
210
|
+
}, [getLayoutedElements]);
|
|
211
|
+
|
|
212
|
+
const { onDragEnd, onDragStart, onDragCancel } = useDragCallbacks({
|
|
213
|
+
setActiveItem,
|
|
214
|
+
setActiveCard,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Handle keyboard deletion of selected nodes (only for automation diagram)
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
if (diagramType !== 'automation') return;
|
|
220
|
+
|
|
221
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
222
|
+
// Check if Delete or Backspace key is pressed
|
|
223
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
224
|
+
// Don't trigger deletion if user is typing in an input field
|
|
225
|
+
if (
|
|
226
|
+
event.target instanceof HTMLInputElement ||
|
|
227
|
+
event.target instanceof HTMLTextAreaElement ||
|
|
228
|
+
(event.target as any)?.isContentEditable
|
|
229
|
+
) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check if a node is selected (either from store or ReactFlow's selection)
|
|
234
|
+
const selectedNodeId = selectedNode || visibleNodes.find(node => node.selected)?.id;
|
|
235
|
+
|
|
236
|
+
if (selectedNodeId) {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
event.stopPropagation();
|
|
239
|
+
|
|
240
|
+
// Find the node to delete
|
|
241
|
+
const nodeToDelete = nodes.find((node) => node.id === selectedNodeId);
|
|
242
|
+
if (!nodeToDelete) return;
|
|
243
|
+
|
|
244
|
+
// Create a remove change for the node
|
|
245
|
+
const nodeChanges: NodeChange[] = [
|
|
246
|
+
{
|
|
247
|
+
id: selectedNodeId,
|
|
248
|
+
type: 'remove',
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
// Apply the changes through onNodesChange (which handles edge cleanup)
|
|
253
|
+
onNodesChange(nodeChanges);
|
|
254
|
+
|
|
255
|
+
// Clear the selected node
|
|
256
|
+
setSelectedNode(null);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Add event listener
|
|
262
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
263
|
+
|
|
264
|
+
// Cleanup on unmount
|
|
265
|
+
return () => {
|
|
266
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
267
|
+
};
|
|
268
|
+
}, [diagramType, selectedNode, nodes, visibleNodes, onNodesChange, setSelectedNode]);
|
|
269
|
+
|
|
270
|
+
const renderDynamicDiagram = () => {
|
|
271
|
+
if (diagramType === 'sequence') {
|
|
272
|
+
return (
|
|
273
|
+
<>
|
|
274
|
+
<SequenceDiagram sequenceNodes={visibleNodes} sequenceEdges={visibleEdges} />
|
|
275
|
+
</>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (diagramType === 'arch') {
|
|
280
|
+
return <ArchDiagram archNodes={visibleNodes as any} archEdges={visibleEdges as any} />;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (diagramType === 'stateMachine') {
|
|
284
|
+
return (
|
|
285
|
+
<>
|
|
286
|
+
<StateMachineDiagram stateNodes={visibleNodes} stateEdges={visibleEdges} />
|
|
287
|
+
</>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (diagramType === 'systemFlow') {
|
|
291
|
+
return (
|
|
292
|
+
<>
|
|
293
|
+
<SystemFlowDiagram systemFlowNodes={visibleNodes} systemFlowEdges={visibleEdges} />
|
|
294
|
+
</>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (diagramType === 'case') {
|
|
298
|
+
return (
|
|
299
|
+
<>
|
|
300
|
+
<UseCaseDiagram caseNodes={visibleNodes} caseEdges={visibleEdges} />
|
|
301
|
+
</>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (diagramType === 'cloud-architecture') {
|
|
305
|
+
return (
|
|
306
|
+
<>
|
|
307
|
+
<CloudArchitectureDiagram initialNodes={visibleNodes as any} initialEdges={visibleEdges as any} />
|
|
308
|
+
</>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (diagramType === 'collaboration') {
|
|
312
|
+
return (
|
|
313
|
+
<>
|
|
314
|
+
<CollaborationDiagram collabNodes={visibleNodes} collabEdges={visibleEdges} />
|
|
315
|
+
</>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (diagramType === 'automation') {
|
|
319
|
+
return (
|
|
320
|
+
<>
|
|
321
|
+
<Box
|
|
322
|
+
sx={{ display: 'flex', justifyContent: 'space-between' }}
|
|
323
|
+
className={className}
|
|
324
|
+
>
|
|
325
|
+
<Box
|
|
326
|
+
ref={setNodeRef}
|
|
327
|
+
sx={{ height: '100svh', width: '100svw', position: 'relative' }}
|
|
328
|
+
>
|
|
329
|
+
{/* Undo/Redo Toolbar */}
|
|
330
|
+
<Box
|
|
331
|
+
sx={{
|
|
332
|
+
position: 'absolute',
|
|
333
|
+
top: 16,
|
|
334
|
+
left: 16,
|
|
335
|
+
zIndex: 10,
|
|
336
|
+
display: 'flex',
|
|
337
|
+
gap: 0.5,
|
|
338
|
+
backgroundColor: 'background.paper',
|
|
339
|
+
borderRadius: 2,
|
|
340
|
+
boxShadow: 2,
|
|
341
|
+
padding: 0.5,
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
<Tooltip title="Undo (Ctrl+Z)" placement="bottom">
|
|
345
|
+
<span>
|
|
346
|
+
<IconButton
|
|
347
|
+
onClick={undo}
|
|
348
|
+
disabled={!canUndo}
|
|
349
|
+
size="small"
|
|
350
|
+
sx={{
|
|
351
|
+
color: canUndo ? 'primary.main' : 'action.disabled',
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<UndoIcon fontSize="small" />
|
|
355
|
+
</IconButton>
|
|
356
|
+
</span>
|
|
357
|
+
</Tooltip>
|
|
358
|
+
<Tooltip title="Redo (Ctrl+Y)" placement="bottom">
|
|
359
|
+
<span>
|
|
360
|
+
<IconButton
|
|
361
|
+
onClick={redo}
|
|
362
|
+
disabled={!canRedo}
|
|
363
|
+
size="small"
|
|
364
|
+
sx={{
|
|
365
|
+
color: canRedo ? 'primary.main' : 'action.disabled',
|
|
366
|
+
}}
|
|
367
|
+
>
|
|
368
|
+
<RedoIcon fontSize="small" />
|
|
369
|
+
</IconButton>
|
|
370
|
+
</span>
|
|
371
|
+
</Tooltip>
|
|
372
|
+
</Box>
|
|
373
|
+
<ReactFlow
|
|
374
|
+
panOnDrag={isPannable}
|
|
375
|
+
nodesDraggable={isPannable}
|
|
376
|
+
nodes={visibleNodes}
|
|
377
|
+
onNodesChange={onNodesChange}
|
|
378
|
+
edges={visibleEdges}
|
|
379
|
+
onEdgesChange={onEdgesChange}
|
|
380
|
+
onReconnect={onReconnect}
|
|
381
|
+
onNodeClick={(event, node) => {
|
|
382
|
+
event.stopPropagation();
|
|
383
|
+
setSelectedNode(node.id);
|
|
384
|
+
}}
|
|
385
|
+
onPaneClick={(event) => {
|
|
386
|
+
setSelectedNode(null);
|
|
387
|
+
closeModalsOnClickOutside(event);
|
|
388
|
+
}}
|
|
389
|
+
nodesConnectable={true}
|
|
390
|
+
nodesFocusable={true}
|
|
391
|
+
edgesFocusable={true}
|
|
392
|
+
elementsSelectable={true}
|
|
393
|
+
selectNodesOnDrag={false}
|
|
394
|
+
defaultEdgeOptions={{
|
|
395
|
+
reconnectable: true,
|
|
396
|
+
}}
|
|
397
|
+
fitView
|
|
398
|
+
fitViewOptions={{
|
|
399
|
+
padding: 0.2,
|
|
400
|
+
minZoom: 0.3,
|
|
401
|
+
maxZoom: 2,
|
|
402
|
+
includeHiddenNodes: false,
|
|
403
|
+
}}
|
|
404
|
+
minZoom={0.1}
|
|
405
|
+
maxZoom={2}
|
|
406
|
+
onNodeDragStart={onNodeDragStart}
|
|
407
|
+
onNodeDragStop={onNodeDragEnd}
|
|
408
|
+
onConnect={onConnect}
|
|
409
|
+
connectionMode={ConnectionMode.Strict}
|
|
410
|
+
suppressContentEditableWarning
|
|
411
|
+
suppressHydrationWarning
|
|
412
|
+
{...props}
|
|
413
|
+
/>
|
|
414
|
+
{props.children}
|
|
415
|
+
</Box>
|
|
416
|
+
</Box>
|
|
417
|
+
|
|
418
|
+
<DragOverlay>
|
|
419
|
+
{activeItem ? (
|
|
420
|
+
<CardDataProvider nodeId={activeItem?.id}>
|
|
421
|
+
<BlockWrapper data={activeItem} />
|
|
422
|
+
</CardDataProvider>
|
|
423
|
+
) : null}
|
|
424
|
+
</DragOverlay>
|
|
425
|
+
|
|
426
|
+
{showAutomationExecutionPanel && (
|
|
427
|
+
<AutomationExecutionPanel
|
|
428
|
+
nodes={visibleNodes as any}
|
|
429
|
+
edges={visibleEdges as any}
|
|
430
|
+
workflowId="automation-workflow"
|
|
431
|
+
onNodeUpdate={(nodeId, updatedData) => {
|
|
432
|
+
const updatedNodes = nodes.map(node =>
|
|
433
|
+
node.id === nodeId
|
|
434
|
+
? { ...node, data: updatedData }
|
|
435
|
+
: node
|
|
436
|
+
);
|
|
437
|
+
setNodes(updatedNodes);
|
|
438
|
+
}}
|
|
439
|
+
/>
|
|
440
|
+
)}
|
|
441
|
+
</>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return (
|
|
445
|
+
<>
|
|
446
|
+
<Box
|
|
447
|
+
sx={{ display: 'flex', justifyContent: 'space-between' }}
|
|
448
|
+
className={className}
|
|
449
|
+
>
|
|
450
|
+
<Box
|
|
451
|
+
ref={setNodeRef}
|
|
452
|
+
sx={{ height: '100svh', width: '100svw', position: 'relative' }}
|
|
453
|
+
>
|
|
454
|
+
{/* Undo/Redo Toolbar */}
|
|
455
|
+
<Box
|
|
456
|
+
sx={{
|
|
457
|
+
position: 'absolute',
|
|
458
|
+
top: 16,
|
|
459
|
+
left: 16,
|
|
460
|
+
zIndex: 10,
|
|
461
|
+
display: 'flex',
|
|
462
|
+
gap: 0.5,
|
|
463
|
+
backgroundColor: 'background.paper',
|
|
464
|
+
borderRadius: 2,
|
|
465
|
+
boxShadow: 2,
|
|
466
|
+
padding: 0.5,
|
|
467
|
+
}}
|
|
468
|
+
>
|
|
469
|
+
<Tooltip title="Undo (Ctrl+Z)" placement="bottom">
|
|
470
|
+
<span>
|
|
471
|
+
<IconButton
|
|
472
|
+
onClick={undo}
|
|
473
|
+
disabled={!canUndo}
|
|
474
|
+
size="small"
|
|
475
|
+
sx={{
|
|
476
|
+
color: canUndo ? 'primary.main' : 'action.disabled',
|
|
477
|
+
}}
|
|
478
|
+
>
|
|
479
|
+
<UndoIcon fontSize="small" />
|
|
480
|
+
</IconButton>
|
|
481
|
+
</span>
|
|
482
|
+
</Tooltip>
|
|
483
|
+
<Tooltip title="Redo (Ctrl+Y)" placement="bottom">
|
|
484
|
+
<span>
|
|
485
|
+
<IconButton
|
|
486
|
+
onClick={redo}
|
|
487
|
+
disabled={!canRedo}
|
|
488
|
+
size="small"
|
|
489
|
+
sx={{
|
|
490
|
+
color: canRedo ? 'primary.main' : 'action.disabled',
|
|
491
|
+
}}
|
|
492
|
+
>
|
|
493
|
+
<RedoIcon fontSize="small" />
|
|
494
|
+
</IconButton>
|
|
495
|
+
</span>
|
|
496
|
+
</Tooltip>
|
|
497
|
+
</Box>
|
|
498
|
+
<Fab
|
|
499
|
+
onClick={() => addNode('function')}
|
|
500
|
+
sx={{ position: 'fixed', top: 450, left: 0 }}
|
|
501
|
+
>
|
|
502
|
+
Add Function
|
|
503
|
+
</Fab>
|
|
504
|
+
<ReactFlow
|
|
505
|
+
panOnDrag={isPannable}
|
|
506
|
+
nodesDraggable={isPannable}
|
|
507
|
+
nodes={visibleNodes}
|
|
508
|
+
onNodesChange={onNodesChange}
|
|
509
|
+
edges={visibleEdges}
|
|
510
|
+
onEdgesChange={onEdgesChange}
|
|
511
|
+
fitView
|
|
512
|
+
onNodeDragStart={onNodeDragStart}
|
|
513
|
+
onNodeDragStop={onNodeDragEnd}
|
|
514
|
+
onConnect={onConnect}
|
|
515
|
+
connectionMode={ConnectionMode.Strict}
|
|
516
|
+
suppressContentEditableWarning
|
|
517
|
+
suppressHydrationWarning
|
|
518
|
+
onPaneClick={closeModalsOnClickOutside}
|
|
519
|
+
{...props}
|
|
520
|
+
/>
|
|
521
|
+
{props.children}
|
|
522
|
+
</Box>
|
|
523
|
+
</Box>
|
|
524
|
+
|
|
525
|
+
<DragOverlay>
|
|
526
|
+
{activeItem ? (
|
|
527
|
+
<CardDataProvider nodeId={activeItem?.id}>
|
|
528
|
+
<BlockWrapper data={activeItem} />
|
|
529
|
+
</CardDataProvider>
|
|
530
|
+
) : null}
|
|
531
|
+
</DragOverlay>
|
|
532
|
+
</>
|
|
533
|
+
);
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
return (
|
|
537
|
+
<DndContext
|
|
538
|
+
onDragEnd={onDragEnd}
|
|
539
|
+
onDragStart={onDragStart}
|
|
540
|
+
onDragCancel={onDragCancel}
|
|
541
|
+
sensors={sensors}
|
|
542
|
+
>
|
|
543
|
+
{renderDynamicDiagram()}
|
|
544
|
+
</DndContext>
|
|
545
|
+
);
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
export default DiagramContent;
|