@d34dman/flowdrop 0.0.5 → 0.0.7
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/api/client.js +17 -17
- package/dist/components/App.svelte +72 -66
- 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 +1 -38
- 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 +5 -1
|
@@ -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
|
|
|
@@ -166,29 +164,41 @@
|
|
|
166
164
|
*/
|
|
167
165
|
async function testApiConnection(): Promise<void> {
|
|
168
166
|
try {
|
|
169
|
-
const
|
|
167
|
+
const baseUrl = endpointConfig?.baseUrl || apiBaseUrl || "/api/flowdrop";
|
|
168
|
+
const testUrl = `${baseUrl}/nodes`;
|
|
170
169
|
|
|
171
170
|
const response = await fetch(testUrl);
|
|
172
171
|
const data = await response.json();
|
|
173
172
|
|
|
174
173
|
if (response.ok && data.success) {
|
|
175
|
-
apiToasts.success(
|
|
174
|
+
apiToasts.success("API connection test", "Connection successful");
|
|
176
175
|
} else {
|
|
177
|
-
apiToasts.error(
|
|
176
|
+
apiToasts.error("API connection test", "Connection failed");
|
|
178
177
|
}
|
|
179
178
|
} catch (err) {
|
|
180
|
-
apiToasts.error(
|
|
179
|
+
apiToasts.error("API connection test", err instanceof Error ? err.message : "Unknown error");
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
/**
|
|
185
184
|
* Initialize API endpoints
|
|
185
|
+
* Only initializes if not already configured (respects configuration from parent)
|
|
186
186
|
*/
|
|
187
187
|
async function initializeApiEndpoints(): Promise<void> {
|
|
188
|
-
//
|
|
189
|
-
const
|
|
188
|
+
// Check if endpoint config is already set (e.g., by parent layout)
|
|
189
|
+
const { getEndpointConfig } = await import('../services/api.js');
|
|
190
|
+
const existingConfig = getEndpointConfig();
|
|
190
191
|
|
|
191
|
-
|
|
192
|
+
// If config already exists and no override provided, use existing
|
|
193
|
+
if (existingConfig && !apiBaseUrl) {
|
|
194
|
+
endpointConfig = existingConfig;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Use provided apiBaseUrl or default
|
|
199
|
+
const baseUrl = apiBaseUrl || '/api/flowdrop';
|
|
200
|
+
|
|
201
|
+
const config = createEndpointConfig(baseUrl, {
|
|
192
202
|
auth: {
|
|
193
203
|
type: 'none' // No authentication for now
|
|
194
204
|
},
|
|
@@ -268,60 +278,56 @@
|
|
|
268
278
|
* Save workflow - exposed API function
|
|
269
279
|
*/
|
|
270
280
|
async function saveWorkflow(): Promise<void> {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
await tick();
|
|
281
|
+
// Wait for any pending DOM updates before saving
|
|
282
|
+
await tick();
|
|
274
283
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
// Import necessary modules
|
|
285
|
+
const { workflowApi } = await import('../services/api.js');
|
|
286
|
+
const { v4: uuidv4 } = await import('uuid');
|
|
278
287
|
|
|
279
|
-
|
|
280
|
-
|
|
288
|
+
// Use current workflow from global store
|
|
289
|
+
const workflowToSave = $workflowStore;
|
|
281
290
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
291
|
+
if (!workflowToSave) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
285
294
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
// Determine the workflow ID
|
|
296
|
+
let workflowId: string;
|
|
297
|
+
if (workflowToSave.id) {
|
|
298
|
+
workflowId = workflowToSave.id;
|
|
299
|
+
} else {
|
|
300
|
+
workflowId = uuidv4();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Create workflow object for saving
|
|
304
|
+
const finalWorkflow = {
|
|
305
|
+
id: workflowId,
|
|
306
|
+
name: workflowToSave.name || 'Untitled Workflow',
|
|
307
|
+
description: workflowToSave.description || '',
|
|
308
|
+
nodes: workflowToSave.nodes || [],
|
|
309
|
+
edges: workflowToSave.edges || [],
|
|
310
|
+
metadata: {
|
|
311
|
+
version: '1.0.0',
|
|
312
|
+
createdAt: workflowToSave.metadata?.createdAt || new Date().toISOString(),
|
|
313
|
+
updatedAt: new Date().toISOString()
|
|
292
314
|
}
|
|
315
|
+
};
|
|
293
316
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
const savedWorkflow = await workflowApi.saveWorkflow(finalWorkflow);
|
|
318
|
+
|
|
319
|
+
// Update the workflow ID if it changed (new workflow)
|
|
320
|
+
// Keep our current workflow state, only update ID and metadata from backend
|
|
321
|
+
if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
|
|
322
|
+
workflowActions.batchUpdate({
|
|
323
|
+
nodes: finalWorkflow.nodes,
|
|
324
|
+
edges: finalWorkflow.edges,
|
|
325
|
+
name: finalWorkflow.name,
|
|
301
326
|
metadata: {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
updatedAt: new Date().toISOString()
|
|
327
|
+
...finalWorkflow.metadata,
|
|
328
|
+
...savedWorkflow.metadata
|
|
305
329
|
}
|
|
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
|
|
330
|
+
});
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
|
|
@@ -362,16 +368,16 @@
|
|
|
362
368
|
link.download = `${finalWorkflow.name}.json`;
|
|
363
369
|
link.click();
|
|
364
370
|
URL.revokeObjectURL(url);
|
|
365
|
-
} catch
|
|
371
|
+
} catch {
|
|
366
372
|
// Export failed
|
|
367
373
|
}
|
|
368
374
|
}
|
|
369
375
|
|
|
370
376
|
// Expose save and export functions globally for external access
|
|
371
377
|
if (typeof window !== 'undefined') {
|
|
372
|
-
// @ts-
|
|
378
|
+
// @ts-expect-error - Adding to window for external access
|
|
373
379
|
window.flowdropSave = saveWorkflow;
|
|
374
|
-
// @ts-
|
|
380
|
+
// @ts-expect-error - Adding to window for external access
|
|
375
381
|
window.flowdropExport = exportWorkflow;
|
|
376
382
|
}
|
|
377
383
|
|
|
@@ -649,7 +655,7 @@
|
|
|
649
655
|
|
|
650
656
|
<!-- Render configuration fields based on schema -->
|
|
651
657
|
{#if configSchema.properties}
|
|
652
|
-
{#each Object.entries(configSchema.properties) as [key, field]}
|
|
658
|
+
{#each Object.entries(configSchema.properties) as [key, field] (key)}
|
|
653
659
|
{@const fieldConfig = field as any}
|
|
654
660
|
{#if fieldConfig.format !== 'hidden'}
|
|
655
661
|
<div class="flowdrop-config-sidebar__field">
|
|
@@ -659,7 +665,7 @@
|
|
|
659
665
|
{#if fieldConfig.enum && fieldConfig.multiple}
|
|
660
666
|
<!-- Checkboxes for enum with multiple selection -->
|
|
661
667
|
<div class="flowdrop-config-sidebar__checkbox-group">
|
|
662
|
-
{#each fieldConfig.enum as option}
|
|
668
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
663
669
|
<label class="flowdrop-config-sidebar__checkbox-item">
|
|
664
670
|
<input
|
|
665
671
|
type="checkbox"
|
|
@@ -696,7 +702,7 @@
|
|
|
696
702
|
class="flowdrop-config-sidebar__select"
|
|
697
703
|
bind:value={configValues[key]}
|
|
698
704
|
>
|
|
699
|
-
{#each fieldConfig.enum as option}
|
|
705
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
700
706
|
<option value={String(option)}>{String(option)}</option>
|
|
701
707
|
{/each}
|
|
702
708
|
</select>
|
|
@@ -742,7 +748,7 @@
|
|
|
742
748
|
bind:value={configValues[key]}
|
|
743
749
|
>
|
|
744
750
|
{#if fieldConfig.options}
|
|
745
|
-
{#each fieldConfig.options as option}
|
|
751
|
+
{#each fieldConfig.options as option (String(option.value))}
|
|
746
752
|
{@const optionConfig = option as any}
|
|
747
753
|
<option value={String(optionConfig.value)}
|
|
748
754
|
>{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
|
}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
let { pipelineId, workflow, apiClient, baseUrl, onActionsReady }: Props = $props();
|
|
31
31
|
|
|
32
32
|
// Initialize API client if not provided
|
|
33
|
-
const client = apiClient || new FlowDropApiClient(baseUrl ||
|
|
33
|
+
const client = apiClient || new FlowDropApiClient(baseUrl || "/api/flowdrop");
|
|
34
34
|
|
|
35
35
|
// Pipeline status and job data
|
|
36
36
|
let pipelineStatus = $state<string>('unknown');
|
|
@@ -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);
|
|
@@ -93,13 +93,6 @@
|
|
|
93
93
|
props.data.metadata?.outputs?.find((port) => port.dataType !== 'trigger')
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
// Use trigger port if present, otherwise use first data port
|
|
97
|
-
let firstInputPort = $derived(triggerInputPort || firstDataInputPort);
|
|
98
|
-
let firstOutputPort = $derived(triggerOutputPort || firstDataOutputPort);
|
|
99
|
-
|
|
100
|
-
let hasInput = $derived(!!firstInputPort);
|
|
101
|
-
let hasOutput = $derived(!!firstOutputPort);
|
|
102
|
-
|
|
103
96
|
// Check if we need to show both trigger and data ports
|
|
104
97
|
let hasBothInputTypes = $derived(!!triggerInputPort && !!firstDataInputPort);
|
|
105
98
|
let hasBothOutputTypes = $derived(!!triggerOutputPort && !!firstDataOutputPort);
|
|
@@ -20,14 +20,6 @@
|
|
|
20
20
|
} from '../utils/nodeWrapper.js';
|
|
21
21
|
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
22
22
|
|
|
23
|
-
interface Props {
|
|
24
|
-
data: WorkflowNode['data'] & {
|
|
25
|
-
nodeId?: string;
|
|
26
|
-
onConfigOpen?: (node: { id: string; type: string; data: WorkflowNode['data'] }) => void;
|
|
27
|
-
};
|
|
28
|
-
selected?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
let {
|
|
32
24
|
data,
|
|
33
25
|
selected = false
|