@d34dman/flowdrop 0.0.14 → 0.0.16

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.
@@ -1,19 +1,176 @@
1
- import { writable, derived } from 'svelte/store';
2
- // Global workflow state
1
+ /**
2
+ * Workflow Store for FlowDrop
3
+ *
4
+ * Provides global state management for workflows with dirty state tracking.
5
+ *
6
+ * @module stores/workflowStore
7
+ */
8
+ import { writable, derived, get } from "svelte/store";
9
+ // =========================================================================
10
+ // Core Workflow Store
11
+ // =========================================================================
12
+ /** Global workflow state */
3
13
  export const workflowStore = writable(null);
4
- // Derived stores for individual workflow properties
5
- export const workflowId = derived(workflowStore, ($workflow) => $workflow?.id || null);
6
- export const workflowName = derived(workflowStore, ($workflow) => $workflow?.name || 'Untitled Workflow');
7
- export const workflowNodes = derived(workflowStore, ($workflow) => $workflow?.nodes || []);
8
- export const workflowEdges = derived(workflowStore, ($workflow) => $workflow?.edges || []);
9
- export const workflowMetadata = derived(workflowStore, ($workflow) => $workflow?.metadata || {
10
- version: '1.0.0',
14
+ // =========================================================================
15
+ // Dirty State Tracking
16
+ // =========================================================================
17
+ /**
18
+ * Store for tracking if there are unsaved changes
19
+ *
20
+ * This is set to true whenever the workflow changes after initialization.
21
+ * It can be reset to false by calling markAsSaved().
22
+ */
23
+ export const isDirtyStore = writable(false);
24
+ /**
25
+ * Snapshot of the workflow when it was last saved
26
+ *
27
+ * Used to compare current state with saved state.
28
+ */
29
+ let savedSnapshot = null;
30
+ /**
31
+ * Callback for dirty state changes
32
+ *
33
+ * Set by the App component to notify parent application.
34
+ */
35
+ let onDirtyStateChangeCallback = null;
36
+ /**
37
+ * Callback for workflow changes
38
+ *
39
+ * Set by the App component to notify parent application.
40
+ */
41
+ let onWorkflowChangeCallback = null;
42
+ /**
43
+ * Set the dirty state change callback
44
+ *
45
+ * @param callback - Function to call when dirty state changes
46
+ */
47
+ export function setOnDirtyStateChange(callback) {
48
+ onDirtyStateChangeCallback = callback;
49
+ }
50
+ /**
51
+ * Set the workflow change callback
52
+ *
53
+ * @param callback - Function to call when workflow changes
54
+ */
55
+ export function setOnWorkflowChange(callback) {
56
+ onWorkflowChangeCallback = callback;
57
+ }
58
+ /**
59
+ * Create a snapshot of the workflow for comparison
60
+ *
61
+ * @param workflow - The workflow to snapshot
62
+ * @returns A JSON string representation for comparison
63
+ */
64
+ function createSnapshot(workflow) {
65
+ if (!workflow)
66
+ return null;
67
+ // Only include the parts that matter for "dirty" detection
68
+ const toSnapshot = {
69
+ name: workflow.name,
70
+ description: workflow.description,
71
+ nodes: workflow.nodes.map((n) => ({
72
+ id: n.id,
73
+ position: n.position,
74
+ data: {
75
+ label: n.data.label,
76
+ config: n.data.config
77
+ }
78
+ })),
79
+ edges: workflow.edges.map((e) => ({
80
+ id: e.id,
81
+ source: e.source,
82
+ target: e.target,
83
+ sourceHandle: e.sourceHandle,
84
+ targetHandle: e.targetHandle
85
+ }))
86
+ };
87
+ return JSON.stringify(toSnapshot);
88
+ }
89
+ /**
90
+ * Update dirty state based on current workflow
91
+ *
92
+ * Compares current workflow with saved snapshot.
93
+ */
94
+ function updateDirtyState() {
95
+ const currentWorkflow = get(workflowStore);
96
+ const currentSnapshot = createSnapshot(currentWorkflow);
97
+ const isDirty = currentSnapshot !== savedSnapshot;
98
+ const previousDirty = get(isDirtyStore);
99
+ if (isDirty !== previousDirty) {
100
+ isDirtyStore.set(isDirty);
101
+ if (onDirtyStateChangeCallback) {
102
+ onDirtyStateChangeCallback(isDirty);
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Mark the workflow change and update dirty state
108
+ *
109
+ * @param changeType - The type of change that occurred
110
+ */
111
+ function notifyWorkflowChange(changeType) {
112
+ const workflow = get(workflowStore);
113
+ if (workflow && onWorkflowChangeCallback) {
114
+ onWorkflowChangeCallback(workflow, changeType);
115
+ }
116
+ updateDirtyState();
117
+ }
118
+ /**
119
+ * Mark the current workflow state as saved
120
+ *
121
+ * Clears the dirty state by updating the saved snapshot.
122
+ */
123
+ export function markAsSaved() {
124
+ const currentWorkflow = get(workflowStore);
125
+ savedSnapshot = createSnapshot(currentWorkflow);
126
+ isDirtyStore.set(false);
127
+ if (onDirtyStateChangeCallback) {
128
+ onDirtyStateChangeCallback(false);
129
+ }
130
+ }
131
+ /**
132
+ * Check if there are unsaved changes
133
+ *
134
+ * @returns true if there are unsaved changes
135
+ */
136
+ export function isDirty() {
137
+ return get(isDirtyStore);
138
+ }
139
+ /**
140
+ * Get the current workflow
141
+ *
142
+ * @returns The current workflow or null
143
+ */
144
+ export function getWorkflow() {
145
+ return get(workflowStore);
146
+ }
147
+ // =========================================================================
148
+ // Derived Stores
149
+ // =========================================================================
150
+ /** Derived store for workflow ID */
151
+ export const workflowId = derived(workflowStore, ($workflow) => $workflow?.id ?? null);
152
+ /** Derived store for workflow name */
153
+ export const workflowName = derived(workflowStore, ($workflow) => $workflow?.name ?? "Untitled Workflow");
154
+ /** Derived store for workflow nodes */
155
+ export const workflowNodes = derived(workflowStore, ($workflow) => $workflow?.nodes ?? []);
156
+ /** Derived store for workflow edges */
157
+ export const workflowEdges = derived(workflowStore, ($workflow) => $workflow?.edges ?? []);
158
+ /** Derived store for workflow metadata */
159
+ export const workflowMetadata = derived(workflowStore, ($workflow) => $workflow?.metadata ?? {
160
+ version: "1.0.0",
11
161
  createdAt: new Date().toISOString(),
12
162
  updatedAt: new Date().toISOString(),
13
163
  versionId: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
14
164
  updateNumber: 0
15
165
  });
16
- // Helper function to check if workflow data has actually changed
166
+ // =========================================================================
167
+ // Helper Functions
168
+ // =========================================================================
169
+ /**
170
+ * Check if workflow data has actually changed
171
+ *
172
+ * Used to prevent unnecessary updates and infinite loops.
173
+ */
17
174
  function hasWorkflowDataChanged(currentWorkflow, newNodes, newEdges) {
18
175
  if (!currentWorkflow)
19
176
  return true;
@@ -48,17 +205,40 @@ function hasWorkflowDataChanged(currentWorkflow, newNodes, newEdges) {
48
205
  }
49
206
  return false;
50
207
  }
51
- // Actions for updating the workflow
208
+ // =========================================================================
209
+ // Workflow Actions
210
+ // =========================================================================
211
+ /**
212
+ * Actions for updating the workflow
213
+ *
214
+ * All actions that modify the workflow will trigger dirty state updates
215
+ * and emit change events.
216
+ */
52
217
  export const workflowActions = {
53
- // Initialize workflow
218
+ /**
219
+ * Initialize workflow (from load or new)
220
+ *
221
+ * This sets the initial saved snapshot and clears dirty state.
222
+ */
54
223
  initialize: (workflow) => {
55
224
  workflowStore.set(workflow);
225
+ // Set the saved snapshot - workflow is "clean" after initialization
226
+ savedSnapshot = createSnapshot(workflow);
227
+ isDirtyStore.set(false);
228
+ if (onDirtyStateChangeCallback) {
229
+ onDirtyStateChangeCallback(false);
230
+ }
56
231
  },
57
- // Update the entire workflow
232
+ /**
233
+ * Update the entire workflow
234
+ */
58
235
  updateWorkflow: (workflow) => {
59
236
  workflowStore.set(workflow);
237
+ notifyWorkflowChange("metadata");
60
238
  },
61
- // Update nodes
239
+ /**
240
+ * Update nodes
241
+ */
62
242
  updateNodes: (nodes) => {
63
243
  workflowStore.update(($workflow) => {
64
244
  if (!$workflow)
@@ -76,12 +256,15 @@ export const workflowActions = {
76
256
  ...$workflow.metadata,
77
257
  updatedAt: new Date().toISOString(),
78
258
  versionId,
79
- updateNumber: ($workflow.metadata?.updateNumber || 0) + 1
259
+ updateNumber: ($workflow.metadata?.updateNumber ?? 0) + 1
80
260
  }
81
261
  };
82
262
  });
263
+ notifyWorkflowChange("node_move");
83
264
  },
84
- // Update edges
265
+ /**
266
+ * Update edges
267
+ */
85
268
  updateEdges: (edges) => {
86
269
  workflowStore.update(($workflow) => {
87
270
  if (!$workflow)
@@ -99,12 +282,15 @@ export const workflowActions = {
99
282
  ...$workflow.metadata,
100
283
  updatedAt: new Date().toISOString(),
101
284
  versionId,
102
- updateNumber: ($workflow.metadata?.updateNumber || 0) + 1
285
+ updateNumber: ($workflow.metadata?.updateNumber ?? 0) + 1
103
286
  }
104
287
  };
105
288
  });
289
+ notifyWorkflowChange("edge_add");
106
290
  },
107
- // Update workflow name
291
+ /**
292
+ * Update workflow name
293
+ */
108
294
  updateName: (name) => {
109
295
  workflowStore.update(($workflow) => {
110
296
  if (!$workflow)
@@ -118,8 +304,11 @@ export const workflowActions = {
118
304
  }
119
305
  };
120
306
  });
307
+ notifyWorkflowChange("name");
121
308
  },
122
- // Add a node
309
+ /**
310
+ * Add a node
311
+ */
123
312
  addNode: (node) => {
124
313
  workflowStore.update(($workflow) => {
125
314
  if (!$workflow)
@@ -133,8 +322,11 @@ export const workflowActions = {
133
322
  }
134
323
  };
135
324
  });
325
+ notifyWorkflowChange("node_add");
136
326
  },
137
- // Remove a node
327
+ /**
328
+ * Remove a node
329
+ */
138
330
  removeNode: (nodeId) => {
139
331
  workflowStore.update(($workflow) => {
140
332
  if (!$workflow)
@@ -149,8 +341,11 @@ export const workflowActions = {
149
341
  }
150
342
  };
151
343
  });
344
+ notifyWorkflowChange("node_remove");
152
345
  },
153
- // Add an edge
346
+ /**
347
+ * Add an edge
348
+ */
154
349
  addEdge: (edge) => {
155
350
  workflowStore.update(($workflow) => {
156
351
  if (!$workflow)
@@ -164,8 +359,11 @@ export const workflowActions = {
164
359
  }
165
360
  };
166
361
  });
362
+ notifyWorkflowChange("edge_add");
167
363
  },
