@d34dman/flowdrop 0.0.44 → 0.0.46
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 +2 -2
- package/dist/components/ConfigForm.svelte +4 -20
- package/dist/components/Navbar.svelte +6 -7
- package/dist/components/NodeSidebar.svelte +6 -2
- package/dist/components/SchemaForm.svelte +2 -10
- package/dist/components/WorkflowEditor.svelte +143 -13
- package/dist/components/form/FormAutocomplete.svelte +5 -9
- package/dist/components/form/FormCheckboxGroup.svelte +11 -1
- package/dist/components/form/FormCheckboxGroup.svelte.d.ts +2 -0
- package/dist/components/form/FormCodeEditor.svelte +16 -7
- package/dist/components/form/FormCodeEditor.svelte.d.ts +2 -0
- package/dist/components/form/FormField.svelte +20 -1
- package/dist/components/form/FormMarkdownEditor.svelte +29 -19
- package/dist/components/form/FormMarkdownEditor.svelte.d.ts +2 -0
- package/dist/components/form/FormNumberField.svelte +4 -0
- package/dist/components/form/FormNumberField.svelte.d.ts +2 -0
- package/dist/components/form/FormRangeField.svelte +4 -0
- package/dist/components/form/FormRangeField.svelte.d.ts +2 -0
- package/dist/components/form/FormSelect.svelte +4 -0
- package/dist/components/form/FormSelect.svelte.d.ts +2 -0
- package/dist/components/form/FormTemplateEditor.svelte +16 -7
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +2 -0
- package/dist/components/form/FormTextField.svelte +4 -0
- package/dist/components/form/FormTextField.svelte.d.ts +2 -0
- package/dist/components/form/FormTextarea.svelte +4 -0
- package/dist/components/form/FormTextarea.svelte.d.ts +2 -0
- package/dist/components/form/FormToggle.svelte +4 -0
- package/dist/components/form/FormToggle.svelte.d.ts +2 -0
- package/dist/components/form/types.d.ts +5 -0
- package/dist/components/form/types.js +1 -1
- package/dist/components/layouts/MainLayout.svelte +5 -2
- package/dist/components/nodes/GatewayNode.svelte +99 -86
- package/dist/components/nodes/IdeaNode.svelte +20 -35
- package/dist/components/nodes/NotesNode.svelte +6 -2
- package/dist/components/nodes/SimpleNode.svelte +32 -31
- package/dist/components/nodes/SquareNode.svelte +35 -45
- package/dist/components/nodes/TerminalNode.svelte +25 -61
- package/dist/components/nodes/ToolNode.svelte +36 -18
- package/dist/components/nodes/WorkflowNode.svelte +97 -73
- package/dist/components/playground/Playground.svelte +43 -38
- package/dist/editor/index.d.ts +3 -1
- package/dist/editor/index.js +5 -1
- package/dist/helpers/nodeLayoutHelper.d.ts +14 -0
- package/dist/helpers/nodeLayoutHelper.js +19 -0
- package/dist/helpers/workflowEditorHelper.js +1 -2
- package/dist/services/autoSaveService.js +5 -5
- package/dist/services/historyService.d.ts +207 -0
- package/dist/services/historyService.js +317 -0
- package/dist/services/settingsService.d.ts +2 -2
- package/dist/services/settingsService.js +15 -21
- package/dist/services/toastService.d.ts +1 -1
- package/dist/services/toastService.js +10 -10
- package/dist/stores/historyStore.d.ts +133 -0
- package/dist/stores/historyStore.js +188 -0
- package/dist/stores/settingsStore.d.ts +1 -1
- package/dist/stores/settingsStore.js +40 -42
- package/dist/stores/themeStore.d.ts +2 -2
- package/dist/stores/themeStore.js +30 -32
- package/dist/stores/workflowStore.d.ts +52 -2
- package/dist/stores/workflowStore.js +102 -2
- package/dist/styles/base.css +67 -7
- package/dist/styles/toast.css +3 -1
- package/dist/styles/tokens.css +38 -2
- package/dist/types/settings.d.ts +3 -3
- package/dist/types/settings.js +13 -19
- package/dist/utils/colors.js +18 -18
- package/package.json +1 -1
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @module stores/themeStore
|
|
22
22
|
*/
|
|
23
|
-
import { writable, derived, get } from
|
|
23
|
+
import { writable, derived, get } from 'svelte/store';
|
|
24
24
|
/** localStorage key for persisting theme preference */
|
|
25
|
-
const STORAGE_KEY =
|
|
25
|
+
const STORAGE_KEY = 'flowdrop-theme';
|
|
26
26
|
/** Default theme preference when none is stored */
|
|
27
|
-
const DEFAULT_THEME =
|
|
27
|
+
const DEFAULT_THEME = 'auto';
|
|
28
28
|
// =========================================================================
|
|
29
29
|
// System Preference Detection
|
|
30
30
|
// =========================================================================
|
|
@@ -34,30 +34,28 @@ const DEFAULT_THEME = "auto";
|
|
|
34
34
|
* @returns 'dark' if system prefers dark mode, 'light' otherwise
|
|
35
35
|
*/
|
|
36
36
|
function getSystemTheme() {
|
|
37
|
-
if (typeof window ===
|
|
38
|
-
return
|
|
37
|
+
if (typeof window === 'undefined') {
|
|
38
|
+
return 'light';
|
|
39
39
|
}
|
|
40
|
-
return window.matchMedia(
|
|
41
|
-
? "dark"
|
|
42
|
-
: "light";
|
|
40
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
43
41
|
}
|
|
44
42
|
/**
|
|
45
43
|
* Store for system theme preference
|
|
46
44
|
* Updates when system preference changes
|
|
47
45
|
*/
|
|
48
|
-
const systemTheme = writable(typeof window !==
|
|
46
|
+
const systemTheme = writable(typeof window !== 'undefined' ? getSystemTheme() : 'light');
|
|
49
47
|
// Listen for system theme changes
|
|
50
|
-
if (typeof window !==
|
|
51
|
-
const mediaQuery = window.matchMedia(
|
|
48
|
+
if (typeof window !== 'undefined') {
|
|
49
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
52
50
|
/**
|
|
53
51
|
* Handler for system theme preference changes
|
|
54
52
|
*/
|
|
55
53
|
const handleSystemThemeChange = (event) => {
|
|
56
|
-
systemTheme.set(event.matches ?
|
|
54
|
+
systemTheme.set(event.matches ? 'dark' : 'light');
|
|
57
55
|
};
|
|
58
56
|
// Modern browsers use addEventListener
|
|
59
57
|
if (mediaQuery.addEventListener) {
|
|
60
|
-
mediaQuery.addEventListener(
|
|
58
|
+
mediaQuery.addEventListener('change', handleSystemThemeChange);
|
|
61
59
|
}
|
|
62
60
|
else {
|
|
63
61
|
// Fallback for older browsers
|
|
@@ -73,18 +71,18 @@ if (typeof window !== "undefined") {
|
|
|
73
71
|
* @returns Saved theme preference or default
|
|
74
72
|
*/
|
|
75
73
|
function loadSavedTheme() {
|
|
76
|
-
if (typeof window ===
|
|
74
|
+
if (typeof window === 'undefined') {
|
|
77
75
|
return DEFAULT_THEME;
|
|
78
76
|
}
|
|
79
77
|
try {
|
|
80
78
|
const saved = localStorage.getItem(STORAGE_KEY);
|
|
81
|
-
if (saved ===
|
|
79
|
+
if (saved === 'light' || saved === 'dark' || saved === 'auto') {
|
|
82
80
|
return saved;
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
catch {
|
|
86
84
|
// localStorage may be unavailable (e.g., private browsing)
|
|
87
|
-
console.warn(
|
|
85
|
+
console.warn('Failed to load theme from localStorage');
|
|
88
86
|
}
|
|
89
87
|
return DEFAULT_THEME;
|
|
90
88
|
}
|
|
@@ -94,7 +92,7 @@ function loadSavedTheme() {
|
|
|
94
92
|
* @param theme - Theme preference to save
|
|
95
93
|
*/
|
|
96
94
|
function saveTheme(theme) {
|
|
97
|
-
if (typeof window ===
|
|
95
|
+
if (typeof window === 'undefined') {
|
|
98
96
|
return;
|
|
99
97
|
}
|
|
100
98
|
try {
|
|
@@ -102,7 +100,7 @@ function saveTheme(theme) {
|
|
|
102
100
|
}
|
|
103
101
|
catch {
|
|
104
102
|
// localStorage may be unavailable
|
|
105
|
-
console.warn(
|
|
103
|
+
console.warn('Failed to save theme to localStorage');
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
/**
|
|
@@ -119,7 +117,7 @@ export const theme = writable(loadSavedTheme());
|
|
|
119
117
|
* When theme is 'auto', this reflects the system preference
|
|
120
118
|
*/
|
|
121
119
|
export const resolvedTheme = derived([theme, systemTheme], ([$theme, $systemTheme]) => {
|
|
122
|
-
if ($theme ===
|
|
120
|
+
if ($theme === 'auto') {
|
|
123
121
|
return $systemTheme;
|
|
124
122
|
}
|
|
125
123
|
return $theme;
|
|
@@ -134,10 +132,10 @@ export const resolvedTheme = derived([theme, systemTheme], ([$theme, $systemThem
|
|
|
134
132
|
* @param resolved - The resolved theme to apply
|
|
135
133
|
*/
|
|
136
134
|
function applyTheme(resolved) {
|
|
137
|
-
if (typeof document ===
|
|
135
|
+
if (typeof document === 'undefined') {
|
|
138
136
|
return;
|
|
139
137
|
}
|
|
140
|
-
document.documentElement.setAttribute(
|
|
138
|
+
document.documentElement.setAttribute('data-theme', resolved);
|
|
141
139
|
}
|
|
142
140
|
/**
|
|
143
141
|
* Set the theme preference
|
|
@@ -155,13 +153,13 @@ export function setTheme(newTheme) {
|
|
|
155
153
|
export function toggleTheme() {
|
|
156
154
|
const currentTheme = get(theme);
|
|
157
155
|
const currentResolved = get(resolvedTheme);
|
|
158
|
-
if (currentTheme ===
|
|
156
|
+
if (currentTheme === 'auto') {
|
|
159
157
|
// Switch to opposite of system preference
|
|
160
|
-
setTheme(currentResolved ===
|
|
158
|
+
setTheme(currentResolved === 'dark' ? 'light' : 'dark');
|
|
161
159
|
}
|
|
162
160
|
else {
|
|
163
161
|
// Toggle between light and dark
|
|
164
|
-
setTheme(currentTheme ===
|
|
162
|
+
setTheme(currentTheme === 'dark' ? 'light' : 'dark');
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
/**
|
|
@@ -170,14 +168,14 @@ export function toggleTheme() {
|
|
|
170
168
|
export function cycleTheme() {
|
|
171
169
|
const currentTheme = get(theme);
|
|
172
170
|
switch (currentTheme) {
|
|
173
|
-
case
|
|
174
|
-
setTheme(
|
|
171
|
+
case 'light':
|
|
172
|
+
setTheme('dark');
|
|
175
173
|
break;
|
|
176
|
-
case
|
|
177
|
-
setTheme(
|
|
174
|
+
case 'dark':
|
|
175
|
+
setTheme('auto');
|
|
178
176
|
break;
|
|
179
|
-
case
|
|
180
|
-
setTheme(
|
|
177
|
+
case 'auto':
|
|
178
|
+
setTheme('light');
|
|
181
179
|
break;
|
|
182
180
|
}
|
|
183
181
|
}
|
|
@@ -208,8 +206,8 @@ export function initializeTheme() {
|
|
|
208
206
|
* @returns true if running in browser and theme is applied
|
|
209
207
|
*/
|
|
210
208
|
export function isThemeInitialized() {
|
|
211
|
-
if (typeof document ===
|
|
209
|
+
if (typeof document === 'undefined') {
|
|
212
210
|
return false;
|
|
213
211
|
}
|
|
214
|
-
return document.documentElement.hasAttribute(
|
|
212
|
+
return document.documentElement.hasAttribute('data-theme');
|
|
215
213
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Store for FlowDrop
|
|
3
3
|
*
|
|
4
|
-
* Provides global state management for workflows with dirty state tracking
|
|
4
|
+
* Provides global state management for workflows with dirty state tracking
|
|
5
|
+
* and undo/redo history integration.
|
|
5
6
|
*
|
|
6
7
|
* @module stores/workflowStore
|
|
7
8
|
*/
|
|
@@ -40,6 +41,28 @@ export declare function markAsSaved(): void;
|
|
|
40
41
|
* @returns true if there are unsaved changes
|
|
41
42
|
*/
|
|
42
43
|
export declare function isDirty(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Enable or disable history recording
|
|
46
|
+
*
|
|
47
|
+
* Useful for bulk operations where you don't want individual history entries.
|
|
48
|
+
*
|
|
49
|
+
* @param enabled - Whether history should be recorded
|
|
50
|
+
*/
|
|
51
|
+
export declare function setHistoryEnabled(enabled: boolean): void;
|
|
52
|
+
/**
|
|
53
|
+
* Check if history recording is enabled
|
|
54
|
+
*
|
|
55
|
+
* @returns true if history is being recorded
|
|
56
|
+
*/
|
|
57
|
+
export declare function isHistoryEnabled(): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Set the restoring from history flag
|
|
60
|
+
*
|
|
61
|
+
* Used internally by the history store when performing undo/redo.
|
|
62
|
+
*
|
|
63
|
+
* @param restoring - Whether we're currently restoring from history
|
|
64
|
+
*/
|
|
65
|
+
export declare function setRestoringFromHistory(restoring: boolean): void;
|
|
43
66
|
/**
|
|
44
67
|
* Get the current workflow
|
|
45
68
|
*
|
|
@@ -74,13 +97,22 @@ export declare const workflowActions: {
|
|
|
74
97
|
/**
|
|
75
98
|
* Initialize workflow (from load or new)
|
|
76
99
|
*
|
|
77
|
-
* This sets the initial saved snapshot
|
|
100
|
+
* This sets the initial saved snapshot, clears dirty state, and initializes history.
|
|
78
101
|
*/
|
|
79
102
|
initialize: (workflow: Workflow) => void;
|
|
80
103
|
/**
|
|
81
104
|
* Update the entire workflow
|
|
105
|
+
*
|
|
106
|
+
* Note: This is typically called from SvelteFlow sync and should not push to history
|
|
107
|
+
* for every small change. History is pushed by specific actions or drag handlers.
|
|
82
108
|
*/
|
|
83
109
|
updateWorkflow: (workflow: Workflow) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Restore workflow from history (undo/redo)
|
|
112
|
+
*
|
|
113
|
+
* This bypasses history recording to prevent recursive loops.
|
|
114
|
+
*/
|
|
115
|
+
restoreFromHistory: (workflow: Workflow) => void;
|
|
84
116
|
/**
|
|
85
117
|
* Update nodes
|
|
86
118
|
*/
|
|
@@ -99,6 +131,9 @@ export declare const workflowActions: {
|
|
|
99
131
|
addNode: (node: WorkflowNode) => void;
|
|
100
132
|
/**
|
|
101
133
|
* Remove a node
|
|
134
|
+
*
|
|
135
|
+
* This is an atomic operation that also removes connected edges.
|
|
136
|
+
* A single undo will restore both the node and its edges.
|
|
102
137
|
*/
|
|
103
138
|
removeNode: (nodeId: string) => void;
|
|
104
139
|
/**
|
|
@@ -111,10 +146,14 @@ export declare const workflowActions: {
|
|
|
111
146
|
removeEdge: (edgeId: string) => void;
|
|
112
147
|
/**
|
|
113
148
|
* Update a specific node
|
|
149
|
+
*
|
|
150
|
+
* Used for config changes. Pushes to history for undo support.
|
|
114
151
|
*/
|
|
115
152
|
updateNode: (nodeId: string, updates: Partial<WorkflowNode>) => void;
|
|
116
153
|
/**
|
|
117
154
|
* Clear the workflow
|
|
155
|
+
*
|
|
156
|
+
* Resets the workflow and clears history.
|
|
118
157
|
*/
|
|
119
158
|
clear: () => void;
|
|
120
159
|
/**
|
|
@@ -125,6 +164,7 @@ export declare const workflowActions: {
|
|
|
125
164
|
* Batch update nodes and edges
|
|
126
165
|
*
|
|
127
166
|
* Useful for complex operations that update multiple things at once.
|
|
167
|
+
* Creates a single history entry for the entire batch.
|
|
128
168
|
*/
|
|
129
169
|
batchUpdate: (updates: {
|
|
130
170
|
nodes?: WorkflowNode[];
|
|
@@ -133,6 +173,16 @@ export declare const workflowActions: {
|
|
|
133
173
|
description?: string;
|
|
134
174
|
metadata?: Partial<Workflow["metadata"]>;
|
|
135
175
|
}) => void;
|
|
176
|
+
/**
|
|
177
|
+
* Push current state to history manually
|
|
178
|
+
*
|
|
179
|
+
* Use this before operations that modify the workflow through other means
|
|
180
|
+
* (e.g., drag operations handled by SvelteFlow directly).
|
|
181
|
+
*
|
|
182
|
+
* @param description - Description of the upcoming change
|
|
183
|
+
* @param workflow - Optional workflow to push (uses store state if not provided)
|
|
184
|
+
*/
|
|
185
|
+
pushHistory: (description?: string, workflow?: Workflow) => void;
|
|
136
186
|
};
|
|
137
187
|
/** Derived store for workflow changes (useful for triggering saves) */
|
|
138
188
|
export declare const workflowChanged: import("svelte/store").Readable<{
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Store for FlowDrop
|
|
3
3
|
*
|
|
4
|
-
* Provides global state management for workflows with dirty state tracking
|
|
4
|
+
* Provides global state management for workflows with dirty state tracking
|
|
5
|
+
* and undo/redo history integration.
|
|
5
6
|
*
|
|
6
7
|
* @module stores/workflowStore
|
|
7
8
|
*/
|
|
8
9
|
import { writable, derived, get } from 'svelte/store';
|
|
10
|
+
import { historyService } from '../services/historyService.js';
|
|
9
11
|
// =========================================================================
|
|
10
12
|
// Core Workflow Store
|
|
11
13
|
// =========================================================================
|
|
@@ -39,6 +41,18 @@ let onDirtyStateChangeCallback = null;
|
|
|
39
41
|
* Set by the App component to notify parent application.
|
|
40
42
|
*/
|
|
41
43
|
let onWorkflowChangeCallback = null;
|
|
44
|
+
/**
|
|
45
|
+
* Flag to track if we're currently restoring from history (undo/redo)
|
|
46
|
+
*
|
|
47
|
+
* When true, prevents pushing to history to avoid recursive loops.
|
|
48
|
+
*/
|
|
49
|
+
let isRestoringFromHistory = false;
|
|
50
|
+
/**
|
|
51
|
+
* Flag to track if history recording is enabled
|
|
52
|
+
*
|
|
53
|
+
* Can be disabled for bulk operations or when history is not needed.
|
|
54
|
+
*/
|
|
55
|
+
let historyEnabled = true;
|
|
42
56
|
/**
|
|
43
57
|
* Set the dirty state change callback
|
|
44
58
|
*
|
|
@@ -136,6 +150,49 @@ export function markAsSaved() {
|
|
|
136
150
|
export function isDirty() {
|
|
137
151
|
return get(isDirtyStore);
|
|
138
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Enable or disable history recording
|
|
155
|
+
*
|
|
156
|
+
* Useful for bulk operations where you don't want individual history entries.
|
|
157
|
+
*
|
|
158
|
+
* @param enabled - Whether history should be recorded
|
|
159
|
+
*/
|
|
160
|
+
export function setHistoryEnabled(enabled) {
|
|
161
|
+
historyEnabled = enabled;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if history recording is enabled
|
|
165
|
+
*
|
|
166
|
+
* @returns true if history is being recorded
|
|
167
|
+
*/
|
|
168
|
+
export function isHistoryEnabled() {
|
|
169
|
+
return historyEnabled;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Set the restoring from history flag
|
|
173
|
+
*
|
|
174
|
+
* Used internally by the history store when performing undo/redo.
|
|
175
|
+
*
|
|
176
|
+
* @param restoring - Whether we're currently restoring from history
|
|
177
|
+
*/
|
|
178
|
+
export function setRestoringFromHistory(restoring) {
|
|
179
|
+
isRestoringFromHistory = restoring;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Push current state to history before making changes
|
|
183
|
+
*
|
|
184
|
+
* @param description - Description of the change about to be made
|
|
185
|
+
* @param workflow - Optional workflow to push (uses store if not provided)
|
|
186
|
+
*/
|
|
187
|
+
function pushToHistory(description, workflow) {
|
|
188
|
+
if (!historyEnabled || isRestoringFromHistory) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const workflowToPush = workflow ?? get(workflowStore);
|
|
192
|
+
if (workflowToPush) {
|
|
193
|
+
historyService.push(workflowToPush, { description });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
139
196
|
/**
|
|
140
197
|
* Get the current workflow
|
|
141
198
|
*
|
|
@@ -218,7 +275,7 @@ export const workflowActions = {
|
|
|
218
275
|
/**
|
|
219
276
|
* Initialize workflow (from load or new)
|
|
220
277
|
*
|
|
221
|
-
* This sets the initial saved snapshot
|
|
278
|
+
* This sets the initial saved snapshot, clears dirty state, and initializes history.
|
|
222
279
|
*/
|
|
223
280
|
initialize: (workflow) => {
|
|
224
281
|
workflowStore.set(workflow);
|
|
@@ -228,14 +285,30 @@ export const workflowActions = {
|
|
|
228
285
|
if (onDirtyStateChangeCallback) {
|
|
229
286
|
onDirtyStateChangeCallback(false);
|
|
230
287
|
}
|
|
288
|
+
// Initialize history with the loaded workflow
|
|
289
|
+
historyService.initialize(workflow);
|
|
231
290
|
},
|
|
232
291
|
/**
|
|
233
292
|
* Update the entire workflow
|
|
293
|
+
*
|
|
294
|
+
* Note: This is typically called from SvelteFlow sync and should not push to history
|
|
295
|
+
* for every small change. History is pushed by specific actions or drag handlers.
|
|
234
296
|
*/
|
|
235
297
|
updateWorkflow: (workflow) => {
|
|
236
298
|
workflowStore.set(workflow);
|
|
237
299
|
notifyWorkflowChange('metadata');
|
|
238
300
|
},
|
|
301
|
+
/**
|
|
302
|
+
* Restore workflow from history (undo/redo)
|
|
303
|
+
*
|
|
304
|
+
* This bypasses history recording to prevent recursive loops.
|
|
305
|
+
*/
|
|
306
|
+
restoreFromHistory: (workflow) => {
|
|
307
|
+
isRestoringFromHistory = true;
|
|
308
|
+
workflowStore.set(workflow);
|
|
309
|
+
notifyWorkflowChange('metadata');
|
|
310
|
+
isRestoringFromHistory = false;
|
|
311
|
+
},
|
|
239
312
|
/**
|
|
240
313
|
* Update nodes
|
|
241
314
|
*/
|
|
@@ -310,6 +383,7 @@ export const workflowActions = {
|
|
|
310
383
|
* Add a node
|
|
311
384
|
*/
|
|
312
385
|
addNode: (node) => {
|
|
386
|
+
pushToHistory('Add node');
|
|
313
387
|
workflowStore.update(($workflow) => {
|
|
314
388
|
if (!$workflow)
|
|
315
389
|
return null;
|
|
@@ -326,8 +400,12 @@ export const workflowActions = {
|
|
|
326
400
|
},
|
|
327
401
|
/**
|
|
328
402
|
* Remove a node
|
|
403
|
+
*
|
|
404
|
+
* This is an atomic operation that also removes connected edges.
|
|
405
|
+
* A single undo will restore both the node and its edges.
|
|
329
406
|
*/
|
|
330
407
|
removeNode: (nodeId) => {
|
|
408
|
+
pushToHistory('Delete node');
|
|
331
409
|
workflowStore.update(($workflow) => {
|
|
332
410
|
if (!$workflow)
|
|
333
411
|
return null;
|
|
@@ -347,6 +425,7 @@ export const workflowActions = {
|
|
|
347
425
|
* Add an edge
|
|
348
426
|
*/
|
|
349
427
|
addEdge: (edge) => {
|
|
428
|
+
pushToHistory('Add connection');
|
|
350
429
|
workflowStore.update(($workflow) => {
|
|
351
430
|
if (!$workflow)
|
|
352
431
|
return null;
|
|
@@ -365,6 +444,7 @@ export const workflowActions = {
|
|
|
365
444
|
* Remove an edge
|
|
366
445
|
*/
|
|
367
446
|
removeEdge: (edgeId) => {
|
|
447
|
+
pushToHistory('Delete connection');
|
|
368
448
|
workflowStore.update(($workflow) => {
|
|
369
449
|
if (!$workflow)
|
|
370
450
|
return null;
|
|
@@ -381,8 +461,11 @@ export const workflowActions = {
|
|
|
381
461
|
},
|
|
382
462
|
/**
|
|
383
463
|
* Update a specific node
|
|
464
|
+
*
|
|
465
|
+
* Used for config changes. Pushes to history for undo support.
|
|
384
466
|
*/
|
|
385
467
|
updateNode: (nodeId, updates) => {
|
|
468
|
+
pushToHistory('Update node config');
|
|
386
469
|
workflowStore.update(($workflow) => {
|
|
387
470
|
if (!$workflow)
|
|
388
471
|
return null;
|
|
@@ -399,11 +482,14 @@ export const workflowActions = {
|
|
|
399
482
|
},
|
|
400
483
|
/**
|
|
401
484
|
* Clear the workflow
|
|
485
|
+
*
|
|
486
|
+
* Resets the workflow and clears history.
|
|
402
487
|
*/
|
|
403
488
|
clear: () => {
|
|
404
489
|
workflowStore.set(null);
|
|
405
490
|
savedSnapshot = null;
|
|
406
491
|
isDirtyStore.set(false);
|
|
492
|
+
historyService.clear();
|
|
407
493
|
if (onDirtyStateChangeCallback) {
|
|
408
494
|
onDirtyStateChangeCallback(false);
|
|
409
495
|
}
|
|
@@ -430,8 +516,10 @@ export const workflowActions = {
|
|
|
430
516
|
* Batch update nodes and edges
|
|
431
517
|
*
|
|
432
518
|
* Useful for complex operations that update multiple things at once.
|
|
519
|
+
* Creates a single history entry for the entire batch.
|
|
433
520
|
*/
|
|
434
521
|
batchUpdate: (updates) => {
|
|
522
|
+
pushToHistory('Batch update');
|
|
435
523
|
workflowStore.update(($workflow) => {
|
|
436
524
|
if (!$workflow)
|
|
437
525
|
return null;
|
|
@@ -449,6 +537,18 @@ export const workflowActions = {
|
|
|
449
537
|
};
|
|
450
538
|
});
|
|
451
539
|
notifyWorkflowChange('metadata');
|
|
540
|
+
},
|
|
541
|
+
/**
|
|
542
|
+
* Push current state to history manually
|
|
543
|
+
*
|
|
544
|
+
* Use this before operations that modify the workflow through other means
|
|
545
|
+
* (e.g., drag operations handled by SvelteFlow directly).
|
|
546
|
+
*
|
|
547
|
+
* @param description - Description of the upcoming change
|
|
548
|
+
* @param workflow - Optional workflow to push (uses store state if not provided)
|
|
549
|
+
*/
|
|
550
|
+
pushHistory: (description, workflow) => {
|
|
551
|
+
pushToHistory(description, workflow);
|
|
452
552
|
}
|
|
453
553
|
};
|
|
454
554
|
// =========================================================================
|
package/dist/styles/base.css
CHANGED
|
@@ -30,6 +30,46 @@
|
|
|
30
30
|
box-shadow: var(--fd-shadow-md);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* Flow node handles: 20px connection area, 12px visible circle (::before)
|
|
34
|
+
Override xyflow's default background so port color (--fd-handle-fill from inline style) shows */
|
|
35
|
+
:global(.svelte-flow__handle) {
|
|
36
|
+
--fd-handle-fill: var(--fd-muted-foreground);
|
|
37
|
+
--fd-handle-border-color: var(--fd-handle-border);
|
|
38
|
+
width: var(--fd-handle-size);
|
|
39
|
+
height: var(--fd-handle-size);
|
|
40
|
+
background: transparent !important;
|
|
41
|
+
background-color: transparent !important;
|
|
42
|
+
border: none;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
transition: transform var(--fd-transition-normal);
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:global(.svelte-flow__handle::before) {
|
|
52
|
+
content: '';
|
|
53
|
+
position: absolute;
|
|
54
|
+
inset: 0;
|
|
55
|
+
margin: auto;
|
|
56
|
+
width: var(--fd-handle-visual-size);
|
|
57
|
+
height: var(--fd-handle-visual-size);
|
|
58
|
+
background-color: var(--fd-handle-fill) !important;
|
|
59
|
+
border: 2px solid var(--fd-handle-border-color);
|
|
60
|
+
border-radius: 50%;
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:global(.svelte-flow__handle:hover::before) {
|
|
65
|
+
background-color: var(--fd-primary);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:global(.svelte-flow__handle:focus) {
|
|
69
|
+
outline: 2px solid var(--fd-ring);
|
|
70
|
+
outline-offset: 2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
33
73
|
/* Button styles */
|
|
34
74
|
.flowdrop-btn {
|
|
35
75
|
display: inline-flex;
|
|
@@ -874,7 +914,11 @@
|
|
|
874
914
|
========================================================================= */
|
|
875
915
|
|
|
876
916
|
/* Interrupt state: Pending (awaiting user response) */
|
|
877
|
-
--fd-interrupt-pending-bg: linear-gradient(
|
|
917
|
+
--fd-interrupt-pending-bg: linear-gradient(
|
|
918
|
+
135deg,
|
|
919
|
+
var(--fd-warning-muted) 0%,
|
|
920
|
+
var(--fd-warning-muted) 100%
|
|
921
|
+
);
|
|
878
922
|
--fd-interrupt-pending-border: var(--fd-warning);
|
|
879
923
|
--fd-interrupt-pending-shadow: rgba(245, 158, 11, 0.15);
|
|
880
924
|
--fd-interrupt-pending-avatar: var(--fd-warning);
|
|
@@ -882,7 +926,11 @@
|
|
|
882
926
|
--fd-interrupt-pending-text-light: var(--fd-warning-hover);
|
|
883
927
|
|
|
884
928
|
/* Interrupt state: Completed (response received - neutral) */
|
|
885
|
-
--fd-interrupt-completed-bg: linear-gradient(
|
|
929
|
+
--fd-interrupt-completed-bg: linear-gradient(
|
|
930
|
+
135deg,
|
|
931
|
+
var(--fd-info-muted) 0%,
|
|
932
|
+
var(--fd-primary-muted) 100%
|
|
933
|
+
);
|
|
886
934
|
--fd-interrupt-completed-border: var(--fd-primary);
|
|
887
935
|
--fd-interrupt-completed-shadow: rgba(59, 130, 246, 0.15);
|
|
888
936
|
--fd-interrupt-completed-avatar: var(--fd-primary);
|
|
@@ -898,7 +946,11 @@
|
|
|
898
946
|
--fd-interrupt-cancelled-text-light: var(--fd-muted-foreground);
|
|
899
947
|
|
|
900
948
|
/* Interrupt state: Error */
|
|
901
|
-
--fd-interrupt-error-bg: linear-gradient(
|
|
949
|
+
--fd-interrupt-error-bg: linear-gradient(
|
|
950
|
+
135deg,
|
|
951
|
+
var(--fd-error-muted) 0%,
|
|
952
|
+
var(--fd-error-muted) 100%
|
|
953
|
+
);
|
|
902
954
|
--fd-interrupt-error-border: var(--fd-error);
|
|
903
955
|
--fd-interrupt-error-shadow: rgba(239, 68, 68, 0.15);
|
|
904
956
|
--fd-interrupt-error-avatar: var(--fd-error);
|
|
@@ -913,8 +965,16 @@
|
|
|
913
965
|
--fd-interrupt-prompt-border-error: rgba(239, 68, 68, 0.2);
|
|
914
966
|
|
|
915
967
|
/* Interrupt button tokens */
|
|
916
|
-
--fd-interrupt-btn-primary-bg: linear-gradient(
|
|
917
|
-
|
|
968
|
+
--fd-interrupt-btn-primary-bg: linear-gradient(
|
|
969
|
+
135deg,
|
|
970
|
+
var(--fd-primary) 0%,
|
|
971
|
+
var(--fd-primary-hover) 100%
|
|
972
|
+
);
|
|
973
|
+
--fd-interrupt-btn-primary-bg-hover: linear-gradient(
|
|
974
|
+
135deg,
|
|
975
|
+
var(--fd-primary-hover) 0%,
|
|
976
|
+
var(--fd-primary-hover) 100%
|
|
977
|
+
);
|
|
918
978
|
--fd-interrupt-btn-primary-shadow: rgba(59, 130, 246, 0.3);
|
|
919
979
|
--fd-interrupt-btn-secondary-bg: var(--fd-muted);
|
|
920
980
|
--fd-interrupt-btn-secondary-border: var(--fd-border);
|
|
@@ -947,7 +1007,7 @@
|
|
|
947
1007
|
}
|
|
948
1008
|
|
|
949
1009
|
/* Dark mode overrides for interrupt component tokens */
|
|
950
|
-
[data-theme=
|
|
1010
|
+
[data-theme='dark'] {
|
|
951
1011
|
/* Interrupt prompt inner styling - dark background for dark mode */
|
|
952
1012
|
--fd-interrupt-prompt-bg: rgba(30, 30, 35, 0.95);
|
|
953
1013
|
--fd-interrupt-prompt-border-pending: rgba(251, 191, 36, 0.25);
|
|
@@ -1176,4 +1236,4 @@
|
|
|
1176
1236
|
|
|
1177
1237
|
.markdown-display--large h3 {
|
|
1178
1238
|
font-size: 1.25rem;
|
|
1179
|
-
}
|
|
1239
|
+
}
|
package/dist/styles/toast.css
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
max-width: 350px;
|
|
29
29
|
font-size: var(--fd-text-sm);
|
|
30
30
|
line-height: 1.3;
|
|
31
|
-
transition:
|
|
31
|
+
transition:
|
|
32
|
+
background var(--fd-transition-fast),
|
|
33
|
+
border-color var(--fd-transition-fast),
|
|
32
34
|
box-shadow var(--fd-transition-fast);
|
|
33
35
|
}
|
package/dist/styles/tokens.css
CHANGED
|
@@ -218,6 +218,9 @@
|
|
|
218
218
|
|
|
219
219
|
/** Icon on node squircle: dark in light theme, white in dark theme */
|
|
220
220
|
--fd-node-icon: var(--_gray-9);
|
|
221
|
+
/** Opacity of the icon squircle background (color-mix percentage). Light mode: 15% / 22% hover; dark mode uses higher values. */
|
|
222
|
+
--fd-node-icon-bg-opacity: 20%;
|
|
223
|
+
--fd-node-icon-bg-opacity-hover: 50%;
|
|
221
224
|
|
|
222
225
|
/* ----- EDGE TOKENS ----- */
|
|
223
226
|
--fd-edge-trigger: var(--_gray-9);
|
|
@@ -254,6 +257,22 @@
|
|
|
254
257
|
/* ----- HANDLE (Node connection points) ----- */
|
|
255
258
|
/* White ring around handles for contrast against node backgrounds */
|
|
256
259
|
--fd-handle-border: #ffffff;
|
|
260
|
+
/* Outer size = connection/hit area (px); visual = visible circle (px) */
|
|
261
|
+
--fd-handle-size: 20px;
|
|
262
|
+
--fd-handle-visual-size: 12px;
|
|
263
|
+
|
|
264
|
+
/* ----- NODE LAYOUT (Dimensions and port alignment; multiples of 10 for grid) ----- */
|
|
265
|
+
--fd-node-grid-step: 10;
|
|
266
|
+
--fd-node-default-width: 290px;
|
|
267
|
+
--fd-node-header-height: 60px;
|
|
268
|
+
--fd-node-header-title-height: 40px;
|
|
269
|
+
--fd-node-header-desc-line: 20px;
|
|
270
|
+
--fd-node-header-gap: 10px;
|
|
271
|
+
--fd-node-port-row-height: 20px;
|
|
272
|
+
--fd-node-terminal-size: 80px;
|
|
273
|
+
--fd-node-square-size: 80px;
|
|
274
|
+
--fd-node-simple-height: 80px;
|
|
275
|
+
--fd-node-tool-min-height: 80px;
|
|
257
276
|
|
|
258
277
|
/* ----- LAYOUT BACKGROUND (Main content area gradient) ----- */
|
|
259
278
|
--fd-layout-background: linear-gradient(135deg, #f9fafb 0%, #e0e7ff 50%, #c7d2fe 100%);
|
|
@@ -265,7 +284,7 @@
|
|
|
265
284
|
Designed for softer contrast and reduced eye strain
|
|
266
285
|
========================================================================= */
|
|
267
286
|
|
|
268
|
-
[data-theme=
|
|
287
|
+
[data-theme='dark'] {
|
|
269
288
|
/* ----- SURFACES (Backgrounds) - Softer contrast ----- */
|
|
270
289
|
--fd-background: #1a1a1e;
|
|
271
290
|
--fd-foreground: var(--_gray-2);
|
|
@@ -296,8 +315,25 @@
|
|
|
296
315
|
--fd-node-border: #4a4a52;
|
|
297
316
|
--fd-node-border-hover: #5a5a62;
|
|
298
317
|
|
|
318
|
+
/* ----- NODE COLORS (port type labels/badges - lighter for readability on dark surfaces) ----- */
|
|
319
|
+
--fd-node-emerald: #34d399;
|
|
320
|
+
--fd-node-blue: #60a5fa;
|
|
321
|
+
--fd-node-amber: #fbbf24;
|
|
322
|
+
--fd-node-orange: #fb923c;
|
|
323
|
+
--fd-node-red: #f87171;
|
|
324
|
+
--fd-node-pink: #f472b6;
|
|
325
|
+
--fd-node-indigo: #818cf8;
|
|
326
|
+
--fd-node-teal: #2dd4bf;
|
|
327
|
+
--fd-node-cyan: #22d3ee;
|
|
328
|
+
--fd-node-lime: #a3e635;
|
|
329
|
+
--fd-node-slate: #94a3b8;
|
|
330
|
+
--fd-node-purple: #c084fc;
|
|
331
|
+
|
|
299
332
|
/** Icon on node squircle: white in dark theme */
|
|
300
333
|
--fd-node-icon: #ffffff;
|
|
334
|
+
/** Icon squircle background opacity: higher in dark mode for visibility on dark surfaces */
|
|
335
|
+
--fd-node-icon-bg-opacity: 50%;
|
|
336
|
+
--fd-node-icon-bg-opacity-hover: 80%;
|
|
301
337
|
|
|
302
338
|
/* ----- PRIMARY (Interactive/Brand) ----- */
|
|
303
339
|
--fd-primary: #60a5fa;
|
|
@@ -363,4 +399,4 @@
|
|
|
363
399
|
|
|
364
400
|
/* ----- LAYOUT BACKGROUND (darker gradient for dark mode) ----- */
|
|
365
401
|
--fd-layout-background: linear-gradient(135deg, #141418 0%, #1a1a2e 50%, #16162a 100%);
|
|
366
|
-
}
|
|
402
|
+
}
|