@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,558 @@
1
+ <!--
2
+ Workflow Node Component
3
+ Renders individual nodes in the workflow editor with full functionality
4
+ Uses SvelteFlow's Handle for connection ports
5
+ Styled with BEM syntax
6
+ -->
7
+
8
+ <script lang="ts">
9
+ import {
10
+ Position,
11
+ // @ts-ignore
12
+ Handle,
13
+ type NodeProps
14
+ } from "@xyflow/svelte";
15
+ import type { WorkflowNode, NodePort } from "../types/index.js";
16
+ import Icon from "@iconify/svelte";
17
+ import { getNodeIcon } from "../utils/icons.js";
18
+ import { getDataTypeColorToken, getCategoryColorToken } from "../utils/colors.js";
19
+
20
+ interface Props {
21
+ data: WorkflowNode["data"] & { nodeId?: string };
22
+ selected?: boolean;
23
+ onPortClick?: (nodeId: string, portId: string, isOutput: boolean, event: MouseEvent) => void;
24
+ }
25
+
26
+ let props: Props = $props();
27
+ let isExpanded = $state(false);
28
+ let configValues = $state({ ...props.data.config });
29
+ let isHandleInteraction = $state(false);
30
+
31
+ /**
32
+ * Handle configuration value changes
33
+ */
34
+ function handleConfigChange(key: string, value: unknown): void {
35
+ configValues = { ...configValues, [key]: value };
36
+ }
37
+
38
+ /**
39
+ * Handle node selection
40
+ */
41
+ function handleNodeClick(): void {
42
+ // Node selection is handled through events
43
+ }
44
+
45
+ /**
46
+ * Toggle node expansion
47
+ */
48
+ function toggleExpansion(): void {
49
+ isExpanded = !isExpanded;
50
+ }
51
+
52
+ /**
53
+ * Handle node drag start
54
+ */
55
+ function handleDragStart(event: DragEvent): void {
56
+ // Prevent default drag behavior
57
+ event.preventDefault();
58
+ }
59
+ </script>
60
+
61
+ <!-- Node Container -->
62
+ <div
63
+ class="flowdrop-workflow-node"
64
+ class:flowdrop-workflow-node--selected={props.selected}
65
+ onclick={handleNodeClick}
66
+ onmouseup={() => {
67
+ isHandleInteraction = false;
68
+ }}
69
+ data-handle-interaction={isHandleInteraction}
70
+ role="button"
71
+ tabindex="0"
72
+ onkeydown={(e) => {
73
+ if (e.key === "Enter" || e.key === " ") {
74
+ e.preventDefault();
75
+ handleNodeClick();
76
+ }
77
+ }}
78
+ aria-label="Workflow node: {props.data.metadata.name}"
79
+ aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
80
+ >
81
+ <!-- Node Header -->
82
+ <div class="flowdrop-workflow-node__header">
83
+ <div class="flowdrop-flex flowdrop-gap--3 flowdrop-items--center">
84
+ <!-- Node Icon -->
85
+ <div class="flowdrop-workflow-node__icon" style="background-color: {getCategoryColorToken(props.data.metadata.category)}">
86
+ <Icon icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)} />
87
+ </div>
88
+
89
+ <!-- Node Title - Icon and Title on same line -->
90
+ <h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
91
+ {props.data.label}
92
+ </h3>
93
+
94
+ <!-- Status Indicators -->
95
+ <div class="flowdrop-flex flowdrop-gap--2">
96
+ {#if props.data.isProcessing}
97
+ <div class="flowdrop-workflow-node__status flowdrop-workflow-node__status--processing" title="Processing"></div>
98
+ {/if}
99
+ {#if props.data.error}
100
+ <div class="flowdrop-workflow-node__status flowdrop-workflow-node__status--error" title="Error"></div>
101
+ {/if}
102
+ <button
103
+ class="flowdrop-workflow-node__expand-btn"
104
+ onclick={(e) => {
105
+ e.stopPropagation();
106
+ toggleExpansion();
107
+ }}
108
+ type="button"
109
+ aria-label="{isExpanded ? 'Collapse' : 'Expand'} node configuration"
110
+ >
111
+ <span class="flowdrop-text--xs flowdrop-font--medium">{isExpanded ? "−" : "+"}</span>
112
+ </button>
113
+ </div>
114
+ </div>
115
+ <!-- Node Description - on new line below -->
116
+ <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1" id="node-description-{props.data.nodeId || 'unknown'}">
117
+ {props.data.metadata.description}
118
+ </p>
119
+ </div>
120
+
121
+ <!-- Node Configuration (Expanded) -->
122
+ {#if isExpanded}
123
+ <div class="flowdrop-workflow-node__config">
124
+ <h4 class="flowdrop-workflow-node__config-title">Configuration</h4>
125
+ <div class="flowdrop-workflow-node__config-content">
126
+ {#each Object.entries(configValues) as [key, value]}
127
+ <div class="flowdrop-form-control">
128
+ <label class="flowdrop-form-control__label" for={`config-${key}`}>
129
+ <span class="flowdrop-text--xs flowdrop-font--medium">{key}</span>
130
+ </label>
131
+ {#if typeof value === "string"}
132
+ <input
133
+ id={`config-${key}`}
134
+ type="text"
135
+ class="flowdrop-input flowdrop-input--sm"
136
+ value={value}
137
+ oninput={(e) => {
138
+ const target = e.target as HTMLInputElement;
139
+ if (target) handleConfigChange(key, target.value);
140
+ }}
141
+ />
142
+ {:else if typeof value === "number"}
143
+ <input
144
+ id={`config-${key}`}
145
+ type="number"
146
+ class="flowdrop-input flowdrop-input--sm"
147
+ value={value}
148
+ oninput={(e) => {
149
+ const target = e.target as HTMLInputElement;
150
+ if (target) {
151
+ const value = Number(target.value);
152
+ if (!isNaN(value)) {
153
+ handleConfigChange(key, value);
154
+ }
155
+ }
156
+ }}
157
+ />
158
+ {:else if typeof value === "boolean"}
159
+ <div class="flowdrop-flex flowdrop-gap--3">
160
+ <input
161
+ id={`config-${key}`}
162
+ type="checkbox"
163
+ class="flowdrop-toggle flowdrop-toggle--sm"
164
+ checked={value}
165
+ onchange={(e) => {
166
+ const target = e.target as HTMLInputElement;
167
+ if (target) handleConfigChange(key, target.checked);
168
+ }}
169
+ />
170
+ <span class="flowdrop-text--xs">{key}</span>
171
+ </div>
172
+ {:else}
173
+ <textarea
174
+ id={`config-${key}`}
175
+ class="flowdrop-textarea flowdrop-textarea--sm"
176
+ placeholder="Enter value..."
177
+ value={JSON.stringify(value, null, 2)}
178
+ oninput={(e) => {
179
+ const target = e.target as HTMLInputElement;
180
+ if (target) {
181
+ try {
182
+ handleConfigChange(key, JSON.parse(target.value));
183
+ } catch {
184
+ handleConfigChange(key, target.value);
185
+ }
186
+ }
187
+ }}
188
+ ></textarea>
189
+ {/if}
190
+ </div>
191
+ {/each}
192
+ </div>
193
+ </div>
194
+ {/if}
195
+
196
+ <!-- Input Ports Container -->
197
+ {#if props.data.metadata.inputs.length > 0}
198
+ <div class="flowdrop-workflow-node__ports">
199
+ <div class="flowdrop-workflow-node__ports-header">
200
+ <h5 class="flowdrop-workflow-node__ports-title">Inputs</h5>
201
+ </div>
202
+ <div class="flowdrop-workflow-node__ports-list">
203
+ {#each props.data.metadata.inputs as port (port.id)}
204
+ <div class="flowdrop-workflow-node__port">
205
+ <!-- Input Handle -->
206
+ <Handle
207
+ type="target"
208
+ position={Position.Left}
209
+ id={port.id}
210
+ class="flowdrop-workflow-node__handle"
211
+ style="top: 50%; transform: translateY(-50%); margin-left: -16px;"
212
+ role="button"
213
+ tabindex={0}
214
+ aria-label="Connect to {port.name} input port"
215
+ />
216
+
217
+ <!-- Port Info -->
218
+ <div class="flowdrop-flex--1 flowdrop-min-w--0">
219
+ <div class="flowdrop-flex flowdrop-gap--2">
220
+ <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
221
+ <span class="flowdrop-badge flowdrop-badge--sm" style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;">
222
+ {port.dataType}
223
+ </span>
224
+ {#if port.required}
225
+ <span class="flowdrop-badge flowdrop-badge--error flowdrop-badge--sm">Required</span>
226
+ {/if}
227
+ </div>
228
+ {#if port.description}
229
+ <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">{port.description}</p>
230
+ {/if}
231
+ </div>
232
+ </div>
233
+ {/each}
234
+ </div>
235
+ </div>
236
+ {/if}
237
+
238
+ <!-- Output Ports Container -->
239
+ {#if props.data.metadata.outputs.length > 0}
240
+ <div class="flowdrop-workflow-node__ports">
241
+ <div class="flowdrop-workflow-node__ports-header">
242
+ <h5 class="flowdrop-workflow-node__ports-title">Outputs</h5>
243
+ </div>
244
+ <div class="flowdrop-workflow-node__ports-list">
245
+ {#each props.data.metadata.outputs as port (port.id)}
246
+ <div class="flowdrop-workflow-node__port">
247
+ <!-- Port Info -->
248
+ <div class="flowdrop-flex--1 flowdrop-min-w--0 flowdrop-text--right">
249
+ <div class="flowdrop-flex flowdrop-gap--2 flowdrop-justify--end">
250
+ <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
251
+ <span class="flowdrop-badge flowdrop-badge--sm" style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;">
252
+ {port.dataType}
253
+ </span>
254
+ </div>
255
+ {#if port.description}
256
+ <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">{port.description}</p>
257
+ {/if}
258
+ </div>
259
+
260
+ <!-- Output Handle -->
261
+ <Handle
262
+ type="source"
263
+ position={Position.Right}
264
+ id={port.id}
265
+ class="flowdrop-workflow-node__handle"
266
+ style="top: 50%; transform: translateY(-50%); margin-right: -16px;"
267
+ role="button"
268
+ tabindex={0}
269
+ aria-label="Connect from {port.name} output port"
270
+ />
271
+ </div>
272
+ {/each}
273
+ </div>
274
+ </div>
275
+ {/if}
276
+ </div>
277
+
278
+ <style>
279
+ .flowdrop-workflow-node {
280
+ position: relative;
281
+ background-color: #ffffff;
282
+ border: 2px solid #e5e7eb;
283
+ border-radius: 0.75rem;
284
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
285
+ min-width: 14rem;
286
+ max-width: 18rem;
287
+ }
288
+
289
+ .flowdrop-workflow-node--selected {
290
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
291
+ border: 2px solid #3b82f6;
292
+ }
293
+
294
+ .flowdrop-workflow-node__header {
295
+ padding: 1rem;
296
+ border-bottom: 1px solid #e5e7eb;
297
+ background-color: #f9fafb;
298
+ border-top-left-radius: 0.75rem;
299
+ border-top-right-radius: 0.75rem;
300
+ }
301
+
302
+ .flowdrop-workflow-node__icon {
303
+ width: 2rem;
304
+ height: 2rem;
305
+ border-radius: 0.5rem;
306
+ color: #ffffff;
307
+ font-size: 0.875rem;
308
+ font-weight: 500;
309
+ display: flex;
310
+ align-items: center;
311
+ justify-content: center;
312
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
313
+ }
314
+
315
+ .flowdrop-workflow-node__header h3 {
316
+ margin: 0;
317
+ line-height: 1;
318
+ }
319
+
320
+ .flowdrop-workflow-node__status {
321
+ width: 0.5rem;
322
+ height: 0.5rem;
323
+ border-radius: 50%;
324
+ }
325
+
326
+ .flowdrop-workflow-node__status--processing {
327
+ background-color: #f59e0b;
328
+ animation: flowdrop-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
329
+ }
330
+
331
+ .flowdrop-workflow-node__status--error {
332
+ background-color: #ef4444;
333
+ }
334
+
335
+ .flowdrop-workflow-node__expand-btn {
336
+ width: 1.5rem;
337
+ height: 1.5rem;
338
+ border-radius: 0.375rem;
339
+ background-color: #f3f4f6;
340
+ display: flex;
341
+ align-items: center;
342
+ justify-content: center;
343
+ color: #6b7280;
344
+ transition: all 0.2s ease-in-out;
345
+ border: none;
346
+ cursor: pointer;
347
+ }
348
+
349
+ .flowdrop-workflow-node__expand-btn:hover {
350
+ background-color: #e5e7eb;
351
+ color: #374151;
352
+ }
353
+
354
+ .flowdrop-workflow-node__config {
355
+ padding: 1rem;
356
+ border-bottom: 1px solid #e5e7eb;
357
+ background-color: #f9fafb;
358
+ }
359
+
360
+ .flowdrop-workflow-node__config-title {
361
+ font-size: 0.75rem;
362
+ font-weight: 600;
363
+ color: #374151;
364
+ margin-bottom: 0.75rem;
365
+ text-transform: uppercase;
366
+ letter-spacing: 0.05em;
367
+ }
368
+
369
+ .flowdrop-workflow-node__config-content {
370
+ display: flex;
371
+ flex-direction: column;
372
+ gap: 0.75rem;
373
+ }
374
+
375
+ .flowdrop-form-control {
376
+ display: flex;
377
+ flex-direction: column;
378
+ }
379
+
380
+ .flowdrop-form-control__label {
381
+ padding-bottom: 0.25rem;
382
+ display: flex;
383
+ flex-direction: column;
384
+ }
385
+
386
+ .flowdrop-textarea {
387
+ display: block;
388
+ width: 100%;
389
+ padding: 0.375rem 0.5rem;
390
+ border: 1px solid #d1d5db;
391
+ border-radius: 0.375rem;
392
+ font-size: 0.75rem;
393
+ line-height: 1.25rem;
394
+ color: #111827;
395
+ background-color: rgba(255, 255, 255, 0.7);
396
+ transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
397
+ resize: vertical;
398
+ min-height: 2.5rem;
399
+ }
400
+
401
+ .flowdrop-textarea:focus {
402
+ outline: none;
403
+ border-color: #3b82f6;
404
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
405
+ }
406
+
407
+ .flowdrop-textarea--sm {
408
+ padding: 0.375rem 0.5rem;
409
+ font-size: 0.75rem;
410
+ }
411
+
412
+ .flowdrop-toggle {
413
+ appearance: none;
414
+ width: 2rem;
415
+ height: 1rem;
416
+ background-color: #d1d5db;
417
+ border-radius: 9999px;
418
+ position: relative;
419
+ cursor: pointer;
420
+ transition: background-color 0.2s ease-in-out;
421
+ }
422
+
423
+ .flowdrop-toggle:checked {
424
+ background-color: #3b82f6;
425
+ }
426
+
427
+ .flowdrop-toggle::before {
428
+ content: "";
429
+ position: absolute;
430
+ width: 0.75rem;
431
+ height: 0.75rem;
432
+ background-color: #ffffff;
433
+ border-radius: 50%;
434
+ top: 0.125rem;
435
+ left: 0.125rem;
436
+ transition: transform 0.2s ease-in-out;
437
+ }
438
+
439
+ .flowdrop-toggle:checked::before {
440
+ transform: translateX(1rem);
441
+ }
442
+
443
+ .flowdrop-toggle--sm {
444
+ width: 1.5rem;
445
+ height: 0.75rem;
446
+ }
447
+
448
+ .flowdrop-toggle--sm::before {
449
+ width: 0.5rem;
450
+ height: 0.5rem;
451
+ top: 0.125rem;
452
+ left: 0.125rem;
453
+ }
454
+
455
+ .flowdrop-toggle--sm:checked::before {
456
+ transform: translateX(0.75rem);
457
+ }
458
+
459
+ .flowdrop-workflow-node__ports {
460
+ border-bottom: 1px solid #e5e7eb;
461
+ }
462
+
463
+ .flowdrop-workflow-node__ports:last-child {
464
+ border-bottom: none;
465
+ }
466
+
467
+ .flowdrop-workflow-node__ports-header {
468
+ padding: 0.5rem 1rem;
469
+ background-color: #f3f4f6;
470
+ }
471
+
472
+ .flowdrop-workflow-node__ports-title {
473
+ font-size: 0.75rem;
474
+ font-weight: 600;
475
+ color: #6b7280;
476
+ text-transform: uppercase;
477
+ letter-spacing: 0.05em;
478
+ }
479
+
480
+ .flowdrop-workflow-node__ports-list {
481
+ display: flex;
482
+ flex-direction: column;
483
+ gap: 0.25rem;
484
+ }
485
+
486
+ .flowdrop-workflow-node__port {
487
+ position: relative;
488
+ display: flex;
489
+ align-items: center;
490
+ gap: 0.5rem;
491
+ padding: 0.5rem 1rem;
492
+ transition: background-color 0.2s ease-in-out;
493
+ }
494
+
495
+ .flowdrop-workflow-node__port:hover {
496
+ background-color: #f9fafb;
497
+ }
498
+
499
+
500
+ .flowdrop-badge--sm {
501
+ padding: 0.125rem 0.25rem;
502
+ font-size: 0.625rem;
503
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
504
+ }
505
+
506
+ .flowdrop-badge--error {
507
+ background-color: #fee2e2;
508
+ color: #991b1b;
509
+ }
510
+
511
+ .flowdrop-justify--end {
512
+ justify-content: flex-end;
513
+ }
514
+
515
+ .flowdrop-text--right {
516
+ text-align: right;
517
+ }
518
+
519
+ .flowdrop-min-w--0 {
520
+ min-width: 0;
521
+ }
522
+
523
+ .flowdrop-truncate {
524
+ overflow: hidden;
525
+ text-overflow: ellipsis;
526
+ white-space: nowrap;
527
+ }
528
+
529
+ .flowdrop-mt--1 {
530
+ margin-top: 0.25rem;
531
+ }
532
+
533
+ @keyframes flowdrop-pulse {
534
+ 0%, 100% {
535
+ opacity: 1;
536
+ }
537
+ 50% {
538
+ opacity: 0.5;
539
+ }
540
+ }
541
+
542
+ /* Focus styles for accessibility */
543
+ .flowdrop-workflow-node:focus {
544
+ outline: 2px solid #3b82f6;
545
+ outline-offset: 2px;
546
+ }
547
+
548
+ .flowdrop-workflow-node:focus:not(:focus-visible) {
549
+ outline: none;
550
+ }
551
+
552
+ /* Configuration form styling */
553
+ .flowdrop-form-control input:focus,
554
+ .flowdrop-form-control textarea:focus {
555
+ transform: translateY(-1px);
556
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
557
+ }
558
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { WorkflowNode } from "../types/index.js";
2
+ interface Props {
3
+ data: WorkflowNode["data"] & {
4
+ nodeId?: string;
5
+ };
6
+ selected?: boolean;
7
+ onPortClick?: (nodeId: string, portId: string, isOutput: boolean, event: MouseEvent) => void;
8
+ }
9
+ declare const WorkflowNode: import("svelte").Component<Props, {}, "">;
10
+ type WorkflowNode = ReturnType<typeof WorkflowNode>;
11
+ export default WorkflowNode;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Sample data for FlowDrop development and testing
3
+ * Full set matching Langflow's default categories and node types
4
+ */
5
+ import type { NodeMetadata, Workflow } from "../types/index.js";
6
+ import { CATEGORY_ICONS } from "../utils/icons.js";
7
+ /**
8
+ * Sample node definitions for development
9
+ * Full set matching Langflow's default categories
10
+ */
11
+ export declare const sampleNodes: NodeMetadata[];
12
+ export { CATEGORY_ICONS as categoryIcons };
13
+ /**
14
+ * Sample workflow for development
15
+ * Updated to use the new node types
16
+ */
17
+ export declare const sampleWorkflow: Workflow;