@flowuent-org/diagramming-core 1.0.8 → 1.1.1
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/apps/diagramming/src/AutomationDiagramData.ts +22 -0
- package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
- package/apps/diagramming/src/main.tsx +463 -463
- package/apps/diagramming/src/node-data.ts +664 -664
- package/apps/diagramming/src/stencil-items.ts +31 -31
- package/apps/diagramming/src/vite-env.d.ts +1 -1
- package/package.json +1 -1
- package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
- package/packages/diagrams/README.md +7 -463
- package/packages/diagrams/UNDO_REDO_API.md +306 -306
- package/packages/diagrams/package.json +27 -27
- package/packages/diagrams/project.json +42 -42
- package/packages/diagrams/rollup.config.js +26 -26
- package/packages/diagrams/src/DiagramFlow.tsx +7 -7
- package/packages/diagrams/src/index.ts +116 -116
- package/packages/diagrams/src/index.ts.bak +99 -99
- package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
- package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
- package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
- package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -0
- package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +227 -0
- package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +178 -115
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +133 -27
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +134 -28
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +132 -27
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +124 -17
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +122 -15
- package/packages/diagrams/src/lib/components/automation/index.ts +3 -0
- package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
- package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
- package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
- package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
- package/packages/diagrams/src/lib/types/card-node.ts +68 -68
- package/packages/diagrams/src/lib/types/node-types.ts +29 -29
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +1179 -1179
- package/packages/diagrams/tsconfig.lib.json +25 -25
- package/tsconfig.base.json +29 -30
- package/TRANSLATION_FIX_SUMMARY.md +0 -118
- package/packages/diagrams/I18N_SETUP.md +0 -126
|
@@ -18,13 +18,16 @@ import {
|
|
|
18
18
|
Error as ErrorIcon,
|
|
19
19
|
ArrowForwardIos as ArrowForwardIcon,
|
|
20
20
|
WhatsApp as WhatsAppIcon,
|
|
21
|
-
Phone as PhoneIcon
|
|
21
|
+
Phone as PhoneIcon,
|
|
22
|
+
Lightbulb as LightbulbIcon
|
|
22
23
|
} from '@mui/icons-material';
|
|
23
24
|
import { RiCloseLine } from 'react-icons/ri';
|
|
24
25
|
import ReactJson from 'react-json-view';
|
|
25
26
|
import { getIconByName } from '../../utils/iconMapper';
|
|
26
27
|
import { useTranslation } from 'react-i18next';
|
|
27
28
|
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
29
|
+
import { AISuggestion } from './AISuggestionsModal';
|
|
30
|
+
import { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
28
31
|
|
|
29
32
|
interface AutomationSheetsNodeProps {
|
|
30
33
|
data: {
|
|
@@ -83,7 +86,10 @@ interface AutomationSheetsNodeProps {
|
|
|
83
86
|
};
|
|
84
87
|
};
|
|
85
88
|
};
|
|
86
|
-
formData?:
|
|
89
|
+
formData?: {
|
|
90
|
+
aiSuggestionsCount?: number; // Number of AI suggestions available
|
|
91
|
+
[key: string]: any;
|
|
92
|
+
};
|
|
87
93
|
// Optional per-output-method statuses supplied by engine
|
|
88
94
|
outputStatuses?: {
|
|
89
95
|
googleSheets?: 'not-set' | 'configured' | 'running' | 'connected' | 'failed';
|
|
@@ -97,6 +103,7 @@ interface AutomationSheetsNodeProps {
|
|
|
97
103
|
export const AutomationSheetsNode: React.FC<AutomationSheetsNodeProps> = ({ data, selected }) => {
|
|
98
104
|
const { t } = useTranslation();
|
|
99
105
|
const [isJsonOpen, setIsJsonOpen] = useState(false);
|
|
106
|
+
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
100
107
|
const rootRef = useRef<any>(null);
|
|
101
108
|
const portalRef = useRef<HTMLDivElement | null>(null);
|
|
102
109
|
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -694,23 +701,30 @@ Data: ${JSON.stringify(data, null, 2)}
|
|
|
694
701
|
|
|
695
702
|
return (
|
|
696
703
|
<Box
|
|
697
|
-
ref={nodeRef}
|
|
698
704
|
sx={{
|
|
699
|
-
width: '380px',
|
|
700
|
-
minHeight: '280px',
|
|
701
|
-
backgroundColor: '#181C25', // Darker background like in image
|
|
702
|
-
border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
|
|
703
|
-
borderRadius: '12px',
|
|
704
|
-
padding: '0',
|
|
705
|
-
color: '#ffffff',
|
|
706
705
|
position: 'relative',
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
cursor: 'pointer',
|
|
710
|
-
overflow: 'hidden',
|
|
706
|
+
width: '380px',
|
|
707
|
+
overflow: 'visible',
|
|
711
708
|
}}
|
|
712
|
-
onClick={handleJsonClick}
|
|
713
709
|
>
|
|
710
|
+
<Box
|
|
711
|
+
ref={nodeRef}
|
|
712
|
+
sx={{
|
|
713
|
+
width: '380px',
|
|
714
|
+
minHeight: '280px',
|
|
715
|
+
backgroundColor: '#181C25', // Darker background like in image
|
|
716
|
+
border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
|
|
717
|
+
borderRadius: '12px',
|
|
718
|
+
padding: '0',
|
|
719
|
+
color: '#ffffff',
|
|
720
|
+
position: 'relative',
|
|
721
|
+
boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
722
|
+
transition: 'all 0.2s ease',
|
|
723
|
+
cursor: 'pointer',
|
|
724
|
+
overflow: 'hidden',
|
|
725
|
+
}}
|
|
726
|
+
onClick={handleJsonClick}
|
|
727
|
+
>
|
|
714
728
|
{/* Header */}
|
|
715
729
|
<Box sx={{
|
|
716
730
|
display: 'flex',
|
|
@@ -825,6 +839,99 @@ Data: ${JSON.stringify(data, null, 2)}
|
|
|
825
839
|
opacity: 0, // Hidden but functional
|
|
826
840
|
}}
|
|
827
841
|
/>
|
|
842
|
+
</Box>
|
|
843
|
+
|
|
844
|
+
{/* AI Suggestions Button - Positioned below the node box */}
|
|
845
|
+
{data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
|
|
846
|
+
<Box
|
|
847
|
+
sx={{
|
|
848
|
+
position: 'absolute',
|
|
849
|
+
top: '100%',
|
|
850
|
+
left: '50%',
|
|
851
|
+
transform: 'translateX(-50%)',
|
|
852
|
+
marginTop: '12px',
|
|
853
|
+
zIndex: 10,
|
|
854
|
+
whiteSpace: 'nowrap',
|
|
855
|
+
}}
|
|
856
|
+
onClick={(e) => {
|
|
857
|
+
e.stopPropagation();
|
|
858
|
+
// Toggle AI Suggestions panel
|
|
859
|
+
setShowSuggestions(!showSuggestions);
|
|
860
|
+
}}
|
|
861
|
+
>
|
|
862
|
+
<Button
|
|
863
|
+
variant="contained"
|
|
864
|
+
startIcon={<LightbulbIcon sx={{ fontSize: '12px' }} />}
|
|
865
|
+
sx={{
|
|
866
|
+
backgroundColor: '#2563EB',
|
|
867
|
+
color: '#ffffff',
|
|
868
|
+
borderRadius: '20px',
|
|
869
|
+
textTransform: 'none',
|
|
870
|
+
fontSize: '10px',
|
|
871
|
+
fontWeight: 400,
|
|
872
|
+
padding: '8px 16px',
|
|
873
|
+
whiteSpace: 'nowrap',
|
|
874
|
+
display: 'inline-flex',
|
|
875
|
+
alignItems: 'center',
|
|
876
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
|
877
|
+
'&:hover': {
|
|
878
|
+
backgroundColor: '#2563eb',
|
|
879
|
+
},
|
|
880
|
+
'& .MuiButton-startIcon': {
|
|
881
|
+
marginRight: '8px',
|
|
882
|
+
}
|
|
883
|
+
}}
|
|
884
|
+
>
|
|
885
|
+
AI Suggestions
|
|
886
|
+
<Box
|
|
887
|
+
component="span"
|
|
888
|
+
sx={{
|
|
889
|
+
marginLeft: '8px',
|
|
890
|
+
backgroundColor: '#FFFFFF26',
|
|
891
|
+
color: '#ffffff',
|
|
892
|
+
fontSize: '10px',
|
|
893
|
+
fontWeight: 400,
|
|
894
|
+
minWidth: '18px',
|
|
895
|
+
height: '18px',
|
|
896
|
+
borderRadius: '9px',
|
|
897
|
+
display: 'inline-flex',
|
|
898
|
+
alignItems: 'center',
|
|
899
|
+
justifyContent: 'center',
|
|
900
|
+
padding: '0 6px',
|
|
901
|
+
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
902
|
+
}}
|
|
903
|
+
>
|
|
904
|
+
{data.formData.aiSuggestionsCount}
|
|
905
|
+
</Box>
|
|
906
|
+
</Button>
|
|
907
|
+
</Box>
|
|
908
|
+
)}
|
|
909
|
+
|
|
910
|
+
{/* AI Suggestions Panel - Rendered on canvas below the button */}
|
|
911
|
+
{showSuggestions && data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && nodeId && (
|
|
912
|
+
<AISuggestionsPanel
|
|
913
|
+
suggestions={data.formData?.aiSuggestions || [
|
|
914
|
+
{
|
|
915
|
+
id: '1',
|
|
916
|
+
title: 'Add Citation Extraction',
|
|
917
|
+
description: 'Automatically extract and format citations from article content.',
|
|
918
|
+
tags: ['classification', 'enhancement'],
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
id: '2',
|
|
922
|
+
title: 'Generate Bullet Summary',
|
|
923
|
+
description: 'Create a concise bullet-point summary of the article\'s main points.',
|
|
924
|
+
tags: ['classification', 'enhancement'],
|
|
925
|
+
},
|
|
926
|
+
]}
|
|
927
|
+
parentNodeId={nodeId}
|
|
928
|
+
onSuggestionClick={(suggestion) => {
|
|
929
|
+
console.log('Suggestion clicked:', suggestion);
|
|
930
|
+
// Handle suggestion selection here
|
|
931
|
+
}}
|
|
932
|
+
onClose={() => setShowSuggestions(false)}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
828
935
|
</Box>
|
|
829
936
|
);
|
|
830
937
|
};
|
|
@@ -6,3 +6,6 @@ export { AutomationEndNode } from './AutomationEndNode';
|
|
|
6
6
|
export { AutomationNoteNode } from './AutomationNoteNode';
|
|
7
7
|
export { AutomationExecutionPanel } from './AutomationExecutionPanel';
|
|
8
8
|
export { AutomationAISuggestionNode } from './AutomationAISuggestionNode';
|
|
9
|
+
export { AISuggestionsModal, showAISuggestionsModal } from './AISuggestionsModal';
|
|
10
|
+
export { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
11
|
+
export type { AISuggestion } from './AISuggestionsModal';
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { DiagramState } from './diagramStoreTypes';
|
|
2
|
-
import { ICardNode } from '@flowuent-labs/diagrams';
|
|
3
|
-
|
|
4
|
-
// Helper function to handle node deletion and edge updates
|
|
5
|
-
export const onWorkflowNodeDelete = (
|
|
6
|
-
node: ICardNode, // The node to be deleted
|
|
7
|
-
draft: DiagramState, // The current diagram state (edges and nodes)
|
|
8
|
-
) => {
|
|
9
|
-
const { edges, nodes: allNodes } = draft;
|
|
10
|
-
|
|
11
|
-
if (node.data.branchRoot) {
|
|
12
|
-
// Find all nodes with the same branchRoot value
|
|
13
|
-
const nodesToDelete = allNodes.filter(
|
|
14
|
-
(n) => n.data.branchRoot === node.data.branchRoot,
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
// Remove the edges connected to these nodes
|
|
18
|
-
const nodeIdsToDelete = new Set(nodesToDelete.map((n) => n.id));
|
|
19
|
-
console.log('nodeIdsToDelete', nodeIdsToDelete);
|
|
20
|
-
// Get the previous node of the root and the next node of the final node
|
|
21
|
-
const branchRootNode = allNodes.find((n) => n.id === node.data.branchRoot);
|
|
22
|
-
const finalNode = allNodes.find((n) => n.id === node.data.finalNodeId);
|
|
23
|
-
|
|
24
|
-
if (branchRootNode && finalNode) {
|
|
25
|
-
const previousEdge = edges.find(
|
|
26
|
-
(edge) => edge.target === branchRootNode.id,
|
|
27
|
-
);
|
|
28
|
-
const nextEdge = edges.find((edge) => edge.source === finalNode.id);
|
|
29
|
-
|
|
30
|
-
// Find the next node (if exists)
|
|
31
|
-
const nextNode =
|
|
32
|
-
nextEdge && allNodes.find((n) => n.id === nextEdge.target);
|
|
33
|
-
// If there's a previous edge and a next node, update the previous edge's target to the next node's id
|
|
34
|
-
if (previousEdge && nextNode) {
|
|
35
|
-
previousEdge.target = nextNode.id;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Remove the nodes that are part of the branch
|
|
40
|
-
draft.nodes = allNodes.filter((n) => !nodeIdsToDelete.has(n.id));
|
|
41
|
-
} else {
|
|
42
|
-
// Find the previous edge and next edge
|
|
43
|
-
const previousEdge = edges.find((edge) => edge.target === node.id);
|
|
44
|
-
const nextEdge = edges.find((edge) => edge.source === node.id);
|
|
45
|
-
|
|
46
|
-
// Find the next node (if exists)
|
|
47
|
-
const nextNode = nextEdge && allNodes.find((n) => n.id === nextEdge.target);
|
|
48
|
-
|
|
49
|
-
// If there's a previous edge and a next node, update the previous edge's target to the next node's id
|
|
50
|
-
if (previousEdge && nextNode) {
|
|
51
|
-
previousEdge.target = nextNode.id;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// If there's a next edge, remove it
|
|
55
|
-
if (nextEdge) {
|
|
56
|
-
const nextEdgeIndex = edges.findIndex((edge) => edge.id === nextEdge.id);
|
|
57
|
-
if (nextEdgeIndex !== -1) {
|
|
58
|
-
edges.splice(nextEdgeIndex, 1); // Remove the next edge from the edges array
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Remove the node from the nodes array
|
|
63
|
-
draft.nodes = allNodes.filter((n) => n.id !== node.id);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
1
|
+
import { DiagramState } from './diagramStoreTypes';
|
|
2
|
+
import { ICardNode } from '@flowuent-labs/diagrams';
|
|
3
|
+
|
|
4
|
+
// Helper function to handle node deletion and edge updates
|
|
5
|
+
export const onWorkflowNodeDelete = (
|
|
6
|
+
node: ICardNode, // The node to be deleted
|
|
7
|
+
draft: DiagramState, // The current diagram state (edges and nodes)
|
|
8
|
+
) => {
|
|
9
|
+
const { edges, nodes: allNodes } = draft;
|
|
10
|
+
|
|
11
|
+
if (node.data.branchRoot) {
|
|
12
|
+
// Find all nodes with the same branchRoot value
|
|
13
|
+
const nodesToDelete = allNodes.filter(
|
|
14
|
+
(n) => n.data.branchRoot === node.data.branchRoot,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Remove the edges connected to these nodes
|
|
18
|
+
const nodeIdsToDelete = new Set(nodesToDelete.map((n) => n.id));
|
|
19
|
+
console.log('nodeIdsToDelete', nodeIdsToDelete);
|
|
20
|
+
// Get the previous node of the root and the next node of the final node
|
|
21
|
+
const branchRootNode = allNodes.find((n) => n.id === node.data.branchRoot);
|
|
22
|
+
const finalNode = allNodes.find((n) => n.id === node.data.finalNodeId);
|
|
23
|
+
|
|
24
|
+
if (branchRootNode && finalNode) {
|
|
25
|
+
const previousEdge = edges.find(
|
|
26
|
+
(edge) => edge.target === branchRootNode.id,
|
|
27
|
+
);
|
|
28
|
+
const nextEdge = edges.find((edge) => edge.source === finalNode.id);
|
|
29
|
+
|
|
30
|
+
// Find the next node (if exists)
|
|
31
|
+
const nextNode =
|
|
32
|
+
nextEdge && allNodes.find((n) => n.id === nextEdge.target);
|
|
33
|
+
// If there's a previous edge and a next node, update the previous edge's target to the next node's id
|
|
34
|
+
if (previousEdge && nextNode) {
|
|
35
|
+
previousEdge.target = nextNode.id;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Remove the nodes that are part of the branch
|
|
40
|
+
draft.nodes = allNodes.filter((n) => !nodeIdsToDelete.has(n.id));
|
|
41
|
+
} else {
|
|
42
|
+
// Find the previous edge and next edge
|
|
43
|
+
const previousEdge = edges.find((edge) => edge.target === node.id);
|
|
44
|
+
const nextEdge = edges.find((edge) => edge.source === node.id);
|
|
45
|
+
|
|
46
|
+
// Find the next node (if exists)
|
|
47
|
+
const nextNode = nextEdge && allNodes.find((n) => n.id === nextEdge.target);
|
|
48
|
+
|
|
49
|
+
// If there's a previous edge and a next node, update the previous edge's target to the next node's id
|
|
50
|
+
if (previousEdge && nextNode) {
|
|
51
|
+
previousEdge.target = nextNode.id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If there's a next edge, remove it
|
|
55
|
+
if (nextEdge) {
|
|
56
|
+
const nextEdgeIndex = edges.findIndex((edge) => edge.id === nextEdge.id);
|
|
57
|
+
if (nextEdgeIndex !== -1) {
|
|
58
|
+
edges.splice(nextEdgeIndex, 1); // Remove the next edge from the edges array
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Remove the node from the nodes array
|
|
63
|
+
draft.nodes = allNodes.filter((n) => n.id !== node.id);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import { Edge } from '@xyflow/react';
|
|
2
|
-
import {
|
|
3
|
-
closestPoint,
|
|
4
|
-
DiagramStore,
|
|
5
|
-
EdgeTypes,
|
|
6
|
-
HistoryEvent,
|
|
7
|
-
ICardNode,
|
|
8
|
-
useCustomReactFlow,
|
|
9
|
-
useDiagram,
|
|
10
|
-
} from '@flowuent-labs/diagrams';
|
|
11
|
-
import React, { useCallback } from 'react';
|
|
12
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
-
import addToHistory from '../../utils/addToHistory';
|
|
14
|
-
|
|
15
|
-
export const useCreateBendPoint = (
|
|
16
|
-
currentEdge: Edge,
|
|
17
|
-
sourceNode: ICardNode,
|
|
18
|
-
targetNode: ICardNode,
|
|
19
|
-
pathRef: React.RefObject<SVGPathElement>,
|
|
20
|
-
) => {
|
|
21
|
-
const { setEdges, getEdge, screenToFlowPosition, addNodes } =
|
|
22
|
-
useCustomReactFlow();
|
|
23
|
-
const store = useDiagram<DiagramStore>();
|
|
24
|
-
const { diagramType, nodes, edges, setStore } = store;
|
|
25
|
-
|
|
26
|
-
return useCallback(
|
|
27
|
-
(evt: React.MouseEvent<SVGPathElement, MouseEvent>) => {
|
|
28
|
-
if (diagramType === 'workflow') {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
evt.preventDefault();
|
|
32
|
-
evt.stopPropagation();
|
|
33
|
-
if (!pathRef.current) return;
|
|
34
|
-
|
|
35
|
-
const position = screenToFlowPosition({
|
|
36
|
-
x: evt.clientX,
|
|
37
|
-
y: evt.clientY,
|
|
38
|
-
});
|
|
39
|
-
const cpoint = closestPoint(pathRef.current, [position.x, position.y]);
|
|
40
|
-
if (!currentEdge || !sourceNode || !targetNode)
|
|
41
|
-
throw new Error(
|
|
42
|
-
'Invalid Edge found with source, target or curr edge missing',
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const nodesAndEdgesBefore = {
|
|
46
|
-
nodes: [...nodes],
|
|
47
|
-
edges: [...edges],
|
|
48
|
-
};
|
|
49
|
-
const nodeId = uuidv4();
|
|
50
|
-
const newBendPoint: ICardNode = {
|
|
51
|
-
id: nodeId,
|
|
52
|
-
position: { x: cpoint[0], y: cpoint[1] },
|
|
53
|
-
data: { nodeId: nodeId },
|
|
54
|
-
type: 'bendPoint',
|
|
55
|
-
};
|
|
56
|
-
const newEdge: Edge = {
|
|
57
|
-
id: uuidv4(),
|
|
58
|
-
source: newBendPoint.id,
|
|
59
|
-
target: currentEdge.target,
|
|
60
|
-
type:
|
|
61
|
-
currentEdge.type === EdgeTypes.Default ||
|
|
62
|
-
currentEdge.type === EdgeTypes.Bend2Target
|
|
63
|
-
? EdgeTypes.Bend2Target
|
|
64
|
-
: EdgeTypes.Bend2Bend,
|
|
65
|
-
};
|
|
66
|
-
newEdge.markerEnd =
|
|
67
|
-
newEdge.type === EdgeTypes.Bend2Target
|
|
68
|
-
? currentEdge.markerEnd
|
|
69
|
-
: undefined;
|
|
70
|
-
currentEdge.target = newBendPoint.id;
|
|
71
|
-
currentEdge.type =
|
|
72
|
-
currentEdge.type === EdgeTypes.Default ||
|
|
73
|
-
currentEdge.type === EdgeTypes.Source2Bend
|
|
74
|
-
? EdgeTypes.Source2Bend
|
|
75
|
-
: EdgeTypes.Bend2Bend;
|
|
76
|
-
delete currentEdge.markerEnd;
|
|
77
|
-
currentEdge.markerStart =
|
|
78
|
-
currentEdge.type === EdgeTypes.Source2Bend
|
|
79
|
-
? currentEdge.markerStart
|
|
80
|
-
: undefined;
|
|
81
|
-
|
|
82
|
-
const newEdges = [
|
|
83
|
-
...edges.filter((e) => e.id !== currentEdge.id),
|
|
84
|
-
currentEdge,
|
|
85
|
-
newEdge,
|
|
86
|
-
];
|
|
87
|
-
const newNodes = [...nodes, newBendPoint];
|
|
88
|
-
const nodesAndEdgesAfter = {
|
|
89
|
-
nodes: newNodes,
|
|
90
|
-
edges: newEdges,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const event: HistoryEvent = {
|
|
94
|
-
forward: {
|
|
95
|
-
t: 'complex-change',
|
|
96
|
-
type: 'complex',
|
|
97
|
-
nodesAndEdges: nodesAndEdgesAfter,
|
|
98
|
-
},
|
|
99
|
-
backward: {
|
|
100
|
-
t: 'complex-change',
|
|
101
|
-
type: 'complex',
|
|
102
|
-
nodesAndEdges: nodesAndEdgesBefore,
|
|
103
|
-
},
|
|
104
|
-
title: 'Bend point created in the middle of ' + currentEdge.id,
|
|
105
|
-
};
|
|
106
|
-
setStore({
|
|
107
|
-
...addToHistory(store, [event]),
|
|
108
|
-
...nodesAndEdgesAfter,
|
|
109
|
-
});
|
|
110
|
-
},
|
|
111
|
-
[
|
|
112
|
-
pathRef.current,
|
|
113
|
-
currentEdge.id,
|
|
114
|
-
getEdge,
|
|
115
|
-
setEdges,
|
|
116
|
-
addNodes,
|
|
117
|
-
sourceNode,
|
|
118
|
-
targetNode,
|
|
119
|
-
],
|
|
120
|
-
);
|
|
121
|
-
};
|
|
1
|
+
import { Edge } from '@xyflow/react';
|
|
2
|
+
import {
|
|
3
|
+
closestPoint,
|
|
4
|
+
DiagramStore,
|
|
5
|
+
EdgeTypes,
|
|
6
|
+
HistoryEvent,
|
|
7
|
+
ICardNode,
|
|
8
|
+
useCustomReactFlow,
|
|
9
|
+
useDiagram,
|
|
10
|
+
} from '@flowuent-labs/diagrams';
|
|
11
|
+
import React, { useCallback } from 'react';
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
import addToHistory from '../../utils/addToHistory';
|
|
14
|
+
|
|
15
|
+
export const useCreateBendPoint = (
|
|
16
|
+
currentEdge: Edge,
|
|
17
|
+
sourceNode: ICardNode,
|
|
18
|
+
targetNode: ICardNode,
|
|
19
|
+
pathRef: React.RefObject<SVGPathElement>,
|
|
20
|
+
) => {
|
|
21
|
+
const { setEdges, getEdge, screenToFlowPosition, addNodes } =
|
|
22
|
+
useCustomReactFlow();
|
|
23
|
+
const store = useDiagram<DiagramStore>();
|
|
24
|
+
const { diagramType, nodes, edges, setStore } = store;
|
|
25
|
+
|
|
26
|
+
return useCallback(
|
|
27
|
+
(evt: React.MouseEvent<SVGPathElement, MouseEvent>) => {
|
|
28
|
+
if (diagramType === 'workflow') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
evt.preventDefault();
|
|
32
|
+
evt.stopPropagation();
|
|
33
|
+
if (!pathRef.current) return;
|
|
34
|
+
|
|
35
|
+
const position = screenToFlowPosition({
|
|
36
|
+
x: evt.clientX,
|
|
37
|
+
y: evt.clientY,
|
|
38
|
+
});
|
|
39
|
+
const cpoint = closestPoint(pathRef.current, [position.x, position.y]);
|
|
40
|
+
if (!currentEdge || !sourceNode || !targetNode)
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Invalid Edge found with source, target or curr edge missing',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const nodesAndEdgesBefore = {
|
|
46
|
+
nodes: [...nodes],
|
|
47
|
+
edges: [...edges],
|
|
48
|
+
};
|
|
49
|
+
const nodeId = uuidv4();
|
|
50
|
+
const newBendPoint: ICardNode = {
|
|
51
|
+
id: nodeId,
|
|
52
|
+
position: { x: cpoint[0], y: cpoint[1] },
|
|
53
|
+
data: { nodeId: nodeId },
|
|
54
|
+
type: 'bendPoint',
|
|
55
|
+
};
|
|
56
|
+
const newEdge: Edge = {
|
|
57
|
+
id: uuidv4(),
|
|
58
|
+
source: newBendPoint.id,
|
|
59
|
+
target: currentEdge.target,
|
|
60
|
+
type:
|
|
61
|
+
currentEdge.type === EdgeTypes.Default ||
|
|
62
|
+
currentEdge.type === EdgeTypes.Bend2Target
|
|
63
|
+
? EdgeTypes.Bend2Target
|
|
64
|
+
: EdgeTypes.Bend2Bend,
|
|
65
|
+
};
|
|
66
|
+
newEdge.markerEnd =
|
|
67
|
+
newEdge.type === EdgeTypes.Bend2Target
|
|
68
|
+
? currentEdge.markerEnd
|
|
69
|
+
: undefined;
|
|
70
|
+
currentEdge.target = newBendPoint.id;
|
|
71
|
+
currentEdge.type =
|
|
72
|
+
currentEdge.type === EdgeTypes.Default ||
|
|
73
|
+
currentEdge.type === EdgeTypes.Source2Bend
|
|
74
|
+
? EdgeTypes.Source2Bend
|
|
75
|
+
: EdgeTypes.Bend2Bend;
|
|
76
|
+
delete currentEdge.markerEnd;
|
|
77
|
+
currentEdge.markerStart =
|
|
78
|
+
currentEdge.type === EdgeTypes.Source2Bend
|
|
79
|
+
? currentEdge.markerStart
|
|
80
|
+
: undefined;
|
|
81
|
+
|
|
82
|
+
const newEdges = [
|
|
83
|
+
...edges.filter((e) => e.id !== currentEdge.id),
|
|
84
|
+
currentEdge,
|
|
85
|
+
newEdge,
|
|
86
|
+
];
|
|
87
|
+
const newNodes = [...nodes, newBendPoint];
|
|
88
|
+
const nodesAndEdgesAfter = {
|
|
89
|
+
nodes: newNodes,
|
|
90
|
+
edges: newEdges,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const event: HistoryEvent = {
|
|
94
|
+
forward: {
|
|
95
|
+
t: 'complex-change',
|
|
96
|
+
type: 'complex',
|
|
97
|
+
nodesAndEdges: nodesAndEdgesAfter,
|
|
98
|
+
},
|
|
99
|
+
backward: {
|
|
100
|
+
t: 'complex-change',
|
|
101
|
+
type: 'complex',
|
|
102
|
+
nodesAndEdges: nodesAndEdgesBefore,
|
|
103
|
+
},
|
|
104
|
+
title: 'Bend point created in the middle of ' + currentEdge.id,
|
|
105
|
+
};
|
|
106
|
+
setStore({
|
|
107
|
+
...addToHistory(store, [event]),
|
|
108
|
+
...nodesAndEdgesAfter,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
[
|
|
112
|
+
pathRef.current,
|
|
113
|
+
currentEdge.id,
|
|
114
|
+
getEdge,
|
|
115
|
+
setEdges,
|
|
116
|
+
addNodes,
|
|
117
|
+
sourceNode,
|
|
118
|
+
targetNode,
|
|
119
|
+
],
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Button } from '@mui/material';
|
|
3
|
-
import { WorkflowNodeActionButtons } from '../../molecules/WorkflowNodeActionButtons';
|
|
4
|
-
import { ICardNode } from '../../types/card-node';
|
|
5
|
-
import { useNodeType } from '@flowuent-labs/diagrams';
|
|
6
|
-
|
|
7
|
-
interface NodeActionButtonsProps {
|
|
8
|
-
addButtonVisible: boolean;
|
|
9
|
-
addNewCaseNode: () => void;
|
|
10
|
-
nextNodes: ICardNode[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const NodeActionButtons = React.memo(
|
|
14
|
-
({ addButtonVisible, addNewCaseNode, nextNodes }: NodeActionButtonsProps) => {
|
|
15
|
-
const type = useNodeType();
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<>
|
|
19
|
-
{addButtonVisible && (
|
|
20
|
-
<WorkflowNodeActionButtons
|
|
21
|
-
direction={'bottom'}
|
|
22
|
-
variant={'addParallelCol'}
|
|
23
|
-
/>
|
|
24
|
-
)}
|
|
25
|
-
|
|
26
|
-
{type === 'switch' && (
|
|
27
|
-
<Button onClick={addNewCaseNode}>
|
|
28
|
-
Add new case
|
|
29
|
-
</Button>
|
|
30
|
-
)}
|
|
31
|
-
|
|
32
|
-
{nextNodes &&
|
|
33
|
-
(nextNodes.length < 2 || type === 'emptyNode' ? (
|
|
34
|
-
<WorkflowNodeActionButtons
|
|
35
|
-
addInMiddle={nextNodes.length === 1}
|
|
36
|
-
direction={'bottom'}
|
|
37
|
-
isReplacing={type === 'emptyNode'}
|
|
38
|
-
/>
|
|
39
|
-
) : null)}
|
|
40
|
-
</>
|
|
41
|
-
);
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
export default NodeActionButtons;
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button } from '@mui/material';
|
|
3
|
+
import { WorkflowNodeActionButtons } from '../../molecules/WorkflowNodeActionButtons';
|
|
4
|
+
import { ICardNode } from '../../types/card-node';
|
|
5
|
+
import { useNodeType } from '@flowuent-labs/diagrams';
|
|
6
|
+
|
|
7
|
+
interface NodeActionButtonsProps {
|
|
8
|
+
addButtonVisible: boolean;
|
|
9
|
+
addNewCaseNode: () => void;
|
|
10
|
+
nextNodes: ICardNode[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NodeActionButtons = React.memo(
|
|
14
|
+
({ addButtonVisible, addNewCaseNode, nextNodes }: NodeActionButtonsProps) => {
|
|
15
|
+
const type = useNodeType();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{addButtonVisible && (
|
|
20
|
+
<WorkflowNodeActionButtons
|
|
21
|
+
direction={'bottom'}
|
|
22
|
+
variant={'addParallelCol'}
|
|
23
|
+
/>
|
|
24
|
+
)}
|
|
25
|
+
|
|
26
|
+
{type === 'switch' && (
|
|
27
|
+
<Button onClick={addNewCaseNode}>
|
|
28
|
+
Add new case
|
|
29
|
+
</Button>
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
{nextNodes &&
|
|
33
|
+
(nextNodes.length < 2 || type === 'emptyNode' ? (
|
|
34
|
+
<WorkflowNodeActionButtons
|
|
35
|
+
addInMiddle={nextNodes.length === 1}
|
|
36
|
+
direction={'bottom'}
|
|
37
|
+
isReplacing={type === 'emptyNode'}
|
|
38
|
+
/>
|
|
39
|
+
) : null)}
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export default NodeActionButtons;
|