@d34dman/flowdrop 0.0.4 → 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 -64
- 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/config/apiConfig.d.ts +2 -1
- package/dist/config/apiConfig.js +3 -2
- 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 +12 -12
- package/dist/svelte-app.js +2 -3
- package/dist/utils/config.d.ts +5 -1
- package/dist/utils/config.js +9 -17
- package/package.json +2 -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,14 +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
|
-
|
|
190
|
-
const
|
|
191
|
-
import.meta.env.VITE_API_BASE_URL || import.meta.env.VITE_DRUPAL_API_URL || '/api/flowdrop';
|
|
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();
|
|
192
190
|
|
|
193
|
-
|
|
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, {
|
|
194
201
|
auth: {
|
|
195
202
|
type: 'none' // No authentication for now
|
|
196
203
|
},
|
|
@@ -270,60 +277,56 @@
|
|
|
270
277
|
* Save workflow - exposed API function
|
|
271
278
|
*/
|
|
272
279
|
async function saveWorkflow(): Promise<void> {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
await tick();
|
|
280
|
+
// Wait for any pending DOM updates before saving
|
|
281
|
+
await tick();
|
|
276
282
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
// Import necessary modules
|
|
284
|
+
const { workflowApi } = await import('../services/api.js');
|
|
285
|
+
const { v4: uuidv4 } = await import('uuid');
|
|
280
286
|
|
|
281
|
-
|
|
282
|
-
|
|
287
|
+
// Use current workflow from global store
|
|
288
|
+
const workflowToSave = $workflowStore;
|
|
283
289
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
290
|
+
if (!workflowToSave) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
287
293
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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()
|
|
294
313
|
}
|
|
314
|
+
};
|
|
295
315
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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,
|
|
303
325
|
metadata: {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
updatedAt: new Date().toISOString()
|
|
326
|
+
...finalWorkflow.metadata,
|
|
327
|
+
...savedWorkflow.metadata
|
|
307
328
|
}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const savedWorkflow = await workflowApi.saveWorkflow(finalWorkflow);
|
|
311
|
-
|
|
312
|
-
// Update the workflow ID if it changed (new workflow)
|
|
313
|
-
// Keep our current workflow state, only update ID and metadata from backend
|
|
314
|
-
if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
|
|
315
|
-
workflowActions.batchUpdate({
|
|
316
|
-
nodes: finalWorkflow.nodes,
|
|
317
|
-
edges: finalWorkflow.edges,
|
|
318
|
-
name: finalWorkflow.name,
|
|
319
|
-
metadata: {
|
|
320
|
-
...finalWorkflow.metadata,
|
|
321
|
-
...savedWorkflow.metadata
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
} catch (error) {
|
|
326
|
-
throw error; // Re-throw so caller can handle
|
|
329
|
+
});
|
|
327
330
|
}
|
|
328
331
|
}
|
|
329
332
|
|
|
@@ -364,16 +367,16 @@
|
|
|
364
367
|
link.download = `${finalWorkflow.name}.json`;
|
|
365
368
|
link.click();
|
|
366
369
|
URL.revokeObjectURL(url);
|
|
367
|
-
} catch
|
|
370
|
+
} catch {
|
|
368
371
|
// Export failed
|
|
369
372
|
}
|
|
370
373
|
}
|
|
371
374
|
|
|
372
375
|
// Expose save and export functions globally for external access
|
|
373
376
|
if (typeof window !== 'undefined') {
|
|
374
|
-
// @ts-
|
|
377
|
+
// @ts-expect-error - Adding to window for external access
|
|
375
378
|
window.flowdropSave = saveWorkflow;
|
|
376
|
-
// @ts-
|
|
379
|
+
// @ts-expect-error - Adding to window for external access
|
|
377
380
|
window.flowdropExport = exportWorkflow;
|
|
378
381
|
}
|
|
379
382
|
|
|
@@ -651,7 +654,7 @@
|
|
|
651
654
|
|
|
652
655
|
<!-- Render configuration fields based on schema -->
|
|
653
656
|
{#if configSchema.properties}
|
|
654
|
-
{#each Object.entries(configSchema.properties) as [key, field]}
|
|
657
|
+
{#each Object.entries(configSchema.properties) as [key, field] (key)}
|
|
655
658
|
{@const fieldConfig = field as any}
|
|
656
659
|
{#if fieldConfig.format !== 'hidden'}
|
|
657
660
|
<div class="flowdrop-config-sidebar__field">
|
|
@@ -661,7 +664,7 @@
|
|
|
661
664
|
{#if fieldConfig.enum && fieldConfig.multiple}
|
|
662
665
|
<!-- Checkboxes for enum with multiple selection -->
|
|
663
666
|
<div class="flowdrop-config-sidebar__checkbox-group">
|
|
664
|
-
{#each fieldConfig.enum as option}
|
|
667
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
665
668
|
<label class="flowdrop-config-sidebar__checkbox-item">
|
|
666
669
|
<input
|
|
667
670
|
type="checkbox"
|
|
@@ -698,7 +701,7 @@
|
|
|
698
701
|
class="flowdrop-config-sidebar__select"
|
|
699
702
|
bind:value={configValues[key]}
|
|
700
703
|
>
|
|
701
|
-
{#each fieldConfig.enum as option}
|
|
704
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
702
705
|
<option value={String(option)}>{String(option)}</option>
|
|
703
706
|
{/each}
|
|
704
707
|
</select>
|
|
@@ -744,7 +747,7 @@
|
|
|
744
747
|
bind:value={configValues[key]}
|
|
745
748
|
>
|
|
746
749
|
{#if fieldConfig.options}
|
|
747
|
-
{#each fieldConfig.options as option}
|
|
750
|
+
{#each fieldConfig.options as option (String(option.value))}
|
|
748
751
|
{@const optionConfig = option as any}
|
|
749
752
|
<option value={String(optionConfig.value)}
|
|
750
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);
|