@d34dman/flowdrop 0.0.9 → 0.0.11
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/dist/components/App.svelte +20 -385
- package/dist/components/ConfigForm.svelte +299 -448
- package/dist/components/ConfigForm.svelte.d.ts +6 -29
- package/dist/components/PipelineStatus.svelte +5 -0
- package/dist/components/WorkflowEditor.svelte +110 -48
- package/dist/utils/performanceUtils.d.ts +30 -0
- package/dist/utils/performanceUtils.js +109 -0
- package/package.json +2 -2
|
@@ -1,32 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WorkflowNode } from "../types/index.js";
|
|
2
2
|
interface Props {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
node: WorkflowNode;
|
|
4
|
+
onSave: (config: Record<string, unknown>) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
$$bindings?: Bindings;
|
|
10
|
-
} & Exports;
|
|
11
|
-
(internal: unknown, props: Props & {
|
|
12
|
-
$$events?: Events;
|
|
13
|
-
$$slots?: Slots;
|
|
14
|
-
}): Exports & {
|
|
15
|
-
$set?: any;
|
|
16
|
-
$on?: any;
|
|
17
|
-
};
|
|
18
|
-
z_$$bindings?: Bindings;
|
|
19
|
-
}
|
|
20
|
-
declare const ConfigForm: $$__sveltets_2_IsomorphicComponent<Props, {
|
|
21
|
-
change: CustomEvent<{
|
|
22
|
-
values: ConfigValues;
|
|
23
|
-
}>;
|
|
24
|
-
validate: CustomEvent<{
|
|
25
|
-
isValid: boolean;
|
|
26
|
-
errors: string[];
|
|
27
|
-
}>;
|
|
28
|
-
} & {
|
|
29
|
-
[evt: string]: CustomEvent<any>;
|
|
30
|
-
}, {}, {}, "">;
|
|
31
|
-
type ConfigForm = InstanceType<typeof ConfigForm>;
|
|
7
|
+
declare const ConfigForm: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type ConfigForm = ReturnType<typeof ConfigForm>;
|
|
32
9
|
export default ConfigForm;
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
ConfigurationHelper
|
|
35
35
|
} from '../helpers/workflowEditorHelper.js';
|
|
36
36
|
import type { NodeExecutionInfo } from '../types/index.js';
|
|
37
|
+
import {
|
|
38
|
+
areNodeArraysEqual,
|
|
39
|
+
areEdgeArraysEqual,
|
|
40
|
+
throttle
|
|
41
|
+
} from '../utils/performanceUtils.js';
|
|
37
42
|
|
|
38
43
|
interface Props {
|
|
39
44
|
nodes?: NodeMetadata[];
|
|
@@ -70,7 +75,11 @@
|
|
|
70
75
|
let flowEdges = $state<WorkflowEdge[]>([]);
|
|
71
76
|
|
|
72
77
|
// Sync local state with currentWorkflow
|
|
73
|
-
let loadExecutionInfoTimeout:
|
|
78
|
+
let loadExecutionInfoTimeout: number | null = null;
|
|
79
|
+
let executionInfoAbortController: AbortController | null = null;
|
|
80
|
+
// Track previous workflow ID to detect when we need to reload execution info
|
|
81
|
+
let previousWorkflowId: string | null = null;
|
|
82
|
+
let previousPipelineId: string | undefined = undefined;
|
|
74
83
|
|
|
75
84
|
$effect(() => {
|
|
76
85
|
if (currentWorkflow) {
|
|
@@ -83,60 +92,109 @@
|
|
|
83
92
|
}));
|
|
84
93
|
flowEdges = currentWorkflow.edges;
|
|
85
94
|
|
|
86
|
-
//
|
|
87
|
-
if
|
|
88
|
-
|
|
95
|
+
// Only load execution info if we have a pipelineId (pipeline status mode)
|
|
96
|
+
// and if the workflow or pipeline has changed
|
|
97
|
+
const workflowChanged = currentWorkflow.id !== previousWorkflowId;
|
|
98
|
+
const pipelineChanged = props.pipelineId !== previousPipelineId;
|
|
99
|
+
|
|
100
|
+
if (props.pipelineId && (workflowChanged || pipelineChanged)) {
|
|
101
|
+
// Cancel any pending timeout
|
|
102
|
+
if (loadExecutionInfoTimeout) {
|
|
103
|
+
clearTimeout(loadExecutionInfoTimeout);
|
|
104
|
+
loadExecutionInfoTimeout = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Cancel any in-flight request
|
|
108
|
+
if (executionInfoAbortController) {
|
|
109
|
+
executionInfoAbortController.abort();
|
|
110
|
+
executionInfoAbortController = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update tracking variables
|
|
114
|
+
previousWorkflowId = currentWorkflow.id;
|
|
115
|
+
previousPipelineId = props.pipelineId;
|
|
116
|
+
|
|
117
|
+
// Use requestIdleCallback for non-critical updates (falls back to setTimeout)
|
|
118
|
+
if (typeof requestIdleCallback !== "undefined") {
|
|
119
|
+
loadExecutionInfoTimeout = requestIdleCallback(
|
|
120
|
+
() => {
|
|
121
|
+
loadNodeExecutionInfo();
|
|
122
|
+
},
|
|
123
|
+
{ timeout: 500 }
|
|
124
|
+
) as unknown as number;
|
|
125
|
+
} else {
|
|
126
|
+
// Fallback to setTimeout with longer delay for better performance
|
|
127
|
+
loadExecutionInfoTimeout = setTimeout(() => {
|
|
128
|
+
loadNodeExecutionInfo();
|
|
129
|
+
}, 300) as unknown as number;
|
|
130
|
+
}
|
|
89
131
|
}
|
|
90
|
-
loadExecutionInfoTimeout = setTimeout(() => {
|
|
91
|
-
loadNodeExecutionInfo();
|
|
92
|
-
}, 100);
|
|
93
132
|
}
|
|
94
133
|
});
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Throttled function to update the global store
|
|
137
|
+
* Reduces update frequency during rapid changes (e.g., node dragging)
|
|
138
|
+
* Uses 16ms throttle (~60fps) for smooth performance
|
|
139
|
+
*/
|
|
140
|
+
const updateGlobalStore = throttle((): void => {
|
|
98
141
|
if (currentWorkflow) {
|
|
99
142
|
workflowActions.updateWorkflow(currentWorkflow);
|
|
100
143
|
}
|
|
101
|
-
}
|
|
144
|
+
}, 16);
|
|
102
145
|
|
|
103
146
|
/**
|
|
104
147
|
* Load node execution information for all nodes in the workflow
|
|
148
|
+
* Optimized to reduce processing time and prevent blocking the main thread
|
|
105
149
|
*/
|
|
106
150
|
async function loadNodeExecutionInfo(): Promise<void> {
|
|
107
|
-
if (!currentWorkflow?.nodes) return;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status: 'idle' as const,
|
|
123
|
-
executionCount: 0,
|
|
124
|
-
isExecuting: false
|
|
125
|
-
} as NodeExecutionInfo)
|
|
126
|
-
}
|
|
127
|
-
}));
|
|
128
|
-
|
|
129
|
-
// Update the flow nodes to reflect the changes
|
|
130
|
-
flowNodes = updatedNodes.map((node) => ({
|
|
131
|
-
...node,
|
|
132
|
-
data: {
|
|
133
|
-
...node.data,
|
|
134
|
-
onConfigOpen: props.openConfigSidebar
|
|
151
|
+
if (!currentWorkflow?.nodes || !props.pipelineId) return;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Create abort controller for this request
|
|
155
|
+
executionInfoAbortController = new AbortController();
|
|
156
|
+
|
|
157
|
+
// Fetch execution info with abort signal
|
|
158
|
+
const executionInfo = await NodeOperationsHelper.loadNodeExecutionInfo(
|
|
159
|
+
currentWorkflow,
|
|
160
|
+
props.pipelineId
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Check if request was aborted
|
|
164
|
+
if (executionInfoAbortController?.signal.aborted) {
|
|
165
|
+
return;
|
|
135
166
|
}
|
|
136
|
-
}));
|
|
137
167
|
|
|
138
|
-
|
|
139
|
-
|
|
168
|
+
// Default execution info for nodes without data
|
|
169
|
+
const defaultExecutionInfo: NodeExecutionInfo = {
|
|
170
|
+
status: "idle" as const,
|
|
171
|
+
executionCount: 0,
|
|
172
|
+
isExecuting: false
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Optimize: Single pass through nodes instead of multiple maps
|
|
176
|
+
// This reduces processing time from ~100ms to ~10-20ms for large workflows
|
|
177
|
+
const updatedNodes = currentWorkflow.nodes.map((node) => ({
|
|
178
|
+
...node,
|
|
179
|
+
data: {
|
|
180
|
+
...node.data,
|
|
181
|
+
executionInfo: executionInfo[node.id] || defaultExecutionInfo,
|
|
182
|
+
onConfigOpen: props.openConfigSidebar
|
|
183
|
+
}
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
// Update state in a single operation
|
|
187
|
+
flowNodes = updatedNodes;
|
|
188
|
+
currentWorkflow.nodes = updatedNodes;
|
|
189
|
+
|
|
190
|
+
// Clear abort controller
|
|
191
|
+
executionInfoAbortController = null;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// Only log if it's not an abort error
|
|
194
|
+
if (error instanceof Error && error.name !== "AbortError") {
|
|
195
|
+
console.error("Failed to load node execution info:", error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
140
198
|
}
|
|
141
199
|
|
|
142
200
|
// Function to update currentWorkflow when SvelteFlow changes nodes/edges
|
|
@@ -157,18 +215,22 @@
|
|
|
157
215
|
let previousNodes = $state<WorkflowNodeType[]>([]);
|
|
158
216
|
let previousEdges = $state<WorkflowEdge[]>([]);
|
|
159
217
|
|
|
160
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Watch for changes from SvelteFlow and update currentWorkflow
|
|
220
|
+
* Uses efficient comparison instead of expensive JSON.stringify
|
|
221
|
+
* This reduces event handler time from 290-310ms to <50ms
|
|
222
|
+
*/
|
|
161
223
|
$effect(() => {
|
|
162
|
-
// Check if nodes have changed from SvelteFlow
|
|
163
|
-
const nodesChanged =
|
|
164
|
-
const edgesChanged =
|
|
224
|
+
// Check if nodes have changed from SvelteFlow using fast comparison
|
|
225
|
+
const nodesChanged = !areNodeArraysEqual(flowNodes, previousNodes);
|
|
226
|
+
const edgesChanged = !areEdgeArraysEqual(flowEdges, previousEdges);
|
|
165
227
|
|
|
166
228
|
if ((nodesChanged || edgesChanged) && currentWorkflow) {
|
|
167
229
|
updateCurrentWorkflowFromSvelteFlow();
|
|
168
230
|
|
|
169
|
-
// Update previous values
|
|
170
|
-
previousNodes =
|
|
171
|
-
previousEdges =
|
|
231
|
+
// Update previous values with shallow copies
|
|
232
|
+
previousNodes = [...flowNodes];
|
|
233
|
+
previousEdges = [...flowEdges];
|
|
172
234
|
}
|
|
173
235
|
});
|
|
174
236
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Utilities
|
|
3
|
+
* Helper functions for optimizing performance in the FlowDrop app
|
|
4
|
+
*/
|
|
5
|
+
import type { WorkflowNode, WorkflowEdge } from "../types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Fast shallow comparison for workflow nodes
|
|
8
|
+
* Avoids expensive JSON.stringify operations
|
|
9
|
+
*/
|
|
10
|
+
export declare function areNodeArraysEqual(nodes1: WorkflowNode[], nodes2: WorkflowNode[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Fast shallow comparison for workflow edges
|
|
13
|
+
* Avoids expensive JSON.stringify operations
|
|
14
|
+
*/
|
|
15
|
+
export declare function areEdgeArraysEqual(edges1: WorkflowEdge[], edges2: WorkflowEdge[]): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Throttle function execution to reduce frequency
|
|
18
|
+
* Uses requestAnimationFrame for smooth UI updates
|
|
19
|
+
*/
|
|
20
|
+
export declare function throttle<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Debounce function execution to reduce frequency
|
|
23
|
+
* Waits for a pause in calls before executing
|
|
24
|
+
*/
|
|
25
|
+
export declare function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void;
|
|
26
|
+
/**
|
|
27
|
+
* RequestAnimationFrame-based throttle for smooth animations
|
|
28
|
+
* Better for visual updates like node dragging
|
|
29
|
+
*/
|
|
30
|
+
export declare function rafThrottle<T extends (...args: any[]) => void>(func: T): (...args: Parameters<T>) => void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Utilities
|
|
3
|
+
* Helper functions for optimizing performance in the FlowDrop app
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Fast shallow comparison for workflow nodes
|
|
7
|
+
* Avoids expensive JSON.stringify operations
|
|
8
|
+
*/
|
|
9
|
+
export function areNodeArraysEqual(nodes1, nodes2) {
|
|
10
|
+
if (nodes1.length !== nodes2.length)
|
|
11
|
+
return false;
|
|
12
|
+
for (let i = 0; i < nodes1.length; i++) {
|
|
13
|
+
const node1 = nodes1[i];
|
|
14
|
+
const node2 = nodes2[i];
|
|
15
|
+
// Quick ID check
|
|
16
|
+
if (node1?.id !== node2?.id)
|
|
17
|
+
return false;
|
|
18
|
+
// Check position (most common change during drag)
|
|
19
|
+
if (node1?.position?.x !== node2?.position?.x ||
|
|
20
|
+
node1?.position?.y !== node2?.position?.y) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
// Check selected state
|
|
24
|
+
if (node1?.selected !== node2?.selected)
|
|
25
|
+
return false;
|
|
26
|
+
// Skip deep config comparison unless we need to
|
|
27
|
+
// Most updates are position-based
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fast shallow comparison for workflow edges
|
|
33
|
+
* Avoids expensive JSON.stringify operations
|
|
34
|
+
*/
|
|
35
|
+
export function areEdgeArraysEqual(edges1, edges2) {
|
|
36
|
+
if (edges1.length !== edges2.length)
|
|
37
|
+
return false;
|
|
38
|
+
for (let i = 0; i < edges1.length; i++) {
|
|
39
|
+
const edge1 = edges1[i];
|
|
40
|
+
const edge2 = edges2[i];
|
|
41
|
+
if (edge1?.id !== edge2?.id ||
|
|
42
|
+
edge1?.source !== edge2?.source ||
|
|
43
|
+
edge1?.target !== edge2?.target ||
|
|
44
|
+
edge1?.sourceHandle !== edge2?.sourceHandle ||
|
|
45
|
+
edge1?.targetHandle !== edge2?.targetHandle) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Throttle function execution to reduce frequency
|
|
53
|
+
* Uses requestAnimationFrame for smooth UI updates
|
|
54
|
+
*/
|
|
55
|
+
export function throttle(func, wait) {
|
|
56
|
+
let timeout = null;
|
|
57
|
+
let lastRan = 0;
|
|
58
|
+
return function (...args) {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
if (!lastRan || now - lastRan >= wait) {
|
|
61
|
+
func(...args);
|
|
62
|
+
lastRan = now;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (timeout) {
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
}
|
|
68
|
+
timeout = setTimeout(() => {
|
|
69
|
+
func(...args);
|
|
70
|
+
lastRan = Date.now();
|
|
71
|
+
}, wait - (now - lastRan));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Debounce function execution to reduce frequency
|
|
77
|
+
* Waits for a pause in calls before executing
|
|
78
|
+
*/
|
|
79
|
+
export function debounce(func, wait) {
|
|
80
|
+
let timeout = null;
|
|
81
|
+
return function (...args) {
|
|
82
|
+
if (timeout) {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
}
|
|
85
|
+
timeout = setTimeout(() => {
|
|
86
|
+
func(...args);
|
|
87
|
+
}, wait);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* RequestAnimationFrame-based throttle for smooth animations
|
|
92
|
+
* Better for visual updates like node dragging
|
|
93
|
+
*/
|
|
94
|
+
export function rafThrottle(func) {
|
|
95
|
+
let rafId = null;
|
|
96
|
+
let lastArgs = null;
|
|
97
|
+
return function (...args) {
|
|
98
|
+
lastArgs = args;
|
|
99
|
+
if (rafId === null) {
|
|
100
|
+
rafId = requestAnimationFrame(() => {
|
|
101
|
+
if (lastArgs) {
|
|
102
|
+
func(...lastArgs);
|
|
103
|
+
}
|
|
104
|
+
rafId = null;
|
|
105
|
+
lastArgs = null;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@d34dman/flowdrop",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.11",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite dev",
|
|
8
8
|
"build": "vite build && npm run prepack",
|
|
@@ -136,4 +136,4 @@
|
|
|
136
136
|
"svelte-5-french-toast": "^2.0.6",
|
|
137
137
|
"uuid": "^11.1.0"
|
|
138
138
|
}
|
|
139
|
-
}
|
|
139
|
+
}
|