@d34dman/flowdrop 0.0.1

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.
Files changed (46) hide show
  1. package/README.md +293 -0
  2. package/dist/adapters/WorkflowAdapter.d.ts +166 -0
  3. package/dist/adapters/WorkflowAdapter.js +337 -0
  4. package/dist/api/client.d.ts +79 -0
  5. package/dist/api/client.js +208 -0
  6. package/dist/app.css +0 -0
  7. package/dist/clients/ApiClient.d.ts +203 -0
  8. package/dist/clients/ApiClient.js +212 -0
  9. package/dist/components/App.svelte +237 -0
  10. package/dist/components/App.svelte.d.ts +3 -0
  11. package/dist/components/CanvasBanner.svelte +51 -0
  12. package/dist/components/CanvasBanner.svelte.d.ts +22 -0
  13. package/dist/components/LoadingSpinner.svelte +36 -0
  14. package/dist/components/LoadingSpinner.svelte.d.ts +8 -0
  15. package/dist/components/Node.svelte +38 -0
  16. package/dist/components/Node.svelte.d.ts +4 -0
  17. package/dist/components/NodeSidebar.svelte +500 -0
  18. package/dist/components/NodeSidebar.svelte.d.ts +9 -0
  19. package/dist/components/WorkflowEditor.svelte +542 -0
  20. package/dist/components/WorkflowEditor.svelte.d.ts +10 -0
  21. package/dist/components/WorkflowNode.svelte +558 -0
  22. package/dist/components/WorkflowNode.svelte.d.ts +11 -0
  23. package/dist/data/samples.d.ts +17 -0
  24. package/dist/data/samples.js +1193 -0
  25. package/dist/examples/adapter-usage.d.ts +66 -0
  26. package/dist/examples/adapter-usage.js +138 -0
  27. package/dist/examples/api-client-usage.d.ts +31 -0
  28. package/dist/examples/api-client-usage.js +241 -0
  29. package/dist/index.d.ts +19 -0
  30. package/dist/index.js +27 -0
  31. package/dist/services/api.d.ts +110 -0
  32. package/dist/services/api.js +149 -0
  33. package/dist/services/workflowStorage.d.ts +37 -0
  34. package/dist/services/workflowStorage.js +116 -0
  35. package/dist/styles/base.css +858 -0
  36. package/dist/svelte-app.d.ts +17 -0
  37. package/dist/svelte-app.js +30 -0
  38. package/dist/types/index.d.ts +179 -0
  39. package/dist/types/index.js +4 -0
  40. package/dist/utils/colors.d.ts +121 -0
  41. package/dist/utils/colors.js +240 -0
  42. package/dist/utils/connections.d.ts +47 -0
  43. package/dist/utils/connections.js +240 -0
  44. package/dist/utils/icons.d.ts +102 -0
  45. package/dist/utils/icons.js +149 -0
  46. package/package.json +99 -0
