@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.
@@ -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 testUrl = '/api/flowdrop/nodes';
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('API connection test', 'Connection successful');
174
+ apiToasts.success("API connection test", "Connection successful");
176
175
  } else {
177
- apiToasts.error('API connection test', 'Connection failed');
176
+ apiToasts.error("API connection test", "Connection failed");
178
177
  }
179
178
  } catch (err) {
180
- apiToasts.error('API connection test', err instanceof Error ? err.message : 'Unknown 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
- // Use default API base URL - can be overridden via runtime configuration
189
- const apiBaseUrl = '/api/flowdrop';
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
- const config = createEndpointConfig(apiBaseUrl, {
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
- try {
272
- // Wait for any pending DOM updates before saving
273
- await tick();
281
+ // Wait for any pending DOM updates before saving
282
+ await tick();
274
283
 
275
- // Import necessary modules
276
- const { workflowApi } = await import('../services/api.js');
277
- const { v4: uuidv4 } = await import('uuid');
284
+ // Import necessary modules
285
+ const { workflowApi } = await import('../services/api.js');
286
+ const { v4: uuidv4 } = await import('uuid');
278
287
 
279
- // Use current workflow from global store
280
- const workflowToSave = $workflowStore;
288
+ // Use current workflow from global store
289
+ const workflowToSave = $workflowStore;
281
290
 
282
- if (!workflowToSave) {
283
- return;
284
- }
291
+ if (!workflowToSave) {
292
+ return;
293
+ }
285
294
 
286
- // Determine the workflow ID
287
- let workflowId: string;
288
- if (workflowToSave.id) {
289
- workflowId = workflowToSave.id;
290
- } else {
291
- workflowId = uuidv4();
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
- // Create workflow object for saving
295
- const finalWorkflow = {
296
- id: workflowId,
297
- name: workflowToSave.name || 'Untitled Workflow',
298
- description: workflowToSave.description || '',
299
- nodes: workflowToSave.nodes || [],
300
- edges: workflowToSave.edges || [],
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
- version: '1.0.0',
303
- createdAt: workflowToSave.metadata?.createdAt || new Date().toISOString(),
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 (error) {
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-ignore - Adding to window for external access
378
+ // @ts-expect-error - Adding to window for external access
373
379
  window.flowdropSave = saveWorkflow;
374
- // @ts-ignore - Adding to window for external access
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
@@ -17,6 +17,7 @@ interface Props {
17
17
  variant?: 'primary' | 'secondary' | 'outline';
18
18
  onclick?: (event: Event) => void;
19
19
  }>;
20
+ apiBaseUrl?: string;
20
21
  }
21
22
  declare const App: import("svelte").Component<Props, {}, "">;
22
23
  type App = ReturnType<typeof App>;
@@ -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 Set<NodeCategory>();
33
+ const categories = new SvelteSet<NodeCategory>();
33
34
  nodes.forEach((node) => categories.add(node.category));
34
35
  return Array.from(categories).sort();
35
36
  }
@@ -19,7 +19,6 @@
19
19
  } from '../utils/nodeStatus.js';
20
20
 
21
21
  interface Props {
22
- nodeId: string;
23
22
  executionInfo?: NodeExecutionInfo;
24
23
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
25
24
  size?: 'sm' | 'md' | 'lg';
@@ -1,6 +1,5 @@
1
1
  import type { NodeExecutionInfo } from '../types/index.js';
2
2
  interface Props {
3
- nodeId: string;
4
3
  executionInfo?: NodeExecutionInfo;
5
4
  position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
5
  size?: 'sm' | 'md' | 'lg';
@@ -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 || window.location.origin);
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