168
- // Remove an edge
364
+ /**
365
+ * Remove an edge
366
+ */
169
367
  removeEdge: (edgeId) => {
170
368
  workflowStore.update(($workflow) => {
171
369
  if (!$workflow)
@@ -179,27 +377,40 @@ export const workflowActions = {
179
377
  }
180
378
  };
181
379
  });
380
+ notifyWorkflowChange("edge_remove");
182
381
  },
183
- // Update a specific node
382
+ /**
383
+ * Update a specific node
384
+ */
184
385
  updateNode: (nodeId, updates) => {
185
386
  workflowStore.update(($workflow) => {
186
387
  if (!$workflow)
187
388
  return null;
188
389
  return {
189
390
  ...$workflow,
190
- nodes: $workflow.nodes.map((node) => (node.id === nodeId ? { ...node, ...updates } : node)),
391
+ nodes: $workflow.nodes.map((node) => node.id === nodeId ? { ...node, ...updates } : node),
191
392
  metadata: {
192
393
  ...$workflow.metadata,
193
394
  updatedAt: new Date().toISOString()
194
395
  }
195
396
  };
196
397
  });
398
+ notifyWorkflowChange("node_config");
197
399
  },
198
- // Clear the workflow
400
+ /**
401
+ * Clear the workflow
402
+ */
199
403
  clear: () => {
200
404
  workflowStore.set(null);
405
+ savedSnapshot = null;
406
+ isDirtyStore.set(false);
407
+ if (onDirtyStateChangeCallback) {
408
+ onDirtyStateChangeCallback(false);
409
+ }
201
410
  },
