@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.
- package/README.md +293 -0
- package/dist/adapters/WorkflowAdapter.d.ts +166 -0
- package/dist/adapters/WorkflowAdapter.js +337 -0
- package/dist/api/client.d.ts +79 -0
- package/dist/api/client.js +208 -0
- package/dist/app.css +0 -0
- package/dist/clients/ApiClient.d.ts +203 -0
- package/dist/clients/ApiClient.js +212 -0
- package/dist/components/App.svelte +237 -0
- package/dist/components/App.svelte.d.ts +3 -0
- package/dist/components/CanvasBanner.svelte +51 -0
- package/dist/components/CanvasBanner.svelte.d.ts +22 -0
- package/dist/components/LoadingSpinner.svelte +36 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +8 -0
- package/dist/components/Node.svelte +38 -0
- package/dist/components/Node.svelte.d.ts +4 -0
- package/dist/components/NodeSidebar.svelte +500 -0
- package/dist/components/NodeSidebar.svelte.d.ts +9 -0
- package/dist/components/WorkflowEditor.svelte +542 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +10 -0
- package/dist/components/WorkflowNode.svelte +558 -0
- package/dist/components/WorkflowNode.svelte.d.ts +11 -0
- package/dist/data/samples.d.ts +17 -0
- package/dist/data/samples.js +1193 -0
- package/dist/examples/adapter-usage.d.ts +66 -0
- package/dist/examples/adapter-usage.js +138 -0
- package/dist/examples/api-client-usage.d.ts +31 -0
- package/dist/examples/api-client-usage.js +241 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +27 -0
- package/dist/services/api.d.ts +110 -0
- package/dist/services/api.js +149 -0
- package/dist/services/workflowStorage.d.ts +37 -0
- package/dist/services/workflowStorage.js +116 -0
- package/dist/styles/base.css +858 -0
- package/dist/svelte-app.d.ts +17 -0
- package/dist/svelte-app.js +30 -0
- package/dist/types/index.d.ts +179 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +121 -0
- package/dist/utils/colors.js +240 -0
- package/dist/utils/connections.d.ts +47 -0
- package/dist/utils/connections.js +240 -0
- package/dist/utils/icons.d.ts +102 -0
- package/dist/utils/icons.js +149 -0
- 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;
|