@@ -0,0 +1,542 @@
1
+ <!--
2
+ Workflow Editor Component
3
+ Main workflow editor with sidebar and flow canvas
4
+ Styled with BEM syntax
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import {
9
+ SvelteFlow,
10
+ ConnectionLineType,
11
+ // @ts-ignore
12
+ Controls,
13
+ // @ts-ignore
14
+ Background,
15
+ // @ts-ignore
16
+ MiniMap,
17
+ // @ts-ignore
18
+ SvelteFlowProvider,
19
+ } from '@xyflow/svelte';
20
+ import "@xyflow/svelte/dist/style.css";
21
+ import NodeSidebar from "./NodeSidebar.svelte";
22
+ import WorkflowNode from "./WorkflowNode.svelte";
23
+ import type { WorkflowNode as WorkflowNodeType, NodeMetadata, Workflow, WorkflowEdge } from "../types/index.js";
24
+ import { validateConnection, hasCycles } from "../utils/connections.js";
25
+ import CanvasBanner from "./CanvasBanner.svelte";
26
+ import { workflowApi, setApiBaseUrl } from "../services/api.js";
27
+ import { v4 as uuidv4 } from "uuid";
28
+
29
+ interface Props {
30
+ nodes: NodeMetadata[];
31
+ workflow?: Workflow;
32
+ apiBaseUrl?: string;
33
+ }
34
+
35
+ let props: Props = $props();
36
+
37
+ // Initialize from props only once, not on every re-render
38
+ let isInitialized = $state(false);
39
+ let flowNodes = $state<WorkflowNodeType[]>([]);
40
+ let flowEdges = $state<WorkflowEdge[]>([]);
41
+
42
+ $effect(() => {
43
+ console.log('WorkflowEditor: props received:', {
44
+ nodes: props.nodes?.length || 0,
45
+ workflow: props.workflow ? 'present' : 'none',
46
+ apiBaseUrl: props.apiBaseUrl
47
+ });
48
+ console.log('WorkflowEditor: props.nodes content:', props.nodes);
49
+
50
+ if (!isInitialized) {
51
+ if (props.workflow) {
52
+ flowNodes = props.workflow.nodes || [];
53
+ flowEdges = props.workflow.edges || [];
54
+ } else {
55
+ flowNodes = [];
56
+ flowEdges = [];
57
+ }
58
+ isInitialized = true;
59
+ }
60
+ });
61
+
62
+ let workflowName = $state(props.workflow?.name || "Untitled Workflow");
63
+ let isEditingTitle = $state(false);
64
+
65
+ // Node types for Svelte Flow
66
+ const nodeTypes = {
67
+ workflowNode: WorkflowNode
68
+ };
69
+
70
+ $effect(() => {
71
+ if (props.apiBaseUrl) {
72
+ setApiBaseUrl(props.apiBaseUrl);
73
+ }
74
+ });
75
+
76
+ /**
77
+ * Clear workflow
78
+ */
79
+ function clearWorkflow(): void {
80
+ flowNodes = [];
81
+ flowEdges = [];
82
+ }
83
+
84
+ /**
85
+ * Save workflow
86
+ */
87
+ async function saveWorkflow(): Promise<void> {
88
+ try {
89
+ const workflow: Workflow = {
90
+ id: props.workflow?.id || uuidv4(),
91
+ name: workflowName,
92
+ nodes: flowNodes,
93
+ edges: flowEdges,
94
+ metadata: {
95
+ version: "1.0.0",
96
+ createdAt: props.workflow?.metadata?.createdAt || new Date().toISOString(),
97
+ updatedAt: new Date().toISOString()
98
+ }
99
+ };
100
+
101
+ const savedWorkflow = await workflowApi.saveWorkflow(workflow);
102
+ console.log("Workflow saved successfully:", savedWorkflow);
103
+
104
+ // Update the workflow ID if it was a new workflow
105
+ if (!props.workflow?.id) {
106
+ // Note: In a real app, you'd want to update the parent component's workflow prop
107
+ console.log("New workflow created with ID:", savedWorkflow.id);
108
+ }
109
+ } catch (error) {
110
+ console.error("Failed to save workflow:", error);
111
+ // Here you would typically show a user-friendly error message
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Export workflow
117
+ */
118
+ function exportWorkflow(): void {
119
+ const workflow: Workflow = {
120
+ id: props.workflow?.id || uuidv4(),
121
+ name: workflowName,
122
+ nodes: flowNodes,
123
+ edges: flowEdges,
124
+ metadata: {
125
+ version: "1.0.0",
126
+ createdAt: props.workflow?.metadata?.createdAt || new Date().toISOString(),
127
+ updatedAt: new Date().toISOString()
128
+ }
129
+ };
130
+
131
+ const dataStr = JSON.stringify(workflow, null, 2);
132
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
133
+ const url = URL.createObjectURL(dataBlob);
134
+ const link = document.createElement("a");
135
+ link.href = url;
136
+ link.download = `${workflow.name}.json`;
137
+ link.click();
138
+ URL.revokeObjectURL(url);
139
+ }
140
+
141
+
142
+ /**
143
+ * Check if workflow has cycles
144
+ */
145
+ function checkWorkflowCycles(): boolean {
146
+ return hasCycles(flowNodes, flowEdges);
147
+ }
148
+
149
+ /**
150
+ * Handle title editing
151
+ */
152
+ function startTitleEdit(): void {
153
+ isEditingTitle = true;
154
+ // Focus the input on next tick
155
+ setTimeout(() => {
156
+ const input = document.querySelector('#workflow-title') as HTMLInputElement;
157
+ if (input) input.focus();
158
+ }, 0);
159
+ }
160
+
161
+ /**
162
+ * Save title changes
163
+ */
164
+ function saveTitle(): void {
165
+ isEditingTitle = false;
166
+ // Update the workflow name in the save/export functions
167
+ }
168
+
169
+ /**
170
+ * Cancel title editing
171
+ */
172
+ function cancelTitleEdit(): void {
173
+ isEditingTitle = false;
174
+ workflowName = props.workflow?.name || "Untitled Workflow";
175
+ }
176
+
177
+ /**
178
+ * Handle title input keydown
179
+ */
180
+ function handleTitleKeydown(event: KeyboardEvent): void {
181
+ if (event.key === "Enter") {
182
+ saveTitle();
183
+ } else if (event.key === "Escape") {
184
+ cancelTitleEdit();
185
+ }
186
+ }
187
+
188
+ </script>
189
+
190
+ <SvelteFlowProvider>
191
+ <div class="flowdrop-workflow-editor">
192
+ <!-- Node Sidebar -->
193
+ <NodeSidebar
194
+ nodes={props.nodes}
195
+ />
196
+
197
+ <!-- Main Editor Area -->
198
+ <div class="flowdrop-workflow-editor__main">
199
+ <!-- Toolbar -->
200
+ <div class="flowdrop-toolbar">
201
+ <div class="flowdrop-toolbar__content">
202
+ <!-- Left side - Workflow info -->
203
+ <div class="flowdrop-toolbar__info">
204
+ {#if isEditingTitle}
205
+ <div class="flowdrop-flex flowdrop-gap--2">
206
+ <input
207
+ id="workflow-title"
208
+ type="text"
209
+ class="flowdrop-input flowdrop-input--lg"
210
+ bind:value={workflowName}
211
+ onkeydown={handleTitleKeydown}
212
+ onblur={saveTitle}
213
+ />
214
+ <button
215
+ class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
216
+ onclick={saveTitle}
217
+ type="button"
218
+ >
219
+
220
+ </button>
221
+ <button
222
+ class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
223
+ onclick={cancelTitleEdit}
224
+ type="button"
225
+ >
226
+
227
+ </button>
228
+ </div>
229
+ {:else}
230
+ <button
231
+ class="flowdrop-workflow-title"
232
+ onclick={startTitleEdit}
233
+ type="button"
234
+ >
235
+ {workflowName}
236
+ </button>
237
+ {/if}
238
+ <div class="flowdrop-workflow-stats">
239
+ <span class="flowdrop-text--sm flowdrop-text--gray">{flowNodes.length} nodes</span>
240
+ <span class="flowdrop-text--sm flowdrop-text--gray">•</span>
241
+ <span class="flowdrop-text--sm flowdrop-text--gray">{flowEdges.length} connections</span>
242
+
243
+ {#if checkWorkflowCycles()}
244
+ <span class="flowdrop-text--sm flowdrop-text--gray">•</span>
245
+ <span class="flowdrop-text--sm flowdrop-font--medium flowdrop-text--error">⚠️ Cycles detected</span>
246
+ {/if}
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Right side - Actions -->
251
+ <div class="flowdrop-toolbar__actions">
252
+ <!-- Workflow Actions -->
253
+ <div class="flowdrop-join">
254
+ <button
255
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline flowdrop-join__item"
256
+ onclick={clearWorkflow}
257
+ type="button"
258
+ >
259
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flowdrop-icon">
260
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
261
+ </svg>
262
+
263
+ Clear
264
+ </button>
265
+ <button
266
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline flowdrop-join__item"
267
+ onclick={exportWorkflow}
268
+ type="button"
269
+ >
270
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flowdrop-icon">
271
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
272
+ </svg>
273
+
274
+ Export
275
+ </button>
276
+ <button
277
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline flowdrop-join__item"
278
+ onclick={saveWorkflow}
279
+ type="button"
280
+ >
281
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flowdrop-icon">
282
+ <path stroke-linecap="round" stroke-linejoin="round" d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z" />
283
+ </svg>
284
+
285
+ Save
286
+ </button>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ </div>
291
+
292
+ <!-- Flow Canvas -->
293
+ <div
294
+ class="flowdrop-canvas"
295
+ role="application"
296
+ aria-label="Workflow canvas"
297
+ ondragover={(e: DragEvent) => {
298
+ e.preventDefault();
299
+ e.dataTransfer!.dropEffect = "copy";
300
+ }}
301
+ ondrop={(e: DragEvent) => {
302
+ e.preventDefault();
303
+
304
+ // Get the data from the drag event
305
+ const nodeTypeData = e.dataTransfer?.getData("application/json");
306
+ if (nodeTypeData) {
307
+ // Get the position relative to the canvas
308
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
309
+ const position = {
310
+ x: e.clientX - rect.left,
311
+ y: e.clientY - rect.top
312
+ };
313
+
314
+ // Create the node manually since SvelteFlow isn't receiving the event
315
+ try {
316
+ const parsedData = JSON.parse(nodeTypeData);
317
+
318
+ // Handle both old format (with type: "node") and new format (direct NodeMetadata)
319
+ let nodeType: NodeMetadata;
320
+ let nodeData: any;
321
+
322
+ if (parsedData.type === "node") {
323
+ // Old format from sidebar
324
+ nodeType = parsedData.nodeData.metadata;
325
+ nodeData = parsedData.nodeData;
326
+ } else {
327
+ // New format (direct NodeMetadata)
328
+ nodeType = parsedData;
329
+ nodeData = {
330
+ label: nodeType.name,
331
+ config: {},
332
+ metadata: nodeType
333
+ };
334
+ }
335
+
336
+ const newNodeId = uuidv4();
337
+
338
+ const newNode: WorkflowNodeType = {
339
+ id: newNodeId,
340
+ type: "workflowNode",
341
+ position, // Use the position calculated from the drop event
342
+ deletable: true,
343
+ data: {
344
+ ...nodeData,
345
+ nodeId: newNodeId // Use the same ID
346
+ }
347
+ };
348
+
349
+ // Add node
350
+ const updatedNodes = [...flowNodes, newNode];
351
+ flowNodes = updatedNodes;
352
+ } catch (error) {
353
+ console.error("Error parsing node data:", error);
354
+ }
355
+ }
356
+ }}
357
+ >
358
+ <SvelteFlow
359
+ bind:nodes={flowNodes}
360
+ bind:edges={flowEdges}
361
+ {nodeTypes}
362
+ clickConnect={true}
363
+ elevateEdgesOnSelect={true}
364
+ connectionLineType={ConnectionLineType.Bezier}
365
+ fitView
366
+ />
367
+ <Controls />
368
+ <Background />
369
+ <MiniMap />
370
+
371
+ <!-- Drop Zone Indicator -->
372
+ {#if flowNodes.length === 0}
373
+ <CanvasBanner title="Drag components here to start building" description="Use the sidebar to add components to your workflow" iconName="mdi:graph" />
374
+ {/if}
375
+ </div>
376
+
377
+ <!-- Status Bar -->
378
+ <div class="flowdrop-status-bar">
379
+ <div class="flowdrop-status-bar__content">
380
+ <div class="flowdrop-flex flowdrop-gap--4">
381
+ <span class="flowdrop-text--xs flowdrop-text--gray">All systems ready. You can start building your workflow.</span>
382
+ </div>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+ </SvelteFlowProvider>
388
+
389
+ <style>
390
+ .flowdrop-workflow-editor {
391
+ display: flex;
392
+ height: 100%;
393
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
394
+ }
395
+
396
+ .flowdrop-workflow-editor__main {
397
+ flex: 1;
398
+ display: flex;
399
+ flex-direction: column;
400
+ min-height: 0;
401
+ }
402
+
403
+ .flowdrop-toolbar {
404
+ background-color: rgba(255, 255, 255, 0.8);
405
+ backdrop-filter: blur(8px);
406
+ border-bottom: 1px solid #e5e7eb;
407
+ padding: 1rem;
408
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
409
+ }
410
+
411
+ .flowdrop-toolbar__content {
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: space-between;
415
+ }
416
+
417
+ .flowdrop-toolbar__info {
418
+ display: flex;
419
+ align-items: center;
420
+ gap: 1rem;
421
+ }
422
+
423
+ .flowdrop-workflow-title {
424
+ font-size: 1.25rem;
425
+ font-weight: 700;
426
+ color: #111827;
427
+ cursor: pointer;
428
+ transition: color 0.2s ease-in-out;
429
+ background: transparent;
430
+ border: none;
431
+ padding: 0;
432
+ }
433
+
434
+ .flowdrop-workflow-title:hover {
435
+ color: #3b82f6;
436
+ }
437
+
438
+ .flowdrop-workflow-stats {
439
+ display: flex;
440
+ align-items: center;
441
+ gap: 0.5rem;
442
+ }
443
+
444
+ .flowdrop-text--error {
445
+ color: #dc2626;
446
+ }
447
+
448
+ .flowdrop-toolbar__actions {
449
+ display: flex;
450
+ align-items: center;
451
+ gap: 0.75rem;
452
+ }
453
+
454
+ .flowdrop-icon {
455
+ width: 1.5rem;
456
+ height: 1.5rem;
457
+ }
458
+
459
+ .flowdrop-canvas {
460
+ flex: 1;
461
+ min-height: 0;
462
+ position: relative;
463
+ }
464
+
465
+ .flowdrop-status-bar {
466
+ background-color: rgba(255, 255, 255, 0.8);
467
+ backdrop-filter: blur(8px);
468
+ border-top: 1px solid #e5e7eb;
469
+ padding: 0.75rem;
470
+ }
471
+
472
+ .flowdrop-status-bar__content {
473
+ display: flex;
474
+ align-items: center;
475
+ justify-content: space-between;
476
+ }
477
+
478
+ :global(.flowdrop-workflow-editor .svelte-flow) {
479
+ background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
480
+ background-image:
481
+ radial-gradient(circle, #d1d5db 1px, transparent 1px);
482
+ background-size: 20px 20px;
483
+ background-position: 0 0, 10px 10px;
484
+ }
485
+
486
+ :global(.flowdrop-workflow-editor .svelte-flow__node:hover) {
487
+ transform: translateY(-2px);
488
+ }
489
+
490
+ :global(.flowdrop-workflow-editor .svelte-flow__edge) {
491
+ stroke-width: 2 !important;
492
+ cursor: pointer;
493
+ pointer-events: all;
494
+ }
495
+
496
+ :global(.flowdrop-workflow-editor .svelte-flow__edge path) {
497
+ stroke-width: 2 !important;
498
+ }
499
+
500
+ :global(.flowdrop-workflow-editor .svelte-flow__edge:hover) {
501
+ stroke: #3b82f6 !important;
502
+ stroke-width: 3 !important;
503
+ }
504
+
505
+ :global(.flowdrop-workflow-editor .svelte-flow__edge:hover path) {
506
+ stroke-width: 3 !important;
507
+ }
508
+
509
+ :global(.flowdrop-workflow-editor .svelte-flow__edge.selected) {
510
+ stroke: #3b82f6 !important;
511
+ stroke-width: 3 !important;
512
+ filter: drop-shadow(0 0 4px rgba(59, 130, 246, 0.5));
513
+ }
514
+
515
+ :global(.flowdrop-workflow-editor .svelte-flow__edge.selected path) {
516
+ stroke-width: 3 !important;
517
+ }
518
+
519
+ /* Ensure edge paths are clickable */
520
+ :global(.flowdrop-workflow-editor .svelte-flow__edge path) {
521
+ pointer-events: all;
522
+ cursor: pointer;
523
+ }
524
+
525
+ :global(.flowdrop-workflow-editor .svelte-flow__handle) {
526
+ width: 18px;
527
+ height: 18px;
528
+ border: 2px solid white;
529
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
530
+ z-index: 10;
531
+ }
532
+
533
+ :global(.flowdrop-workflow-editor .svelte-flow__handle:hover) {
534
+ transform: scale(1.2);
535
+ }
536
+
537
+ /* Ensure our custom handles are clickable */
538
+ :global(.flowdrop-workflow-editor .svelte-flow__handle) {
539
+ pointer-events: all;
540
+ cursor: crosshair;
541
+ }
542
+ </style>
@@ -0,0 +1,10 @@
1
+ import "@xyflow/svelte/dist/style.css";
2
+ import type { NodeMetadata, Workflow } from "../types/index.js";
3
+ interface Props {
4
+ nodes: NodeMetadata[];
5
+ workflow?: Workflow;
6
+ apiBaseUrl?: string;
7
+ }
8
+ declare const WorkflowEditor: import("svelte").Component<Props, {}, "">;
9
+ type WorkflowEditor = ReturnType<typeof WorkflowEditor>;
10
+ export default WorkflowEditor;