@d34dman/flowdrop 0.0.24 → 0.0.26
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 +52 -62
- package/dist/components/App.svelte +15 -2
- package/dist/components/ConfigForm.svelte +4 -1
- package/dist/components/ConfigModal.svelte +4 -70
- package/dist/components/ConfigPanel.svelte +4 -9
- package/dist/components/EdgeRefresher.svelte +42 -0
- package/dist/components/EdgeRefresher.svelte.d.ts +9 -0
- package/dist/components/ReadOnlyDetails.svelte +3 -1
- package/dist/components/UniversalNode.svelte +6 -3
- package/dist/components/WorkflowEditor.svelte +33 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +3 -1
- package/dist/components/form/FormCheckboxGroup.svelte +2 -9
- package/dist/components/form/FormCodeEditor.svelte +416 -0
- package/dist/components/form/FormCodeEditor.svelte.d.ts +23 -0
- package/dist/components/form/FormField.svelte +125 -85
- package/dist/components/form/FormField.svelte.d.ts +1 -1
- package/dist/components/form/FormFieldWrapper.svelte +2 -10
- package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
- package/dist/components/form/FormMarkdownEditor.svelte +553 -0
- package/dist/components/form/FormMarkdownEditor.svelte.d.ts +29 -0
- package/dist/components/form/FormNumberField.svelte +5 -6
- package/dist/components/form/FormRangeField.svelte +3 -13
- package/dist/components/form/FormSelect.svelte +4 -5
- package/dist/components/form/FormSelect.svelte.d.ts +1 -1
- package/dist/components/form/FormTemplateEditor.svelte +463 -0
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +25 -0
- package/dist/components/form/FormTextField.svelte +3 -4
- package/dist/components/form/FormTextarea.svelte +3 -4
- package/dist/components/form/FormToggle.svelte +2 -3
- package/dist/components/form/index.d.ts +14 -11
- package/dist/components/form/index.js +14 -11
- package/dist/components/form/types.d.ts +55 -2
- package/dist/components/form/types.js +1 -1
- package/dist/components/nodes/NotesNode.svelte +39 -45
- package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
- package/dist/components/nodes/WorkflowNode.svelte +1 -3
- package/dist/styles/base.css +1 -1
- package/package.json +162 -148
package/README.md
CHANGED
|
@@ -51,13 +51,12 @@ npm install @d34dman/flowdrop
|
|
|
51
51
|
|
|
52
52
|
You get a production-ready workflow UI. You keep full control of everything else.
|
|
53
53
|
|
|
54
|
-
|
|
55
54
|
## Quickstart
|
|
56
55
|
|
|
57
56
|
```svelte
|
|
58
57
|
<script lang="ts">
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
import { WorkflowEditor } from '@d34dman/flowdrop';
|
|
59
|
+
import '@d34dman/flowdrop/styles/base.css';
|
|
61
60
|
</script>
|
|
62
61
|
|
|
63
62
|
<WorkflowEditor />
|
|
@@ -65,11 +64,10 @@ You get a production-ready workflow UI. You keep full control of everything else
|
|
|
65
64
|
|
|
66
65
|
**5 lines. One fully-functional workflow editor.**
|
|
67
66
|
|
|
68
|
-
|
|
69
67
|
## Features
|
|
70
68
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
69
|
+
| | |
|
|
70
|
+
| ---------------------------- | ------------------------------------------------------------------------- |
|
|
73
71
|
| 🎨 **Visual Editor Only** | Pure UI component. No hidden backend, no external dependencies |
|
|
74
72
|
| 🔐 **You Own Everything** | Your data, your servers, your orchestration logic, your security policies |
|
|
75
73
|
| 🔌 **Backend Agnostic** | Connect to any API: Drupal, Laravel, Express, FastAPI, or your own |
|
|
@@ -77,7 +75,6 @@ You get a production-ready workflow UI. You keep full control of everything else
|
|
|
77
75
|
| 🎭 **Framework Flexible** | Use as Svelte component or mount into React, Vue, Angular, or vanilla JS |
|
|
78
76
|
| 🐳 **Deploy Anywhere** | Runtime config means build once, deploy everywhere |
|
|
79
77
|
|
|
80
|
-
|
|
81
78
|
## Node Types
|
|
82
79
|
|
|
83
80
|
FlowDrop ships with 7 beautifully designed node types:
|
|
@@ -105,27 +102,27 @@ FlowDrop ships with 7 beautifully designed node types:
|
|
|
105
102
|
|
|
106
103
|
```svelte
|
|
107
104
|
<script>
|
|
108
|
-
|
|
105
|
+
import { WorkflowEditor, NodeSidebar } from '@d34dman/flowdrop';
|
|
109
106
|
</script>
|
|
110
107
|
|
|
111
108
|
<div class="flex h-screen">
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
<NodeSidebar {nodes} />
|
|
110
|
+
<WorkflowEditor {nodes} />
|
|
114
111
|
</div>
|
|
115
112
|
```
|
|
116
113
|
|
|
117
114
|
### Vanilla JS / React / Vue / Angular
|
|
118
115
|
|
|
119
116
|
```javascript
|
|
120
|
-
import { mountFlowDropApp, createEndpointConfig } from
|
|
121
|
-
|
|
122
|
-
const app = await mountFlowDropApp(document.getElementById(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
117
|
+
import { mountFlowDropApp, createEndpointConfig } from '@d34dman/flowdrop';
|
|
118
|
+
|
|
119
|
+
const app = await mountFlowDropApp(document.getElementById('editor'), {
|
|
120
|
+
workflow: myWorkflow,
|
|
121
|
+
endpointConfig: createEndpointConfig('/api/flowdrop'),
|
|
122
|
+
eventHandlers: {
|
|
123
|
+
onDirtyStateChange: (isDirty) => console.log('Unsaved changes:', isDirty),
|
|
124
|
+
onAfterSave: (workflow) => console.log('Saved!', workflow)
|
|
125
|
+
}
|
|
129
126
|
});
|
|
130
127
|
|
|
131
128
|
// Full control over the editor
|
|
@@ -137,69 +134,66 @@ app.destroy();
|
|
|
137
134
|
### Enterprise Integration
|
|
138
135
|
|
|
139
136
|
```javascript
|
|
140
|
-
import { mountFlowDropApp, CallbackAuthProvider } from
|
|
137
|
+
import { mountFlowDropApp, CallbackAuthProvider } from '@d34dman/flowdrop';
|
|
141
138
|
|
|
142
139
|
const app = await mountFlowDropApp(container, {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
140
|
+
// Dynamic token refresh
|
|
141
|
+
authProvider: new CallbackAuthProvider({
|
|
142
|
+
getToken: () => authService.getAccessToken(),
|
|
143
|
+
onUnauthorized: () => authService.refreshToken()
|
|
144
|
+
}),
|
|
145
|
+
|
|
146
|
+
// Lifecycle hooks
|
|
147
|
+
eventHandlers: {
|
|
148
|
+
onBeforeUnmount: (workflow, isDirty) => {
|
|
149
|
+
if (isDirty) saveDraft(workflow);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Auto-save, toasts, and more
|
|
154
|
+
features: {
|
|
155
|
+
autoSaveDraft: true,
|
|
156
|
+
autoSaveDraftInterval: 30000
|
|
157
|
+
}
|
|
161
158
|
});
|
|
162
159
|
```
|
|
163
160
|
|
|
164
|
-
|
|
165
161
|
## API Configuration
|
|
166
162
|
|
|
167
163
|
Connect to any backend in seconds:
|
|
168
164
|
|
|
169
165
|
```typescript
|
|
170
|
-
import { createEndpointConfig } from
|
|
166
|
+
import { createEndpointConfig } from '@d34dman/flowdrop';
|
|
171
167
|
|
|
172
168
|
const config = createEndpointConfig({
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
169
|
+
baseUrl: 'https://api.example.com',
|
|
170
|
+
endpoints: {
|
|
171
|
+
nodes: { list: '/nodes', get: '/nodes/{id}' },
|
|
172
|
+
workflows: {
|
|
173
|
+
list: '/workflows',
|
|
174
|
+
get: '/workflows/{id}',
|
|
175
|
+
create: '/workflows',
|
|
176
|
+
update: '/workflows/{id}',
|
|
177
|
+
execute: '/workflows/{id}/execute'
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
auth: { type: 'bearer', token: 'your-token' }
|
|
185
181
|
});
|
|
186
182
|
```
|
|
187
183
|
|
|
188
|
-
|
|
189
184
|
## Customization
|
|
190
185
|
|
|
191
186
|
Make it yours with CSS custom properties:
|
|
192
187
|
|
|
193
188
|
```css
|
|
194
189
|
:root {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
190
|
+
--flowdrop-background-color: #0a0a0a;
|
|
191
|
+
--flowdrop-primary-color: #6366f1;
|
|
192
|
+
--flowdrop-border-color: #27272a;
|
|
193
|
+
--flowdrop-text-color: #fafafa;
|
|
199
194
|
}
|
|
200
195
|
```
|
|
201
196
|
|
|
202
|
-
|
|
203
197
|
## Deploy
|
|
204
198
|
|
|
205
199
|
### Docker (Recommended)
|
|
@@ -218,8 +212,6 @@ FLOWDROP_API_BASE_URL=http://your-backend/api node build
|
|
|
218
212
|
|
|
219
213
|
Runtime configuration means you build once and deploy to staging, production, or anywhere else with just environment variables.
|
|
220
214
|
|
|
221
|
-
|
|
222
|
-
|
|
223
215
|
## Documentation
|
|
224
216
|
|
|
225
217
|
| Resource | Description |
|
|
@@ -238,12 +230,10 @@ npm run build # Build library
|
|
|
238
230
|
npm test # Run all tests
|
|
239
231
|
```
|
|
240
232
|
|
|
241
|
-
|
|
242
233
|
## Contributing
|
|
243
234
|
|
|
244
235
|
FlowDrop is stabilizing. Contributions will open soon. Star the repo to stay updated.
|
|
245
236
|
|
|
246
|
-
|
|
247
237
|
<p align="center">
|
|
248
238
|
<strong>FlowDrop</strong> - The visual workflow editor you own completely
|
|
249
239
|
</p>
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
import ConfigPanel from './ConfigPanel.svelte';
|
|
15
15
|
import Navbar from './Navbar.svelte';
|
|
16
16
|
import { api, setEndpointConfig } from '../services/api.js';
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
NodeMetadata,
|
|
19
|
+
Workflow,
|
|
20
|
+
WorkflowNode,
|
|
21
|
+
ConfigSchema,
|
|
22
|
+
NodeUIExtensions
|
|
23
|
+
} from '../types/index.js';
|
|
18
24
|
import { createEndpointConfig } from '../config/endpoints.js';
|
|
19
25
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
20
26
|
import type { AuthProvider } from '../types/auth.js';
|
|
@@ -663,7 +669,7 @@
|
|
|
663
669
|
>
|
|
664
670
|
<ConfigForm
|
|
665
671
|
node={currentNode}
|
|
666
|
-
onSave={(updatedConfig, uiExtensions?: NodeUIExtensions) => {
|
|
672
|
+
onSave={async (updatedConfig, uiExtensions?: NodeUIExtensions) => {
|
|
667
673
|
if (selectedNodeId && currentNode) {
|
|
668
674
|
// Build the updated node data
|
|
669
675
|
const updatedData = {
|
|
@@ -687,6 +693,13 @@
|
|
|
687
693
|
// NOTE: We do NOT change the node's type field anymore
|
|
688
694
|
// All nodes use 'universalNode' and UniversalNode handles internal switching
|
|
689
695
|
workflowActions.updateNode(selectedNodeId, nodeUpdates);
|
|
696
|
+
|
|
697
|
+
// For gateway nodes (which have branches), refresh edge positions
|
|
698
|
+
// This fixes the bug where reordering branches doesn't update connections visually
|
|
699
|
+
const nodeType = currentNode.data?.metadata?.type;
|
|
700
|
+
if (nodeType === "gateway" && workflowEditorRef && selectedNodeId) {
|
|
701
|
+
await workflowEditorRef.refreshEdgePositions(selectedNodeId);
|
|
702
|
+
}
|
|
690
703
|
}
|
|
691
704
|
|
|
692
705
|
closeConfigSidebar();
|
|
@@ -131,7 +131,10 @@
|
|
|
131
131
|
if (inputEl.id && !inputEl.id.startsWith('ext-')) {
|
|
132
132
|
if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
|
|
133
133
|
updatedConfig[inputEl.id] = inputEl.checked;
|
|
134
|
-
} else if (
|
|
134
|
+
} else if (
|
|
135
|
+
inputEl instanceof HTMLInputElement &&
|
|
136
|
+
(inputEl.type === 'number' || inputEl.type === 'range')
|
|
137
|
+
) {
|
|
135
138
|
updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
|
|
136
139
|
} else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
|
|
137
140
|
// Parse hidden field values that might be JSON
|
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
cancel: void;
|
|
20
20
|
}>();
|
|
21
21
|
|
|
22
|
-
function handleSave() {
|
|
23
|
-
dispatch('save', { values: localConfigValues });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
function handleCancel() {
|
|
27
23
|
dispatch('cancel');
|
|
28
24
|
}
|
|
@@ -79,29 +75,13 @@
|
|
|
79
75
|
<ConfigForm
|
|
80
76
|
schema={props.configSchema}
|
|
81
77
|
values={localConfigValues}
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
showUIExtensions={false}
|
|
79
|
+
onSave={(config) => {
|
|
80
|
+
dispatch('save', { values: config });
|
|
84
81
|
}}
|
|
82
|
+
onCancel={handleCancel}
|
|
85
83
|
/>
|
|
86
84
|
</div>
|
|
87
|
-
|
|
88
|
-
<!-- Modal Footer -->
|
|
89
|
-
<div class="config-modal__footer">
|
|
90
|
-
<button
|
|
91
|
-
type="button"
|
|
92
|
-
class="config-modal__btn config-modal__btn--secondary"
|
|
93
|
-
onclick={handleCancel}
|
|
94
|
-
>
|
|
95
|
-
Cancel
|
|
96
|
-
</button>
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
class="config-modal__btn config-modal__btn--primary"
|
|
100
|
-
onclick={handleSave}
|
|
101
|
-
>
|
|
102
|
-
Save Configuration
|
|
103
|
-
</button>
|
|
104
|
-
</div>
|
|
105
85
|
</div>
|
|
106
86
|
</div>
|
|
107
87
|
{/if}
|
|
@@ -179,48 +159,6 @@
|
|
|
179
159
|
min-height: 0;
|
|
180
160
|
}
|
|
181
161
|
|
|
182
|
-
.config-modal__footer {
|
|
183
|
-
display: flex;
|
|
184
|
-
align-items: center;
|
|
185
|
-
justify-content: flex-end;
|
|
186
|
-
gap: 0.75rem;
|
|
187
|
-
padding: 1rem 1.5rem 1.5rem 1.5rem;
|
|
188
|
-
border-top: 1px solid #e5e7eb;
|
|
189
|
-
background-color: #f9fafb;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.config-modal__btn {
|
|
193
|
-
padding: 0.5rem 1rem;
|
|
194
|
-
border-radius: 0.375rem;
|
|
195
|
-
font-size: 0.875rem;
|
|
196
|
-
font-weight: 500;
|
|
197
|
-
cursor: pointer;
|
|
198
|
-
transition: all 0.2s;
|
|
199
|
-
border: 1px solid transparent;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.config-modal__btn--primary {
|
|
203
|
-
background-color: #3b82f6;
|
|
204
|
-
color: white;
|
|
205
|
-
border-color: #3b82f6;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.config-modal__btn--primary:hover {
|
|
209
|
-
background-color: #2563eb;
|
|
210
|
-
border-color: #2563eb;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.config-modal__btn--secondary {
|
|
214
|
-
background-color: white;
|
|
215
|
-
color: #374151;
|
|
216
|
-
border-color: #d1d5db;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.config-modal__btn--secondary:hover {
|
|
220
|
-
background-color: #f9fafb;
|
|
221
|
-
border-color: #9ca3af;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
162
|
/* Responsive adjustments */
|
|
225
163
|
@media (max-width: 1024px) {
|
|
226
164
|
.config-modal {
|
|
@@ -242,10 +180,6 @@
|
|
|
242
180
|
.config-modal__content {
|
|
243
181
|
padding: 1rem;
|
|
244
182
|
}
|
|
245
|
-
|
|
246
|
-
.config-modal__footer {
|
|
247
|
-
padding: 0.75rem 1rem 1rem 1rem;
|
|
248
|
-
}
|
|
249
183
|
}
|
|
250
184
|
|
|
251
185
|
@media (max-width: 640px) {
|
|
@@ -60,13 +60,7 @@
|
|
|
60
60
|
<!-- Header -->
|
|
61
61
|
<div class="config-panel__header">
|
|
62
62
|
<h2 class="config-panel__title">{title}</h2>
|
|
63
|
-
<button
|
|
64
|
-
class="config-panel__close"
|
|
65
|
-
onclick={onClose}
|
|
66
|
-
aria-label="Close panel"
|
|
67
|
-
>
|
|
68
|
-
×
|
|
69
|
-
</button>
|
|
63
|
+
<button class="config-panel__close" onclick={onClose} aria-label="Close panel"> × </button>
|
|
70
64
|
</div>
|
|
71
65
|
|
|
72
66
|
<!-- Details Section (between header and content) -->
|
|
@@ -121,7 +115,9 @@
|
|
|
121
115
|
color: #6b7280;
|
|
122
116
|
padding: 0.25rem;
|
|
123
117
|
border-radius: 0.25rem;
|
|
124
|
-
transition:
|
|
118
|
+
transition:
|
|
119
|
+
color 0.15s,
|
|
120
|
+
background-color 0.15s;
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
.config-panel__close:hover {
|
|
@@ -157,4 +153,3 @@
|
|
|
157
153
|
letter-spacing: 0.05em;
|
|
158
154
|
}
|
|
159
155
|
</style>
|
|
160
|
-
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
EdgeRefresher Component
|
|
3
|
+
Helper component that uses useUpdateNodeInternals to force edge recalculation
|
|
4
|
+
Must be rendered inside SvelteFlowProvider context
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { useUpdateNodeInternals } from '@xyflow/svelte';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Node ID to refresh - when this changes, edges are recalculated */
|
|
12
|
+
nodeIdToRefresh: string | null;
|
|
13
|
+
/** Callback when refresh is complete */
|
|
14
|
+
onRefreshComplete?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { nodeIdToRefresh, onRefreshComplete }: Props = $props();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the updateNodeInternals function from Svelte Flow context
|
|
21
|
+
* This recalculates handle positions and forces edge path updates
|
|
22
|
+
*/
|
|
23
|
+
const updateNodeInternals = useUpdateNodeInternals();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Watch for nodeIdToRefresh changes and trigger edge recalculation
|
|
27
|
+
*/
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (nodeIdToRefresh) {
|
|
30
|
+
// Tell Svelte Flow to recalculate node internals (handle positions)
|
|
31
|
+
updateNodeInternals(nodeIdToRefresh);
|
|
32
|
+
|
|
33
|
+
// Notify parent that refresh is complete
|
|
34
|
+
if (onRefreshComplete) {
|
|
35
|
+
onRefreshComplete();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<!-- This component renders nothing - it's just for the hook logic -->
|
|
42
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Node ID to refresh - when this changes, edges are recalculated */
|
|
3
|
+
nodeIdToRefresh: string | null;
|
|
4
|
+
/** Callback when refresh is complete */
|
|
5
|
+
onRefreshComplete?: () => void;
|
|
6
|
+
}
|
|
7
|
+
declare const EdgeRefresher: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type EdgeRefresher = ReturnType<typeof EdgeRefresher>;
|
|
9
|
+
export default EdgeRefresher;
|
|
@@ -128,9 +128,12 @@
|
|
|
128
128
|
</script>
|
|
129
129
|
|
|
130
130
|
<div class="universal-node">
|
|
131
|
-
<!-- Render the node component dynamically -->
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
<!-- Render the node component dynamically (Svelte 5 dynamic component syntax) -->
|
|
132
|
+
{#if nodeComponent}
|
|
133
|
+
<!-- svelte-ignore binding_property_non_reactive -->
|
|
134
|
+
{@const NodeComponent = nodeComponent}
|
|
135
|
+
<NodeComponent {data} {selected} />
|
|
136
|
+
{/if}
|
|
134
137
|
|
|
135
138
|
<!-- Status overlay - only show if there's meaningful status information -->
|
|
136
139
|
{#if shouldShowStatus}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
} from '../types/index.js';
|
|
24
24
|
import CanvasBanner from './CanvasBanner.svelte';
|
|
25
25
|
import FlowDropZone from './FlowDropZone.svelte';
|
|
26
|
+
import EdgeRefresher from './EdgeRefresher.svelte';
|
|
26
27
|
import { tick } from 'svelte';
|
|
27
28
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
28
29
|
import ConnectionLine from './ConnectionLine.svelte';
|
|
@@ -359,9 +360,41 @@
|
|
|
359
360
|
console.warn('No currentWorkflow available for new node');
|
|
360
361
|
}
|
|
361
362
|
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Node ID that needs edge refresh - used to trigger EdgeRefresher component
|
|
366
|
+
*/
|
|
367
|
+
let nodeIdToRefresh = $state<string | null>(null);
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Force edge position recalculation after node config changes
|
|
371
|
+
* This should be called after saving gateway/switch node configs where branches are reordered
|
|
372
|
+
* Svelte Flow doesn't automatically recalculate edge paths when handle positions change
|
|
373
|
+
* @param nodeId - The ID of the node whose handles have changed position
|
|
374
|
+
*/
|
|
375
|
+
export async function refreshEdgePositions(nodeId: string): Promise<void> {
|
|
376
|
+
// Wait for DOM to update with new handle positions
|
|
377
|
+
await tick();
|
|
378
|
+
|
|
379
|
+
// Trigger the EdgeRefresher component to call updateNodeInternals
|
|
380
|
+
nodeIdToRefresh = nodeId;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Callback when edge refresh is complete
|
|
385
|
+
*/
|
|
386
|
+
function handleEdgeRefreshComplete(): void {
|
|
387
|
+
nodeIdToRefresh = null;
|
|
388
|
+
}
|
|
362
389
|
</script>
|
|
363
390
|
|
|
364
391
|
<SvelteFlowProvider>
|
|
392
|
+
<!-- EdgeRefresher component - handles updateNodeInternals calls -->
|
|
393
|
+
<EdgeRefresher
|
|
394
|
+
{nodeIdToRefresh}
|
|
395
|
+
onRefreshComplete={handleEdgeRefreshComplete}
|
|
396
|
+
/>
|
|
397
|
+
|
|
365
398
|
<div class="flowdrop-workflow-editor">
|
|
366
399
|
<!-- Main Editor Area -->
|
|
367
400
|
<div class="flowdrop-workflow-editor__main">
|
|
@@ -15,6 +15,8 @@ interface Props {
|
|
|
15
15
|
nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
|
|
16
16
|
pipelineId?: string;
|
|
17
17
|
}
|
|
18
|
-
declare const WorkflowEditor: import("svelte").Component<Props, {
|
|
18
|
+
declare const WorkflowEditor: import("svelte").Component<Props, {
|
|
19
|
+
refreshEdgePositions: (nodeId: string) => Promise<void>;
|
|
20
|
+
}, "">;
|
|
19
21
|
type WorkflowEditor = ReturnType<typeof WorkflowEditor>;
|
|
20
22
|
export default WorkflowEditor;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
|
-
import Icon from
|
|
12
|
+
import Icon from '@iconify/svelte';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
/** Field identifier (used for ARIA) */
|
|
@@ -24,13 +24,7 @@
|
|
|
24
24
|
onChange: (value: string[]) => void;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
let {
|
|
28
|
-
id,
|
|
29
|
-
value = [],
|
|
30
|
-
options = [],
|
|
31
|
-
ariaDescribedBy,
|
|
32
|
-
onChange
|
|
33
|
-
}: Props = $props();
|
|
27
|
+
let { id, value = [], options = [], ariaDescribedBy, onChange }: Props = $props();
|
|
34
28
|
|
|
35
29
|
/**
|
|
36
30
|
* Handle checkbox toggle
|
|
@@ -149,4 +143,3 @@
|
|
|
149
143
|
line-height: 1.4;
|
|
150
144
|
}
|
|
151
145
|
</style>
|
|
152
|
-
|