@flowuent-org/diagramming-core 1.2.1 → 1.2.2

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.
Files changed (20) hide show
  1. package/apps/diagramming/src/AutomationDiagramData.ts +65 -1
  2. package/package.json +1 -1
  3. package/packages/diagrams/src/lib/components/CanvasSearchBar.tsx +281 -0
  4. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +796 -788
  5. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +608 -600
  6. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +833 -825
  7. package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +584 -0
  8. package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +422 -414
  9. package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +1120 -1112
  10. package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +511 -503
  11. package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -0
  12. package/packages/diagrams/src/lib/components/automation/NodeActionButtons.tsx +146 -145
  13. package/packages/diagrams/src/lib/components/automation/index.ts +21 -12
  14. package/packages/diagrams/src/lib/contexts/SearchContext.tsx +78 -0
  15. package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +79 -76
  16. package/packages/diagrams/src/lib/templates/DiagramContent.tsx +276 -61
  17. package/packages/diagrams/src/lib/types/automation-node-data-types.ts +22 -2
  18. package/packages/diagrams/src/lib/types/node-types.ts +2 -0
  19. package/packages/diagrams/src/lib/utils/highlightText.tsx +93 -0
  20. package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -0
@@ -149,6 +149,51 @@ export const automationDefaultNodes = [
149
149
  height: 150,
150
150
  measured: { width: 336, height: 150 },
151
151
  },
152
+ {
153
+ id: 'navigation-node',
154
+ type: 'AutomationNavigationNode',
155
+ position: { x: 600, y: 200 },
156
+ data: {
157
+ // Display data for the node UI
158
+ label: 'Navigate to Website',
159
+ description: 'Navigate to the target website and wait for page load',
160
+ status: 'Ready',
161
+ navigationType: 'navigate',
162
+ url: 'https://example.com',
163
+ lastRun: 'Never',
164
+ backgroundColor: '#181C25',
165
+ textColor: '#ffffff',
166
+ borderColor: '#1e293b',
167
+ iconName: 'Navigation',
168
+ // Form data for configuration
169
+ formData: {
170
+ nodeId: 'navigation-node',
171
+ title: 'Navigate to Website',
172
+ type: 'navigation',
173
+ navigationType: 'navigate',
174
+ url: 'https://example.com',
175
+ timeout: 30000,
176
+ retryCount: 3,
177
+ outputVariable: 'navigationResult',
178
+ errorHandling: {
179
+ onError: 'retry',
180
+ maxRetries: 3,
181
+ fallbackAction: 'skip',
182
+ },
183
+ isPinned: false,
184
+ isBlock: false,
185
+ blocks: [],
186
+ parallelChildrenCount: 0,
187
+ conditions: {
188
+ combinator: 'and',
189
+ rules: [],
190
+ },
191
+ },
192
+ },
193
+ width: 336,
194
+ height: 150,
195
+ measured: { width: 336, height: 150 },
196
+ },
152
197
  {
153
198
  id: 'ai-suggestion-node',
154
199
  type: 'AutomationAISuggestionNode',
@@ -504,8 +549,27 @@ export const automationDefaultEdges = [
504
549
  },
505
550
  },