202
- // Update workflow metadata
411
+ /**
412
+ * Update workflow metadata
413
+ */
203
414
  updateMetadata: (metadata) => {
204
415
  workflowStore.update(($workflow) => {
205
416
  if (!$workflow)
@@ -213,8 +424,13 @@ export const workflowActions = {
213
424
  }
214
425
  };
215
426
  });
427
+ notifyWorkflowChange("metadata");
216
428
  },
217
- // Batch update nodes and edges (useful for complex operations)
429
+ /**
430
+ * Batch update nodes and edges
431
+ *
432
+ * Useful for complex operations that update multiple things at once.
433
+ */
218
434
  batchUpdate: (updates) => {
219
435
  workflowStore.update(($workflow) => {
220
436
  if (!$workflow)
@@ -232,11 +448,15 @@ export const workflowActions = {
232
448
  }
233
449
  };
234
450
  });
451
+ notifyWorkflowChange("metadata");
235
452
  }
236
453
  };
237
- // Derived store for workflow changes (useful for triggering saves)
454
+ // =========================================================================
455
+ // Additional Derived Stores
456
+ // =========================================================================
457
+ /** Derived store for workflow changes (useful for triggering saves) */
238
458
  export const workflowChanged = derived([workflowNodes, workflowEdges, workflowName], ([nodes, edges, name]) => ({ nodes, edges, name }));
