@d34dman/flowdrop 0.0.5 → 0.0.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/README.md +107 -104
- package/dist/components/App.svelte +67 -62
- package/dist/components/App.svelte.d.ts +1 -0
- package/dist/components/Navbar.svelte +1 -11
- package/dist/components/NodeSidebar.svelte +2 -1
- package/dist/components/NodeStatusOverlay.svelte +0 -1
- package/dist/components/NodeStatusOverlay.svelte.d.ts +0 -1
- package/dist/components/PipelineStatus.svelte +0 -37
- package/dist/components/SimpleNode.svelte +0 -7
- package/dist/components/SquareNode.svelte +0 -7
- package/dist/components/UniversalNode.svelte +0 -8
- package/dist/components/WorkflowEditor.svelte +63 -456
- package/dist/components/WorkflowNode.svelte +1 -8
- package/dist/helpers/workflowEditorHelper.d.ts +87 -0
- package/dist/helpers/workflowEditorHelper.js +365 -0
- package/dist/index.d.ts +31 -1
- package/dist/index.js +30 -1
- package/dist/mocks/app-navigation.d.ts +4 -4
- package/dist/mocks/app-navigation.js +4 -4
- package/dist/services/api.js +13 -18
- package/dist/services/globalSave.js +4 -4
- package/dist/svelte-app.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,34 +42,34 @@ npm install @d34dman/flowdrop
|
|
|
42
42
|
### Basic Import
|
|
43
43
|
|
|
44
44
|
```javascript
|
|
45
|
-
import { WorkflowEditor } from
|
|
46
|
-
import
|
|
45
|
+
import { WorkflowEditor } from '@d34dman/flowdrop';
|
|
46
|
+
import '@d34dman/flowdrop/styles/base.css';
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
### Using the WorkflowEditor Component
|
|
50
50
|
|
|
51
51
|
```svelte
|
|
52
52
|
<script lang="ts">
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
53
|
+
import { WorkflowEditor } from '@d34dman/flowdrop';
|
|
54
|
+
import type { NodeMetadata, Workflow } from '@d34dman/flowdrop';
|
|
55
|
+
|
|
56
|
+
let nodes: NodeMetadata[] = [
|
|
57
|
+
{
|
|
58
|
+
id: 'text_input',
|
|
59
|
+
name: 'Text Input',
|
|
60
|
+
category: 'input_output',
|
|
61
|
+
description: 'User input field',
|
|
62
|
+
inputs: [],
|
|
63
|
+
outputs: [{ id: 'value', name: 'Value', type: 'output', dataType: 'string' }]
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
let workflow: Workflow = {
|
|
68
|
+
id: 'workflow_1',
|
|
69
|
+
name: 'My Workflow',
|
|
70
|
+
nodes: [],
|
|
71
|
+
edges: []
|
|
72
|
+
};
|
|
73
73
|
</script>
|
|
74
74
|
|
|
75
75
|
<WorkflowEditor {nodes} />
|
|
@@ -81,34 +81,34 @@ import "@d34dman/flowdrop/styles/base.css";
|
|
|
81
81
|
|
|
82
82
|
```svelte
|
|
83
83
|
<script>
|
|
84
|
-
|
|
84
|
+
import { WorkflowEditor, NodeSidebar } from '@d34dman/flowdrop';
|
|
85
85
|
</script>
|
|
86
86
|
|
|
87
87
|
<div class="editor-container">
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
<NodeSidebar {nodes} />
|
|
89
|
+
<WorkflowEditor {nodes} />
|
|
90
90
|
</div>
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
#### 2. Using Mount Functions (Vanilla JS/Other Frameworks)
|
|
94
94
|
|
|
95
95
|
```javascript
|
|
96
|
-
import { mountWorkflowEditor } from
|
|
96
|
+
import { mountWorkflowEditor } from '@d34dman/flowdrop';
|
|
97
97
|
|
|
98
|
-
const container = document.getElementById(
|
|
98
|
+
const container = document.getElementById('workflow-container');
|
|
99
99
|
const editor = mountWorkflowEditor(container, {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
nodes: availableNodes,
|
|
101
|
+
endpointConfig: {
|
|
102
|
+
baseUrl: '/api/flowdrop',
|
|
103
|
+
endpoints: {
|
|
104
|
+
workflows: {
|
|
105
|
+
list: '/workflows',
|
|
106
|
+
get: '/workflows/{id}',
|
|
107
|
+
create: '/workflows',
|
|
108
|
+
update: '/workflows/{id}'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
// Cleanup
|
|
@@ -121,15 +121,15 @@ editor.destroy();
|
|
|
121
121
|
|
|
122
122
|
```javascript
|
|
123
123
|
Drupal.behaviors.flowdropEditor = {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
attach: function (context, settings) {
|
|
125
|
+
const container = context.querySelector('.flowdrop-container');
|
|
126
|
+
if (container && window.FlowDrop) {
|
|
127
|
+
window.FlowDrop.mountWorkflowEditor(container, {
|
|
128
|
+
endpointConfig: settings.flowdrop.endpointConfig,
|
|
129
|
+
nodes: settings.flowdrop.nodes
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
133
|
};
|
|
134
134
|
```
|
|
135
135
|
|
|
@@ -140,6 +140,7 @@ Drupal.behaviors.flowdropEditor = {
|
|
|
140
140
|
Main editor component for creating and editing workflows.
|
|
141
141
|
|
|
142
142
|
**Props:**
|
|
143
|
+
|
|
143
144
|
- `nodes`: Array of available node types
|
|
144
145
|
- `endpointConfig`: API endpoint configuration
|
|
145
146
|
- `height`: Editor height (default: "100vh")
|
|
@@ -152,6 +153,7 @@ Main editor component for creating and editing workflows.
|
|
|
152
153
|
Sidebar displaying available node types.
|
|
153
154
|
|
|
154
155
|
**Props:**
|
|
156
|
+
|
|
155
157
|
- `nodes`: Array of node types to display
|
|
156
158
|
|
|
157
159
|
### ConfigSidebar
|
|
@@ -159,6 +161,7 @@ Sidebar displaying available node types.
|
|
|
159
161
|
Configuration panel for selected nodes.
|
|
160
162
|
|
|
161
163
|
**Props:**
|
|
164
|
+
|
|
162
165
|
- `isOpen`: Sidebar visibility
|
|
163
166
|
- `configSchema`: JSON schema for configuration
|
|
164
167
|
- `configValues`: Current configuration values
|
|
@@ -170,29 +173,29 @@ Configuration panel for selected nodes.
|
|
|
170
173
|
Configure the API client to connect to your backend:
|
|
171
174
|
|
|
172
175
|
```typescript
|
|
173
|
-
import { createEndpointConfig } from
|
|
176
|
+
import { createEndpointConfig } from '@d34dman/flowdrop';
|
|
174
177
|
|
|
175
178
|
const config = createEndpointConfig({
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
179
|
+
baseUrl: 'https://api.example.com',
|
|
180
|
+
endpoints: {
|
|
181
|
+
nodes: {
|
|
182
|
+
list: '/nodes',
|
|
183
|
+
get: '/nodes/{id}'
|
|
184
|
+
},
|
|
185
|
+
workflows: {
|
|
186
|
+
list: '/workflows',
|
|
187
|
+
get: '/workflows/{id}',
|
|
188
|
+
create: '/workflows',
|
|
189
|
+
update: '/workflows/{id}',
|
|
190
|
+
delete: '/workflows/{id}',
|
|
191
|
+
execute: '/workflows/{id}/execute'
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
timeout: 30000,
|
|
195
|
+
auth: {
|
|
196
|
+
type: 'bearer',
|
|
197
|
+
token: 'your-token'
|
|
198
|
+
}
|
|
196
199
|
});
|
|
197
200
|
```
|
|
198
201
|
|
|
@@ -204,10 +207,10 @@ Override CSS custom properties:
|
|
|
204
207
|
|
|
205
208
|
```css
|
|
206
209
|
:root {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
--flowdrop-background-color: #f9fafb;
|
|
211
|
+
--flowdrop-primary-color: #3b82f6;
|
|
212
|
+
--flowdrop-border-color: #e5e7eb;
|
|
213
|
+
--flowdrop-text-color: #1f2937;
|
|
211
214
|
}
|
|
212
215
|
```
|
|
213
216
|
|
|
@@ -217,37 +220,37 @@ Define custom node types:
|
|
|
217
220
|
|
|
218
221
|
```typescript
|
|
219
222
|
const customNode: NodeMetadata = {
|
|
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
|
-
|
|
223
|
+
id: 'custom_processor',
|
|
224
|
+
name: 'Custom Processor',
|
|
225
|
+
category: 'data_processing',
|
|
226
|
+
description: 'Process data with custom logic',
|
|
227
|
+
icon: 'mdi:cog',
|
|
228
|
+
color: '#3b82f6',
|
|
229
|
+
inputs: [
|
|
230
|
+
{
|
|
231
|
+
id: 'input',
|
|
232
|
+
name: 'Input',
|
|
233
|
+
type: 'input',
|
|
234
|
+
dataType: 'mixed'
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
outputs: [
|
|
238
|
+
{
|
|
239
|
+
id: 'output',
|
|
240
|
+
name: 'Output',
|
|
241
|
+
type: 'output',
|
|
242
|
+
dataType: 'mixed'
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
configSchema: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
properties: {
|
|
248
|
+
operation: {
|
|
249
|
+
type: 'string',
|
|
250
|
+
title: 'Operation'
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
251
254
|
};
|
|
252
255
|
```
|
|
253
256
|
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
import { createEndpointConfig } from '../config/endpoints.js';
|
|
18
18
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
19
19
|
import { workflowStore, workflowActions, workflowName } from '../stores/workflowStore.js';
|
|
20
|
-
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
21
20
|
import { apiToasts, dismissToast } from '../services/toastService.js';
|
|
22
21
|
|
|
23
22
|
// Configuration props for runtime customization
|
|
@@ -42,6 +41,8 @@
|
|
|
42
41
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
43
42
|
onclick?: (event: Event) => void;
|
|
44
43
|
}>;
|
|
44
|
+
// API configuration - optional, defaults to '/api/flowdrop'
|
|
45
|
+
apiBaseUrl?: string;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
let {
|
|
@@ -55,7 +56,8 @@
|
|
|
55
56
|
nodeStatuses = {},
|
|
56
57
|
pipelineId,
|
|
57
58
|
navbarTitle,
|
|
58
|
-
navbarActions = []
|
|
59
|
+
navbarActions = [],
|
|
60
|
+
apiBaseUrl
|
|
59
61
|
}: Props = $props();
|
|
60
62
|
|
|
61
63
|
// Create breadcrumb-style title - at top level to avoid store subscription issues
|
|
@@ -75,7 +77,6 @@
|
|
|
75
77
|
// Remove workflow prop - use global store directly
|
|
76
78
|
// let workflow = $derived($workflowStore || initialWorkflow);
|
|
77
79
|
let error = $state<string | null>(null);
|
|
78
|
-
let loading = $state(true);
|
|
79
80
|
let endpointConfig = $state<EndpointConfig | null>(null);
|
|
80
81
|
|
|
81
82
|
// ConfigSidebar state
|
|
@@ -130,7 +131,6 @@
|
|
|
130
131
|
// Show loading toast
|
|
131
132
|
const loadingToast = apiToasts.loading('Loading node types');
|
|
132
133
|
try {
|
|
133
|
-
loading = true;
|
|
134
134
|
error = null;
|
|
135
135
|
|
|
136
136
|
const fetchedNodes = await api.nodes.getNodes();
|
|
@@ -149,8 +149,6 @@
|
|
|
149
149
|
|
|
150
150
|
// Fallback to sample data
|
|
151
151
|
nodes = sampleNodes;
|
|
152
|
-
} finally {
|
|
153
|
-
loading = false;
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
|
|
@@ -183,12 +181,23 @@
|
|
|
183
181
|
|
|
184
182
|
/**
|
|
185
183
|
* Initialize API endpoints
|
|
184
|
+
* Only initializes if not already configured (respects configuration from parent)
|
|
186
185
|
*/
|
|
187
186
|
async function initializeApiEndpoints(): Promise<void> {
|
|
188
|
-
//
|
|
189
|
-
const
|
|
187
|
+
// Check if endpoint config is already set (e.g., by parent layout)
|
|
188
|
+
const { getEndpointConfig } = await import('../services/api.js');
|
|
189
|
+
const existingConfig = getEndpointConfig();
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
// If config already exists and no override provided, use existing
|
|
192
|
+
if (existingConfig && !apiBaseUrl) {
|
|
193
|
+
endpointConfig = existingConfig;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Use provided apiBaseUrl or default
|
|
198
|
+
const baseUrl = apiBaseUrl || '/api/flowdrop';
|
|
199
|
+
|
|
200
|
+
const config = createEndpointConfig(baseUrl, {
|
|
192
201
|
auth: {
|
|
193
202
|
type: 'none' // No authentication for now
|
|
194
203
|
},
|
|
@@ -268,60 +277,56 @@
|
|
|
268
277
|
* Save workflow - exposed API function
|
|
269
278
|
*/
|
|
270
279
|
async function saveWorkflow(): Promise<void> {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
await tick();
|
|
280
|
+
// Wait for any pending DOM updates before saving
|
|
281
|
+
await tick();
|
|
274
282
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
283
|
+
// Import necessary modules
|
|
284
|
+
const { workflowApi } = await import('../services/api.js');
|
|
285
|
+
const { v4: uuidv4 } = await import('uuid');
|
|
278
286
|
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
// Use current workflow from global store
|
|
288
|
+
const workflowToSave = $workflowStore;
|
|
281
289
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
290
|
+
if (!workflowToSave) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
285
293
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
294
|
+
// Determine the workflow ID
|
|
295
|
+
let workflowId: string;
|
|
296
|
+
if (workflowToSave.id) {
|
|
297
|
+
workflowId = workflowToSave.id;
|
|
298
|
+
} else {
|
|
299
|
+
workflowId = uuidv4();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Create workflow object for saving
|
|
303
|
+
const finalWorkflow = {
|
|
304
|
+
id: workflowId,
|
|
305
|
+
name: workflowToSave.name || 'Untitled Workflow',
|
|
306
|
+
description: workflowToSave.description || '',
|
|
307
|
+
nodes: workflowToSave.nodes || [],
|
|
308
|
+
edges: workflowToSave.edges || [],
|
|
309
|
+
metadata: {
|
|
310
|
+
version: '1.0.0',
|
|
311
|
+
createdAt: workflowToSave.metadata?.createdAt || new Date().toISOString(),
|
|
312
|
+
updatedAt: new Date().toISOString()
|
|
292
313
|
}
|
|
314
|
+
};
|
|
293
315
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
316
|
+
const savedWorkflow = await workflowApi.saveWorkflow(finalWorkflow);
|
|
317
|
+
|
|
318
|
+
// Update the workflow ID if it changed (new workflow)
|
|
319
|
+
// Keep our current workflow state, only update ID and metadata from backend
|
|
320
|
+
if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
|
|
321
|
+
workflowActions.batchUpdate({
|
|
322
|
+
nodes: finalWorkflow.nodes,
|
|
323
|
+
edges: finalWorkflow.edges,
|
|
324
|
+
name: finalWorkflow.name,
|
|
301
325
|
metadata: {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
updatedAt: new Date().toISOString()
|
|
326
|
+
...finalWorkflow.metadata,
|
|
327
|
+
...savedWorkflow.metadata
|
|
305
328
|
}
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const savedWorkflow = await workflowApi.saveWorkflow(finalWorkflow);
|
|
309
|
-
|
|
310
|
-
// Update the workflow ID if it changed (new workflow)
|
|
311
|
-
// Keep our current workflow state, only update ID and metadata from backend
|
|
312
|
-
if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
|
|
313
|
-
workflowActions.batchUpdate({
|
|
314
|
-
nodes: finalWorkflow.nodes,
|
|
315
|
-
edges: finalWorkflow.edges,
|
|
316
|
-
name: finalWorkflow.name,
|
|
317
|
-
metadata: {
|
|
318
|
-
...finalWorkflow.metadata,
|
|
319
|
-
...savedWorkflow.metadata
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
} catch (error) {
|
|
324
|
-
throw error; // Re-throw so caller can handle
|
|
329
|
+
});
|
|
325
330
|
}
|
|
326
331
|
}
|
|
327
332
|
|
|
@@ -362,16 +367,16 @@
|
|
|
362
367
|
link.download = `${finalWorkflow.name}.json`;
|
|
363
368
|
link.click();
|
|
364
369
|
URL.revokeObjectURL(url);
|
|
365
|
-
} catch
|
|
370
|
+
} catch {
|
|
366
371
|
// Export failed
|
|
367
372
|
}
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
// Expose save and export functions globally for external access
|
|
371
376
|
if (typeof window !== 'undefined') {
|
|
372
|
-
// @ts-
|
|
377
|
+
// @ts-expect-error - Adding to window for external access
|
|
373
378
|
window.flowdropSave = saveWorkflow;
|
|
374
|
-
// @ts-
|
|
379
|
+
// @ts-expect-error - Adding to window for external access
|
|
375
380
|
window.flowdropExport = exportWorkflow;
|
|
376
381
|
}
|
|
377
382
|
|
|
@@ -649,7 +654,7 @@
|
|
|
649
654
|
|
|
650
655
|
<!-- Render configuration fields based on schema -->
|
|
651
656
|
{#if configSchema.properties}
|
|
652
|
-
{#each Object.entries(configSchema.properties) as [key, field]}
|
|
657
|
+
{#each Object.entries(configSchema.properties) as [key, field] (key)}
|
|
653
658
|
{@const fieldConfig = field as any}
|
|
654
659
|
{#if fieldConfig.format !== 'hidden'}
|
|
655
660
|
<div class="flowdrop-config-sidebar__field">
|
|
@@ -659,7 +664,7 @@
|
|
|
659
664
|
{#if fieldConfig.enum && fieldConfig.multiple}
|
|
660
665
|
<!-- Checkboxes for enum with multiple selection -->
|
|
661
666
|
<div class="flowdrop-config-sidebar__checkbox-group">
|
|
662
|
-
{#each fieldConfig.enum as option}
|
|
667
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
663
668
|
<label class="flowdrop-config-sidebar__checkbox-item">
|
|
664
669
|
<input
|
|
665
670
|
type="checkbox"
|
|
@@ -696,7 +701,7 @@
|
|
|
696
701
|
class="flowdrop-config-sidebar__select"
|
|
697
702
|
bind:value={configValues[key]}
|
|
698
703
|
>
|
|
699
|
-
{#each fieldConfig.enum as option}
|
|
704
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
700
705
|
<option value={String(option)}>{String(option)}</option>
|
|
701
706
|
{/each}
|
|
702
707
|
</select>
|
|
@@ -742,7 +747,7 @@
|
|
|
742
747
|
bind:value={configValues[key]}
|
|
743
748
|
>
|
|
744
749
|
{#if fieldConfig.options}
|
|
745
|
-
{#each fieldConfig.options as option}
|
|
750
|
+
{#each fieldConfig.options as option (String(option.value))}
|
|
746
751
|
{@const optionConfig = option as any}
|
|
747
752
|
<option value={String(optionConfig.value)}
|
|
748
753
|
>{String(optionConfig.label)}</option
|
|
@@ -33,19 +33,9 @@
|
|
|
33
33
|
|
|
34
34
|
let { primaryActions = [], showStatus = true, title, breadcrumbs = [] }: Props = $props();
|
|
35
35
|
|
|
36
|
-
// Simple current path tracking without SvelteKit dependency
|
|
37
|
-
let currentPath = $state(typeof window !== 'undefined' ? window.location.pathname : '/');
|
|
38
|
-
|
|
39
36
|
// Dropdown state
|
|
40
37
|
let isDropdownOpen = $state(false);
|
|
41
38
|
|
|
42
|
-
function isActive(href: string): boolean {
|
|
43
|
-
if (href === '/') {
|
|
44
|
-
return currentPath === '/';
|
|
45
|
-
}
|
|
46
|
-
return currentPath.startsWith(href);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
39
|
// Close dropdown when clicking outside
|
|
50
40
|
function handleClickOutside(event: MouseEvent) {
|
|
51
41
|
const target = event.target as HTMLElement;
|
|
@@ -93,7 +83,7 @@
|
|
|
93
83
|
<div class="flowdrop-navbar__breadcrumb-container">
|
|
94
84
|
<nav class="flowdrop-navbar__breadcrumb" aria-label="Breadcrumb">
|
|
95
85
|
<ol class="flowdrop-navbar__breadcrumb-list">
|
|
96
|
-
{#each breadcrumbs as breadcrumb, index}
|
|
86
|
+
{#each breadcrumbs as breadcrumb, index (index)}
|
|
97
87
|
<li class="flowdrop-navbar__breadcrumb-item">
|
|
98
88
|
{#if breadcrumb.href && index < breadcrumbs.length - 1}
|
|
99
89
|
<a href={breadcrumb.href} class="flowdrop-navbar__breadcrumb-link">
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import Icon from '@iconify/svelte';
|
|
11
11
|
import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
|
|
12
12
|
import { getCategoryColorToken } from '../utils/colors.js';
|
|
13
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
15
16
|
nodes: NodeMetadata[];
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
function getCategories(): NodeCategory[] {
|
|
30
31
|
const nodes = props.nodes || [];
|
|
31
32
|
if (nodes.length === 0) return [];
|
|
32
|
-
const categories = new
|
|
33
|
+
const categories = new SvelteSet<NodeCategory>();
|
|
33
34
|
nodes.forEach((node) => categories.add(node.category));
|
|
34
35
|
return Array.from(categories).sort();
|
|
35
36
|
}
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
|
|
64
64
|
// Loading and error states
|
|
65
65
|
let isLoadingJobStatus = $state(false);
|
|
66
|
-
let error = $state<string | null>(null);
|
|
67
66
|
|
|
68
67
|
// Logs sidebar state
|
|
69
68
|
let isLogsSidebarOpen = $state(false);
|
|
@@ -147,42 +146,6 @@
|
|
|
147
146
|
isLogsSidebarOpen = !isLogsSidebarOpen;
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
/**
|
|
151
|
-
* Get status color for visual indicators
|
|
152
|
-
*/
|
|
153
|
-
function getStatusColor(status: string): string {
|
|
154
|
-
switch (status) {
|
|
155
|
-
case 'completed':
|
|
156
|
-
return '#10b981'; // green
|
|
157
|
-
case 'running':
|
|
158
|
-
return '#3b82f6'; // blue
|
|
159
|
-
case 'error':
|
|
160
|
-
case 'failed':
|
|
161
|
-
return '#ef4444'; // red
|
|
162
|
-
case 'pending':
|
|
163
|
-
default:
|
|
164
|
-
return '#6b7280'; // gray
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Get status icon for visual indicators
|
|
170
|
-
*/
|
|
171
|
-
function getStatusIcon(status: string): string {
|
|
172
|
-
switch (status) {
|
|
173
|
-
case 'completed':
|
|
174
|
-
return 'mdi:check-circle';
|
|
175
|
-
case 'running':
|
|
176
|
-
return 'mdi:loading';
|
|
177
|
-
case 'error':
|
|
178
|
-
case 'failed':
|
|
179
|
-
return 'mdi:alert-circle';
|
|
180
|
-
case 'pending':
|
|
181
|
-
default:
|
|
182
|
-
return 'mdi:clock-outline';
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
149
|
/**
|
|
187
150
|
* Get pipeline actions for the parent navbar
|
|
188
151
|
*/
|
|
@@ -107,13 +107,6 @@
|
|
|
107
107
|
props.data.metadata?.outputs?.find((port) => port.dataType !== 'trigger')
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
-
// Use trigger port if present, otherwise use first data port
|
|
111
|
-
let firstInputPort = $derived(triggerInputPort || firstDataInputPort);
|
|
112
|
-
let firstOutputPort = $derived(triggerOutputPort || firstDataOutputPort);
|
|
113
|
-
|
|
114
|
-
let hasInput = $derived(!!firstInputPort);
|
|
115
|
-
let hasOutput = $derived(!!firstOutputPort);
|
|
116
|
-
|
|
117
110
|
// Check if we need to show both trigger and data ports
|
|
118
111
|
let hasBothInputTypes = $derived(!!triggerInputPort && !!firstDataInputPort);
|
|
119
112
|
let hasBothOutputTypes = $derived(!!triggerOutputPort && !!firstDataOutputPort);
|