506
551
  {
507
- id: 'edge-api-to-formatting',
552
+ id: 'edge-api-to-navigation',
508
553
  source: 'api-call-node',
554
+ target: 'navigation-node',
555
+ sourceHandle: 'right',
556
+ targetHandle: 'left',
557
+ data: {
558
+ label: '',
559
+ type: 'flow',
560
+ },
561
+ style: {
562
+ stroke: '#ffffff',
563
+ strokeWidth: 2,
564
+ },
565
+ markerEnd: {
566
+ type: MarkerType.ArrowClosed,
567
+ color: '#ffffff',
568
+ },
569
+ },
570
+ {
571
+ id: 'edge-navigation-to-formatting',
572
+ source: 'navigation-node',
509
573
  target: 'data-formatting-node',
510
574
  sourceHandle: 'right',
511
575
  targetHandle: 'left',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowuent-org/diagramming-core",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,281 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Box, TextField, IconButton, Typography, Paper } from '@mui/material';
3
+ import { RiCloseLine, RiArrowUpLine, RiArrowDownLine } from 'react-icons/ri';
4
+ import { Node } from '@xyflow/react';
5
+ import { useSearch } from '../contexts/SearchContext';
6
+
7
+ interface CanvasSearchBarProps {
8
+ nodes: Node[];
9
+ onClose: () => void;
10
+ onNodeSelect: (nodeId: string) => void;
11
+ onViewportChange: (nodeId: string) => void;
12
+ getViewport: () => { x: number; y: number; zoom: number };
13
+ setViewport: (viewport: { x: number; y: number; zoom: number }) => void;
14
+ }
15
+
16
+ export const CanvasSearchBar: React.FC<CanvasSearchBarProps> = ({
17
+ nodes,
18
+ onClose,
19
+ onNodeSelect,
20
+ onViewportChange,
21
+ getViewport,
22
+ setViewport,
23
+ }) => {
24
+ const { searchQuery, setSearchQuery } = useSearch();
25
+ const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
26
+ const [matchingNodes, setMatchingNodes] = useState<Node[]>([]);
27
+ const inputRef = useRef<HTMLInputElement>(null);
28
+
29
+ // Focus input when component mounts
30
+ useEffect(() => {
31
+ inputRef.current?.focus();
32
+ }, []);
33
+
34
+ // Search nodes when query changes
35
+ useEffect(() => {
36
+ if (!searchQuery.trim()) {
37
+ setMatchingNodes([]);
38
+ setCurrentMatchIndex(0);
39
+ return;
40
+ }
41
+
42
+ const searchLower = searchQuery.toLowerCase().trim();
43
+ const matches = nodes.filter((node) => {
44
+ const label = (node.data as any)?.label?.toLowerCase() || '';
45
+ const id = node.id.toLowerCase();
46
+ const description = (node.data as any)?.description?.toLowerCase() || '';
47
+ return (
48
+ label.includes(searchLower) ||
49
+ id.includes(searchLower) ||
50
+ description.includes(searchLower)
51
+ );
52
+ });
53
+
54
+ setMatchingNodes(matches);
55
+ setCurrentMatchIndex(0);
56
+
57
+ // Navigate to first match if any
58
+ if (matches.length > 0) {
59
+ navigateToNode(matches[0].id);
60
+ }
61
+ }, [searchQuery, nodes]);
62
+
63
+ // Navigate to a specific node
64
+ const navigateToNode = useCallback(
65
+ (nodeId: string) => {
66
+ const node = nodes.find((n) => n.id === nodeId);
67
+ if (!node) return;
68
+
69
+ // Center view on the node
70
+ const viewport = getViewport();
71
+ setViewport({
72
+ x: -node.position.x + window.innerWidth / 2 - (node.width || 0) / 2,
73
+ y: -node.position.y + window.innerHeight / 2 - (node.height || 0) / 2,
74
+ zoom: viewport.zoom,
75
+ });
76
+
77
+ // Select the node
78
+ onNodeSelect(nodeId);
79
+ onViewportChange(nodeId);
80
+ },
81
+ [nodes, getViewport, setViewport, onNodeSelect, onViewportChange]
82
+ );
83
+
84
+ // Navigate to next match
85
+ const handleNextMatch = useCallback(() => {
86
+ if (matchingNodes.length === 0) return;
87
+ const nextIndex = (currentMatchIndex + 1) % matchingNodes.length;
88
+ setCurrentMatchIndex(nextIndex);
89
+ navigateToNode(matchingNodes[nextIndex].id);
90
+ }, [matchingNodes, currentMatchIndex, navigateToNode]);
91
+
92
+ // Navigate to previous match
93
+ const handlePreviousMatch = useCallback(() => {
94
+ if (matchingNodes.length === 0) return;
95
+ const prevIndex =
96
+ currentMatchIndex === 0 ? matchingNodes.length - 1 : currentMatchIndex - 1;
97
+ setCurrentMatchIndex(prevIndex);
98
+ navigateToNode(matchingNodes[prevIndex].id);
99
+ }, [matchingNodes, currentMatchIndex, navigateToNode]);
100
+
101
+ // Clear search query when closing
102
+ const handleClose = useCallback(() => {
103
+ setSearchQuery('');
104
+ onClose();
105
+ }, [setSearchQuery, onClose]);
106
+
107
+ // Handle keyboard shortcuts
108
+ useEffect(() => {
109
+ const handleKeyDown = (event: KeyboardEvent) => {
110
+ if (event.key === 'Escape') {
111
+ handleClose();
112
+ } else if (event.key === 'Enter') {
113
+ if (event.shiftKey) {
114
+ handlePreviousMatch();
115
+ } else {
116
+ handleNextMatch();
117
+ }
118
+ } else if (event.key === 'ArrowDown' && event.ctrlKey) {
119
+ event.preventDefault();
120
+ handleNextMatch();
121
+ } else if (event.key === 'ArrowUp' && event.ctrlKey) {
122
+ event.preventDefault();
123
+ handlePreviousMatch();
124
+ }
125
+ };
126
+
127
+ document.addEventListener('keydown', handleKeyDown);
128
+ return () => {
129
+ document.removeEventListener('keydown', handleKeyDown);
130
+ };
131
+ }, [handleClose, handleNextMatch, handlePreviousMatch]);
132
+
133
+ return (
134
+ <Paper
135
+ elevation={8}
136
+ sx={{
137
+ position: 'fixed',
138
+ top: 16,
139
+ left: '50%',
140
+ transform: 'translateX(-50%)',
141
+ zIndex: 10000,
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ gap: 1,
145
+ p: 1,
146
+ bgcolor: '#1F2937',
147
+ border: '1px solid #374151',
148
+ borderRadius: '8px',
149
+ minWidth: '400px',
150
+ boxShadow: '0 4px 20px rgba(0, 0, 0, 0.5)',
151
+ }}
152
+ >
153
+ <TextField
154
+ inputRef={inputRef}
155
+ placeholder="Search nodes..."
156
+ value={searchQuery}
157
+ onChange={(e) => setSearchQuery(e.target.value)}
158
+ size="small"
159
+ autoFocus
160
+ sx={{
161
+ flex: 1,
162
+ '& .MuiOutlinedInput-root': {
163
+ bgcolor: '#111827',
164
+ color: '#fff',
165
+ borderRadius: '6px',
166
+ '& fieldset': {
167
+ borderColor: '#374151',
168
+ },
169
+ '&:hover fieldset': {
170
+ borderColor: '#4B5563',
171
+ },
172
+ '&.Mui-focused fieldset': {
173
+ borderColor: '#3b82f6',
174
+ },
175
+ },
176
+ '& .MuiInputBase-input': {
177
+ color: '#fff',
178
+ fontSize: '14px',
179
+ '&::placeholder': {
180
+ color: '#9CA3AF',
181
+ opacity: 1,
182
+ },
183
+ },
184
+ }}
185
+ />
186
+
187
+ {/* Match count and navigation */}
188
+ {searchQuery.trim() && matchingNodes.length > 0 && (
189
+ <Box
190
+ sx={{
191
+ display: 'flex',
192
+ alignItems: 'center',
193
+ gap: 0.5,
194
+ px: 1,
195
+ borderRight: '1px solid #374151',
196
+ borderLeft: '1px solid #374151',
197
+ }}
198
+ >
199
+ <Typography
200
+ variant="caption"
201
+ sx={{
202
+ color: '#9CA3AF',
203
+ fontSize: '12px',
204
+ minWidth: '50px',
205
+ textAlign: 'center',
206
+ }}
207
+ >
208
+ {currentMatchIndex + 1}/{matchingNodes.length}
209
+ </Typography>
210
+ <IconButton
211
+ size="small"
212
+ onClick={handlePreviousMatch}
213
+ disabled={matchingNodes.length === 0}
214
+ sx={{
215
+ color: '#9CA3AF',
216
+ '&:hover': {
217
+ bgcolor: 'rgba(255, 255, 255, 0.1)',
218
+ color: '#fff',
219
+ },
220
+ '&:disabled': {
221
+ color: '#4B5563',
222
+ },
223
+ }}
224
+ title="Previous match (Shift+Enter or Ctrl+↑)"
225
+ >
226
+ <RiArrowUpLine size={16} />
227
+ </IconButton>
228
+ <IconButton
229
+ size="small"
230
+ onClick={handleNextMatch}
231
+ disabled={matchingNodes.length === 0}
232
+ sx={{
233
+ color: '#9CA3AF',
234
+ '&:hover': {
235
+ bgcolor: 'rgba(255, 255, 255, 0.1)',
236
+ color: '#fff',
237
+ },
238
+ '&:disabled': {
239
+ color: '#4B5563',
240
+ },
241
+ }}
242
+ title="Next match (Enter or Ctrl+↓)"
243
+ >
244
+ <RiArrowDownLine size={16} />
245
+ </IconButton>
246
+ </Box>
247
+ )}
248
+
249
+ {/* No results indicator */}
250
+ {searchQuery.trim() && matchingNodes.length === 0 && (
251
+ <Typography
252
+ variant="caption"
253
+ sx={{
254
+ color: '#EF4444',
255
+ fontSize: '12px',
256
+ px: 1,
257
+ }}
258
+ >
259
+ No matches
260
+ </Typography>
261
+ )}
262
+
263
+ {/* Close button */}
264
+ <IconButton
265
+ size="small"
266
+ onClick={handleClose}
267
+ sx={{
268
+ color: '#9CA3AF',
269
+ '&:hover': {
270
+ bgcolor: 'rgba(255, 255, 255, 0.1)',
271
+ color: '#fff',
272
+ },
273
+ }}
274
+ title="Close (Esc)"
275
+ >
276
+ <RiCloseLine size={18} />
277
+ </IconButton>
278
+ </Paper>
279
+ );
280
+ };
281
+