239
- // Derived store for workflow validation
459
+ /** Derived store for workflow validation */
240
460
  export const workflowValidation = derived([workflowNodes, workflowEdges], ([nodes, edges]) => ({
241
461
  hasNodes: nodes.length > 0,
242
462
  hasEdges: edges.length > 0,
@@ -244,9 +464,9 @@ export const workflowValidation = derived([workflowNodes, workflowEdges], ([node
244
464
  edgeCount: edges.length,
245
465
  isValid: nodes.length > 0 && edges.length >= 0
246
466
  }));
247
- // Derived store for workflow metadata changes
467
+ /** Derived store for workflow metadata changes */
248
468
  export const workflowMetadataChanged = derived(workflowMetadata, (metadata) => ({
249
469
  createdAt: metadata.createdAt,
250
470
  updatedAt: metadata.updatedAt,
251
- version: metadata.version || '1.0.0'
471
+ version: metadata.version ?? "1.0.0"
252
472
  }));
@@ -1,10 +1,15 @@
1
1
  /**
2
2
  * Svelte App Wrapper for Framework Integration
3
- * Provides mount/unmount functions for integrating FlowDrop into any web application
4
- * Particularly useful for integration with vanilla JS, Drupal, WordPress, or other frameworks
3
+ *
4
+ * Provides mount/unmount functions for integrating FlowDrop into any web application.
5
+ * Particularly useful for integration with vanilla JS, Drupal, WordPress, or other frameworks.
6
+ *
7
+ * @module svelte-app
5
8
  */
6
- import type { Workflow, NodeMetadata, PortConfig } from './types/index.js';
7
- import type { EndpointConfig } from './config/endpoints.js';
9
+ import type { Workflow, NodeMetadata, PortConfig } from "./types/index.js";
10
+ import type { EndpointConfig } from "./config/endpoints.js";
11
+ import type { AuthProvider } from "./types/auth.js";
12
+ import type { FlowDropEventHandlers, FlowDropFeatures } from "./types/events.js";
8
13
  declare global {
9
14
  interface Window {
10
15
  flowdropSave?: () => Promise<void>;
@@ -12,57 +17,134 @@ declare global {
12
17
  }
13
18
  }
14
19
  /**
15
- * Return type for mounted Svelte app
20
+ * Navbar action configuration
16
21
  */
17
- interface MountedSvelteApp {
18
- destroy: () => void;
19
- save?: () => Promise<void>;
20
- export?: () => void;
22
+ export interface NavbarAction {
23
+ label: string;
24
+ href: string;
25
+ icon?: string;
26
+ variant?: "primary" | "secondary" | "outline";
27
+ onclick?: (event: Event) => void;
21
28
  }
22
29
  /**
23
- * Mount the full FlowDrop App with navbar, sidebars, and workflow editor
24
- * Use this for a complete workflow editing experience with all UI components
25
- * @param container - DOM element to mount the app into
26
- * @param options - Configuration options for the app
30
+ * Mount options for FlowDrop App
27
31
  */
28
- export declare function mountFlowDropApp(container: HTMLElement, options?: {
32
+ export interface FlowDropMountOptions {
33
+ /** Initial workflow to load */
29
34
  workflow?: Workflow;
35
+ /** Available node types */
30
36
  nodes?: NodeMetadata[];
37
+ /** API endpoint configuration */
31
38
  endpointConfig?: EndpointConfig;
39
+ /** Port configuration for connections */
32
40
  portConfig?: PortConfig;
41
+ /** Editor height */
33
42
  height?: string | number;
43
+ /** Editor width */
34
44
  width?: string | number;
45
+ /** Show the navbar */
35
46
  showNavbar?: boolean;
47
+ /** Disable the node sidebar */
36
48
  disableSidebar?: boolean;
49
+ /** Lock the workflow (prevent changes) */
37
50
  lockWorkflow?: boolean;
51
+ /** Read-only mode */
38
52
  readOnly?: boolean;
39
- nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
53
+ /** Pipeline ID for status display */
40
54
  pipelineId?: string;
55
+ /** Node execution statuses */
56
+ nodeStatuses?: Record<string, "pending" | "running" | "completed" | "error">;
57
+ /** Custom navbar title */
41
58
  navbarTitle?: string;
42
- navbarActions?: Array<{
43
- label: string;
44
- href: string;
45
- icon?: string;
46
- variant?: 'primary' | 'secondary' | 'outline';
47
- onclick?: (event: Event) => void;
48
- }>;
49
- }): Promise<MountedSvelteApp>;
59
+ /** Custom navbar actions */
60
+ navbarActions?: NavbarAction[];
61
+ /** Authentication provider for API requests */
62
+ authProvider?: AuthProvider;
63
+ /** Event handlers for workflow lifecycle */
64
+ eventHandlers?: FlowDropEventHandlers;
65
+ /** Feature configuration */
66
+ features?: FlowDropFeatures;
67
+ /** Custom storage key for localStorage drafts */
68
+ draftStorageKey?: string;
69
+ }
70
+ /**
71
+ * Return type for mounted FlowDrop app
72
+ */
73
+ export interface MountedFlowDropApp {
74
+ /**
75
+ * Destroy the app and clean up resources
76
+ */
77
+ destroy: () => void;
78
+ /**
79
+ * Check if there are unsaved changes
80
+ */
81
+ isDirty: () => boolean;
82
+ /**
83
+ * Mark the workflow as saved (clears dirty state)
84
+ */
85
+ markAsSaved: () => void;
86
+ /**
87
+ * Get the current workflow data
88
+ */
89
+ getWorkflow: () => Workflow | null;
90
+ /**
91
+ * Trigger save operation
92
+ */
93
+ save: () => Promise<void>;
94
+ /**
95
+ * Trigger export operation (downloads JSON)
96
+ */
97
+ export: () => void;
98
+ }
99
+ /**
100
+ * Mount the full FlowDrop App with navbar, sidebars, and workflow editor
101
+ *
102
+ * Use this for a complete workflow editing experience with all UI components.
103
+ *
104
+ * @param container - DOM element to mount the app into
105
+ * @param options - Configuration options for the app
106
+ * @returns Promise resolving to a MountedFlowDropApp instance
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const app = await mountFlowDropApp(document.getElementById("editor"), {
111
+ * workflow: myWorkflow,
112
+ * endpointConfig: createEndpointConfig("/api/flowdrop"),
113
+ * authProvider: new CallbackAuthProvider({
114
+ * getToken: () => authService.getAccessToken()
115
+ * }),
116
+ * eventHandlers: {
117
+ * onDirtyStateChange: (isDirty) => updateSaveButton(isDirty),
118
+ * onAfterSave: () => showSuccess("Saved!")
119
+ * }
120
+ * });
121
+ * ```
122
+ */
123
+ export declare function mountFlowDropApp(container: HTMLElement, options?: FlowDropMountOptions): Promise<MountedFlowDropApp>;
50
124
  /**
51
125
  * Mount the WorkflowEditor component in a container
52
- * Simpler alternative to mountFlowDropApp - only mounts the editor without navbar
126
+ *
127
+ * Simpler alternative to mountFlowDropApp - only mounts the editor without navbar.
128
+ *
129
+ * @param container - DOM element to mount the editor into
130
+ * @param options - Configuration options
131
+ * @returns Promise resolving to a MountedFlowDropApp instance
53
132
  */
54
133
  export declare function mountWorkflowEditor(container: HTMLElement, options?: {
55
134
  workflow?: Workflow;
56
135
  nodes?: NodeMetadata[];
57
136
  endpointConfig?: EndpointConfig;
58
137
  portConfig?: PortConfig;
59
- }): Promise<MountedSvelteApp>;
138
+ }): Promise<MountedFlowDropApp>;
60
139
  /**
61
- * Unmount a Svelte app (works for both App and WorkflowEditor)
140
+ * Unmount a FlowDrop app
141
+ *
142
+ * @param app - The mounted app to unmount
62
143
  */
63
- export declare function unmountFlowDropApp(app: MountedSvelteApp): void;
144
+ export declare function unmountFlowDropApp(app: MountedFlowDropApp): void;
64
145
  /**
65
- * Unmount a Svelte app (alias for backward compatibility)
146
+ * Unmount a FlowDrop app (alias for backward compatibility)
147
+ *
148
+ * @param app - The mounted app to unmount
66
149
  */
67
- export declare function unmountWorkflowEditor(app: MountedSvelteApp): void;
68
- export {};
150
+ export declare function unmountWorkflowEditor(app: MountedFlowDropApp): void;