@flowdrop/flowdrop 2.0.0-beta.1 → 2.0.0-beta.3
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/CHANGELOG.md +67 -0
- package/MIGRATION-2.0.md +173 -3
- package/dist/api/enhanced-client.js +6 -11
- package/dist/components/App.svelte +22 -45
- package/dist/components/App.svelte.d.ts +2 -7
- package/dist/components/CanvasIconButton.svelte +76 -0
- package/dist/components/CanvasIconButton.svelte.d.ts +18 -0
- package/dist/components/ConfigForm.svelte +6 -21
- package/dist/components/ConfigPanel.svelte +4 -3
- package/dist/components/LogoWordmark.svelte +113 -0
- package/dist/components/LogoWordmark.svelte.d.ts +26 -0
- package/dist/components/Navbar.svelte +8 -59
- package/dist/components/NodeSidebar.svelte +4 -11
- package/dist/components/NodeSwapPicker.svelte +0 -2
- package/dist/components/PipelineStatus.svelte +6 -1
- package/dist/components/PipelineStatus.svelte.d.ts +3 -0
- package/dist/components/PortMappingRow.svelte +0 -2
- package/dist/components/SchemaForm.svelte +4 -14
- package/dist/components/SettingsModal.svelte +0 -5
- package/dist/components/SettingsPanel.svelte +2 -6
- package/dist/components/ThemeToggle.svelte +0 -5
- package/dist/components/UniversalNode.svelte +32 -1
- package/dist/components/WorkflowEditor.svelte +66 -52
- package/dist/components/WorkflowEditor.svelte.d.ts +21 -0
- package/dist/components/chat/AIChatPanel.svelte +7 -2
- package/dist/components/console/ConsoleAutocomplete.svelte +1 -1
- package/dist/components/console/ConsoleOutput.svelte +2 -2
- package/dist/components/form/FormArray.svelte +0 -16
- package/dist/components/form/FormAutocomplete.svelte +18 -15
- package/dist/components/form/FormCheckboxGroup.svelte +0 -4
- package/dist/components/form/FormCodeEditor.svelte +9 -7
- package/dist/components/form/FormFieldLight.svelte +33 -4
- package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
- package/dist/components/form/FormMarkdownEditor.svelte +8 -5
- package/dist/components/form/FormNumberField.svelte +0 -4
- package/dist/components/form/FormRangeField.svelte +1 -20
- package/dist/components/form/FormSelect.svelte +10 -6
- package/dist/components/form/FormTemplateEditor.svelte +6 -4
- package/dist/components/form/FormTextField.svelte +10 -6
- package/dist/components/form/FormTextarea.svelte +10 -6
- package/dist/components/form/FormToggle.svelte +0 -4
- package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
- package/dist/components/icons/CommandLineIcon.svelte +15 -0
- package/dist/components/icons/CommandLineIcon.svelte.d.ts +26 -0
- package/dist/components/icons/MenuIcon.svelte +4 -0
- package/dist/components/icons/MenuIcon.svelte.d.ts +26 -0
- package/dist/components/icons/MenuOpenIcon.svelte +6 -0
- package/dist/components/icons/MenuOpenIcon.svelte.d.ts +26 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +0 -10
- package/dist/components/interrupt/ConfirmationPrompt.svelte +0 -5
- package/dist/components/interrupt/InterruptBubble.svelte +11 -12
- package/dist/components/interrupt/ReviewPrompt.svelte +0 -20
- package/dist/components/interrupt/TextInputPrompt.svelte +0 -6
- package/dist/components/layouts/MainLayout.svelte +4 -5
- package/dist/components/nodes/AtomNode.svelte +46 -34
- package/dist/components/nodes/GatewayNode.svelte +91 -99
- package/dist/components/nodes/IdeaNode.svelte +62 -90
- package/dist/components/nodes/NodeConfigButton.svelte +86 -0
- package/dist/components/nodes/NodeConfigButton.svelte.d.ts +15 -0
- package/dist/components/nodes/NotesNode.svelte +70 -81
- package/dist/components/nodes/SimpleNode.svelte +28 -78
- package/dist/components/nodes/SquareNode.svelte +79 -109
- package/dist/components/nodes/TerminalNode.svelte +28 -86
- package/dist/components/nodes/ToolNode.svelte +82 -95
- package/dist/components/nodes/WorkflowNode.svelte +91 -100
- package/dist/components/playground/ChatInput.svelte +0 -1
- package/dist/components/playground/InputCollector.svelte +0 -2
- package/dist/components/playground/PipelineKanbanView.svelte +4 -2
- package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
- package/dist/components/playground/PipelinePanel.svelte +20 -3
- package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
- package/dist/components/playground/PipelineTableView.svelte +4 -2
- package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
- package/dist/components/playground/Playground.svelte +76 -25
- package/dist/components/playground/Playground.svelte.d.ts +3 -0
- package/dist/components/playground/PlaygroundApp.svelte +6 -1
- package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
- package/dist/components/playground/PlaygroundModal.svelte +5 -0
- package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
- package/dist/components/playground/PlaygroundStudio.svelte +7 -6
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +2 -1
- package/dist/components/playground/pipelineViewUtils.svelte.js +2 -2
- package/dist/config/endpoints.d.ts +23 -0
- package/dist/config/endpoints.js +28 -0
- package/dist/core/index.d.ts +1 -2
- package/dist/core/index.js +2 -6
- package/dist/display/index.d.ts +6 -1
- package/dist/display/index.js +9 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/form/full.d.ts +2 -1
- package/dist/form/full.js +4 -1
- package/dist/form/index.d.ts +0 -1
- package/dist/form/index.js +3 -2
- package/dist/helpers/workflowEditorHelper.d.ts +4 -2
- package/dist/helpers/workflowEditorHelper.js +4 -3
- package/dist/playground/index.d.ts +2 -2
- package/dist/playground/index.js +2 -2
- package/dist/playground/mount.d.ts +19 -5
- package/dist/playground/mount.js +16 -8
- package/dist/registry/builtinNodeTypes.d.ts +53 -0
- package/dist/registry/builtinNodeTypes.js +67 -0
- package/dist/registry/builtinNodes.d.ts +2 -39
- package/dist/registry/builtinNodes.js +6 -53
- package/dist/services/agentSpecExecutionService.d.ts +0 -2
- package/dist/services/agentSpecExecutionService.js +0 -2
- package/dist/services/apiVariableService.js +12 -26
- package/dist/services/categoriesApi.js +3 -6
- package/dist/services/chatService.d.ts +4 -3
- package/dist/services/chatService.js +13 -18
- package/dist/services/interruptService.d.ts +7 -6
- package/dist/services/interruptService.js +19 -21
- package/dist/services/playgroundService.d.ts +9 -8
- package/dist/services/playgroundService.js +23 -25
- package/dist/services/portConfigApi.js +3 -6
- package/dist/services/settingsService.d.ts +9 -4
- package/dist/services/settingsService.js +23 -12
- package/dist/skins/drafter.d.ts +30 -0
- package/dist/skins/drafter.js +185 -0
- package/dist/skins/index.d.ts +2 -1
- package/dist/skins/index.js +4 -2
- package/dist/stores/apiContext.d.ts +11 -0
- package/dist/stores/apiContext.js +15 -0
- package/dist/stores/categoriesStore.svelte.js +0 -1
- package/dist/stores/playgroundStore.svelte.js +0 -2
- package/dist/styles/base.css +38 -9
- package/dist/styles/tokens.css +54 -2
- package/dist/svelte-app.d.ts +6 -0
- package/dist/svelte-app.js +4 -2
- package/dist/themes/drafter.d.ts +2 -0
- package/dist/themes/drafter.js +15 -0
- package/dist/themes/index.d.ts +2 -1
- package/dist/themes/index.js +8 -2
- package/dist/types/auth.d.ts +9 -51
- package/dist/types/auth.js +4 -54
- package/dist/types/events.d.ts +18 -0
- package/dist/types/events.js +2 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.js +0 -1
- package/dist/types/settings.d.ts +1 -1
- package/dist/types/settings.js +1 -1
- package/dist/types/skin.d.ts +1 -1
- package/dist/types/theme.d.ts +16 -2
- package/dist/utils/edgeStyling.js +9 -5
- package/dist/utils/fetchWithAuth.d.ts +36 -15
- package/dist/utils/fetchWithAuth.js +53 -23
- package/dist/utils/nodeTypes.js +1 -1
- package/package.json +2 -1
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* @module services/playgroundService
|
|
8
8
|
*/
|
|
9
9
|
import { defaultShouldStopPolling } from '../types/playground.js';
|
|
10
|
-
import { buildEndpointUrl
|
|
10
|
+
import { buildEndpointUrl } from '../config/endpoints.js';
|
|
11
|
+
import { authenticatedFetch } from '../utils/fetchWithAuth.js';
|
|
11
12
|
import { logger } from '../utils/logger.js';
|
|
12
13
|
/**
|
|
13
14
|
* Default polling interval in milliseconds
|
|
@@ -64,14 +65,11 @@ export class PlaygroundService {
|
|
|
64
65
|
* @param options - Fetch options
|
|
65
66
|
* @returns The parsed JSON response
|
|
66
67
|
*/
|
|
67
|
-
async request(config, url, options = {}) {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
...headers,
|
|
73
|
-
...options.headers
|
|
74
|
-
}
|
|
68
|
+
async request(config, url, options = {}, authProvider) {
|
|
69
|
+
const response = await authenticatedFetch(url, options, {
|
|
70
|
+
config,
|
|
71
|
+
endpointKey: 'playground',
|
|
72
|
+
authProvider
|
|
75
73
|
});
|
|
76
74
|
if (!response.ok) {
|
|
77
75
|
const errorData = await response.json().catch(() => ({}));
|
|
@@ -92,7 +90,7 @@ export class PlaygroundService {
|
|
|
92
90
|
* @param options - Optional pagination parameters
|
|
93
91
|
* @returns Array of playground sessions
|
|
94
92
|
*/
|
|
95
|
-
async listSessions(endpointConfig, workflowId, options) {
|
|
93
|
+
async listSessions(endpointConfig, workflowId, options, authProvider) {
|
|
96
94
|
const config = this.getConfig(endpointConfig);
|
|
97
95
|
let url = buildEndpointUrl(config, config.endpoints.playground.listSessions, {
|
|
98
96
|
id: workflowId
|
|
@@ -109,7 +107,7 @@ export class PlaygroundService {
|
|
|
109
107
|
if (queryString) {
|
|
110
108
|
url = `${url}?${queryString}`;
|
|
111
109
|
}
|
|
112
|
-
const response = await this.request(config, url);
|
|
110
|
+
const response = await this.request(config, url, {}, authProvider);
|
|
113
111
|
return response.data ?? [];
|
|
114
112
|
}
|
|
115
113
|
/**
|
|
@@ -120,7 +118,7 @@ export class PlaygroundService {
|
|
|
120
118
|
* @param metadata - Optional session metadata
|
|
121
119
|
* @returns The created session
|
|
122
120
|
*/
|
|
123
|
-
async createSession(endpointConfig, workflowId, name, metadata) {
|
|
121
|
+
async createSession(endpointConfig, workflowId, name, metadata, authProvider) {
|
|
124
122
|
const config = this.getConfig(endpointConfig);
|
|
125
123
|
const url = buildEndpointUrl(config, config.endpoints.playground.createSession, {
|
|
126
124
|
id: workflowId
|
|
@@ -128,7 +126,7 @@ export class PlaygroundService {
|
|
|
128
126
|
const response = await this.request(config, url, {
|
|
129
127
|
method: 'POST',
|
|
130
128
|
body: JSON.stringify({ name, metadata })
|
|
131
|
-
});
|
|
129
|
+
}, authProvider);
|
|
132
130
|
if (!response.data) {
|
|
133
131
|
throw new Error('Failed to create session: No data returned');
|
|
134
132
|
}
|
|
@@ -140,12 +138,12 @@ export class PlaygroundService {
|
|
|
140
138
|
* @param sessionId - The session UUID
|
|
141
139
|
* @returns The session details
|
|
142
140
|
*/
|
|
143
|
-
async getSession(endpointConfig, sessionId) {
|
|
141
|
+
async getSession(endpointConfig, sessionId, authProvider) {
|
|
144
142
|
const config = this.getConfig(endpointConfig);
|
|
145
143
|
const url = buildEndpointUrl(config, config.endpoints.playground.getSession, {
|
|
146
144
|
sessionId
|
|
147
145
|
});
|
|
148
|
-
const response = await this.request(config, url);
|
|
146
|
+
const response = await this.request(config, url, {}, authProvider);
|
|
149
147
|
if (!response.data) {
|
|
150
148
|
throw new Error('Session not found');
|
|
151
149
|
}
|
|
@@ -156,14 +154,14 @@ export class PlaygroundService {
|
|
|
156
154
|
*
|
|
157
155
|
* @param sessionId - The session UUID
|
|
158
156
|
*/
|
|
159
|
-
async deleteSession(endpointConfig, sessionId) {
|
|
157
|
+
async deleteSession(endpointConfig, sessionId, authProvider) {
|
|
160
158
|
const config = this.getConfig(endpointConfig);
|
|
161
159
|
const url = buildEndpointUrl(config, config.endpoints.playground.deleteSession, {
|
|
162
160
|
sessionId
|
|
163
161
|
});
|
|
164
162
|
await this.request(config, url, {
|
|
165
163
|
method: 'DELETE'
|
|
166
|
-
});
|
|
164
|
+
}, authProvider);
|
|
167
165
|
}
|
|
168
166
|
// =========================================================================
|
|
169
167
|
// Message Handling
|
|
@@ -181,7 +179,7 @@ export class PlaygroundService {
|
|
|
181
179
|
* @param options - Pagination options
|
|
182
180
|
* @returns Messages and session status
|
|
183
181
|
*/
|
|
184
|
-
async getMessages(endpointConfig, sessionId, options = {}) {
|
|
182
|
+
async getMessages(endpointConfig, sessionId, options = {}, authProvider) {
|
|
185
183
|
const config = this.getConfig(endpointConfig);
|
|
186
184
|
let url = buildEndpointUrl(config, config.endpoints.playground.getMessages, {
|
|
187
185
|
sessionId
|
|
@@ -203,7 +201,7 @@ export class PlaygroundService {
|
|
|
203
201
|
if (queryString) {
|
|
204
202
|
url = `${url}?${queryString}`;
|
|
205
203
|
}
|
|
206
|
-
return this.request(config, url);
|
|
204
|
+
return this.request(config, url, {}, authProvider);
|
|
207
205
|
}
|
|
208
206
|
/**
|
|
209
207
|
* Send a message to a playground session
|
|
@@ -213,7 +211,7 @@ export class PlaygroundService {
|
|
|
213
211
|
* @param inputs - Optional additional inputs for workflow nodes
|
|
214
212
|
* @returns The created message
|
|
215
213
|
*/
|
|
216
|
-
async sendMessage(endpointConfig, sessionId, content, inputs) {
|
|
214
|
+
async sendMessage(endpointConfig, sessionId, content, inputs, authProvider) {
|
|
217
215
|
const config = this.getConfig(endpointConfig);
|
|
218
216
|
const url = buildEndpointUrl(config, config.endpoints.playground.sendMessage, {
|
|
219
217
|
sessionId
|
|
@@ -225,7 +223,7 @@ export class PlaygroundService {
|
|
|
225
223
|
const response = await this.request(config, url, {
|
|
226
224
|
method: 'POST',
|
|
227
225
|
body: JSON.stringify(requestBody)
|
|
228
|
-
});
|
|
226
|
+
}, authProvider);
|
|
229
227
|
if (!response.data) {
|
|
230
228
|
throw new Error('Failed to send message: No data returned');
|
|
231
229
|
}
|
|
@@ -236,14 +234,14 @@ export class PlaygroundService {
|
|
|
236
234
|
*
|
|
237
235
|
* @param sessionId - The session UUID
|
|
238
236
|
*/
|
|
239
|
-
async stopExecution(endpointConfig, sessionId) {
|
|
237
|
+
async stopExecution(endpointConfig, sessionId, authProvider) {
|
|
240
238
|
const config = this.getConfig(endpointConfig);
|
|
241
239
|
const url = buildEndpointUrl(config, config.endpoints.playground.stopExecution, {
|
|
242
240
|
sessionId
|
|
243
241
|
});
|
|
244
242
|
await this.request(config, url, {
|
|
245
243
|
method: 'POST'
|
|
246
|
-
});
|
|
244
|
+
}, authProvider);
|
|
247
245
|
}
|
|
248
246
|
// =========================================================================
|
|
249
247
|
// Polling
|
|
@@ -257,7 +255,7 @@ export class PlaygroundService {
|
|
|
257
255
|
* @param shouldStopPolling - Optional override for stop conditions (default: defaultShouldStopPolling)
|
|
258
256
|
* @param initialSequenceNumber - Optional sequence number to seed polling from (avoids re-fetching already loaded messages)
|
|
259
257
|
*/
|
|
260
|
-
startPolling(endpointConfig, sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling, initialSequenceNumber) {
|
|
258
|
+
startPolling(endpointConfig, sessionId, callback, interval = DEFAULT_POLLING_INTERVAL, shouldStopPolling, initialSequenceNumber, authProvider) {
|
|
261
259
|
// Stop any existing polling
|
|
262
260
|
this.stopPolling();
|
|
263
261
|
this.pollingSessionId = sessionId;
|
|
@@ -271,7 +269,7 @@ export class PlaygroundService {
|
|
|
271
269
|
try {
|
|
272
270
|
const response = await this.getMessages(endpointConfig, sessionId, {
|
|
273
271
|
since: this.lastSequenceNumber ?? undefined
|
|
274
|
-
});
|
|
272
|
+
}, authProvider);
|
|
275
273
|
// Update last sequence number cursor
|
|
276
274
|
if (response.data && response.data.length > 0) {
|
|
277
275
|
const lastMessage = response.data[response.data.length - 1];
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Port Configuration API Service
|
|
3
3
|
* Handles fetching port configuration from the backend
|
|
4
4
|
*/
|
|
5
|
-
import { buildEndpointUrl
|
|
5
|
+
import { buildEndpointUrl } from '../config/endpoints.js';
|
|
6
|
+
import { authenticatedFetch } from '../utils/fetchWithAuth.js';
|
|
6
7
|
import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
|
|
7
8
|
import { logger } from '../utils/logger.js';
|
|
8
9
|
/**
|
|
@@ -11,11 +12,7 @@ import { logger } from '../utils/logger.js';
|
|
|
11
12
|
export async function fetchPortConfig(endpointConfig, authProvider) {
|
|
12
13
|
try {
|
|
13
14
|
const url = buildEndpointUrl(endpointConfig, endpointConfig.endpoints.portConfig);
|
|
14
|
-
const
|
|
15
|
-
const authHeaders = authProvider ? await authProvider.getAuthHeaders() : {};
|
|
16
|
-
const response = await fetch(url, {
|
|
17
|
-
headers: { ...configHeaders, ...authHeaders }
|
|
18
|
-
});
|
|
15
|
+
const response = await authenticatedFetch(url, {}, { config: endpointConfig, endpointKey: 'portConfig', authProvider });
|
|
19
16
|
if (!response.ok) {
|
|
20
17
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
21
18
|
}
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { FlowDropSettings, PartialSettings } from '../types/settings.js';
|
|
11
11
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
12
|
+
import type { AuthProvider } from '../types/auth.js';
|
|
12
13
|
/**
|
|
13
|
-
* Set the endpoint configuration for settings API
|
|
14
|
+
* Set the endpoint configuration (and optional auth provider) for settings API
|
|
15
|
+
* calls.
|
|
14
16
|
*
|
|
15
17
|
* @param config - Endpoint configuration
|
|
18
|
+
* @param provider - Optional auth provider supplying request auth headers
|
|
16
19
|
*/
|
|
17
|
-
export declare function setSettingsEndpointConfig(config: EndpointConfig): void;
|
|
20
|
+
export declare function setSettingsEndpointConfig(config: EndpointConfig, provider?: AuthProvider): void;
|
|
18
21
|
/**
|
|
19
22
|
* Get the current endpoint configuration
|
|
20
23
|
*
|
|
@@ -62,8 +65,9 @@ export declare class SettingsService {
|
|
|
62
65
|
* Create a new settings service instance
|
|
63
66
|
*
|
|
64
67
|
* @param config - Endpoint configuration
|
|
68
|
+
* @param authProvider - Optional auth provider applied to settings requests
|
|
65
69
|
*/
|
|
66
|
-
constructor(config: EndpointConfig);
|
|
70
|
+
constructor(config: EndpointConfig, authProvider?: AuthProvider);
|
|
67
71
|
/**
|
|
68
72
|
* Get user preferences from the backend
|
|
69
73
|
*
|
|
@@ -87,6 +91,7 @@ export declare class SettingsService {
|
|
|
87
91
|
* Create a settings service instance
|
|
88
92
|
*
|
|
89
93
|
* @param config - Endpoint configuration
|
|
94
|
+
* @param authProvider - Optional auth provider applied to settings requests
|
|
90
95
|
* @returns SettingsService instance
|
|
91
96
|
*/
|
|
92
|
-
export declare function createSettingsService(config: EndpointConfig): SettingsService;
|
|
97
|
+
export declare function createSettingsService(config: EndpointConfig, authProvider?: AuthProvider): SettingsService;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module services/settingsService
|
|
9
9
|
*/
|
|
10
|
-
import { buildEndpointUrl
|
|
10
|
+
import { buildEndpointUrl } from '../config/endpoints.js';
|
|
11
|
+
import { authenticatedFetch } from '../utils/fetchWithAuth.js';
|
|
11
12
|
// =========================================================================
|
|
12
13
|
// Configuration
|
|
13
14
|
// =========================================================================
|
|
@@ -16,12 +17,20 @@ import { buildEndpointUrl, getEndpointHeaders } from '../config/endpoints.js';
|
|
|
16
17
|
*/
|
|
17
18
|
let endpointConfig = null;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* Auth provider reference, applied to every settings request so the backend
|
|
21
|
+
* sync path authenticates consistently with the rest of the library.
|
|
22
|
+
*/
|
|
23
|
+
let authProvider;
|
|
24
|
+
/**
|
|
25
|
+
* Set the endpoint configuration (and optional auth provider) for settings API
|
|
26
|
+
* calls.
|
|
20
27
|
*
|
|
21
28
|
* @param config - Endpoint configuration
|
|
29
|
+
* @param provider - Optional auth provider supplying request auth headers
|
|
22
30
|
*/
|
|
23
|
-
export function setSettingsEndpointConfig(config) {
|
|
31
|
+
export function setSettingsEndpointConfig(config, provider) {
|
|
24
32
|
endpointConfig = config;
|
|
33
|
+
authProvider = provider;
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
36
|
* Get the current endpoint configuration
|
|
@@ -47,10 +56,10 @@ async function settingsRequest(endpointKey, endpointPath, options = {}) {
|
|
|
47
56
|
throw new Error('Endpoint configuration not set. Call setSettingsEndpointConfig() first.');
|
|
48
57
|
}
|
|
49
58
|
const url = buildEndpointUrl(endpointConfig, endpointPath);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
const response = await authenticatedFetch(url, options, {
|
|
60
|
+
config: endpointConfig,
|
|
61
|
+
endpointKey,
|
|
62
|
+
authProvider
|
|
54
63
|
});
|
|
55
64
|
// Check if response is JSON
|
|
56
65
|
const contentType = response.headers.get('content-type');
|
|
@@ -151,11 +160,12 @@ export class SettingsService {
|
|
|
151
160
|
* Create a new settings service instance
|
|
152
161
|
*
|
|
153
162
|
* @param config - Endpoint configuration
|
|
163
|
+
* @param authProvider - Optional auth provider applied to settings requests
|
|
154
164
|
*/
|
|
155
|
-
constructor(config) {
|
|
165
|
+
constructor(config, authProvider) {
|
|
156
166
|
this.config = config;
|
|
157
|
-
// Also set the module-level config
|
|
158
|
-
setSettingsEndpointConfig(config);
|
|
167
|
+
// Also set the module-level config + auth provider
|
|
168
|
+
setSettingsEndpointConfig(config, authProvider);
|
|
159
169
|
}
|
|
160
170
|
/**
|
|
161
171
|
* Get user preferences from the backend
|
|
@@ -189,8 +199,9 @@ export class SettingsService {
|
|
|
189
199
|
* Create a settings service instance
|
|
190
200
|
*
|
|
191
201
|
* @param config - Endpoint configuration
|
|
202
|
+
* @param authProvider - Optional auth provider applied to settings requests
|
|
192
203
|
* @returns SettingsService instance
|
|
193
204
|
*/
|
|
194
|
-
export function createSettingsService(config) {
|
|
195
|
-
return new SettingsService(config);
|
|
205
|
+
export function createSettingsService(config, authProvider) {
|
|
206
|
+
return new SettingsService(config, authProvider);
|
|
196
207
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FlowDropSkin } from '../types/skin';
|
|
2
|
+
/**
|
|
3
|
+
* Drafter — a fresh, modern "drafting workspace" skin for the whole editor
|
|
4
|
+
* environment (see ref-image.png): a fresh white shell around a mint/aqua
|
|
5
|
+
* canvas with soft cyan/teal technical grid lines, crisp engineering geometry
|
|
6
|
+
* and clean teal-ink text — not a neutral admin UI and not an all-cyan shell.
|
|
7
|
+
*
|
|
8
|
+
* Unlike a canvas-only skin, Drafter themes the entire shell so it reads as one
|
|
9
|
+
* designed environment:
|
|
10
|
+
* - App chrome (navbar, sidebars, inspector, status bars, form fields) stays
|
|
11
|
+
* white/near-white for a clean fresh product feel.
|
|
12
|
+
* - The mint/aqua personality lives in the canvas, node glass, focus rings,
|
|
13
|
+
* selected states and small technical accents.
|
|
14
|
+
* - Disabled/read-only surfaces use quiet light grey so state remains obvious.
|
|
15
|
+
* - Borders are mostly neutral hairlines; strong/focus borders + node outlines
|
|
16
|
+
* are teal/cyan ink (--fd-ring / --fd-border-strong / --fd-node-border).
|
|
17
|
+
* - Focus rings are cyan (--fd-ring), not the default blue.
|
|
18
|
+
* - Nodes are frosted glass with a faint inner highlight; the minimap + zoom
|
|
19
|
+
* controls share the same vocabulary.
|
|
20
|
+
* - Note/instruction cards read as cool annotations (translucent tint + slate
|
|
21
|
+
* ink border), never as a filled "success" card.
|
|
22
|
+
* - Emerald/green is reserved for primary actions, selection and live edges
|
|
23
|
+
* (--fd-primary) — it is not used for every outline.
|
|
24
|
+
*
|
|
25
|
+
* Data-type port colors and category icon colors are left untouched so they keep
|
|
26
|
+
* their full color against the draft surface. The square grid is rendered by
|
|
27
|
+
* WorkflowEditor via config.canvas.grid = 'lines'; --fd-grid-pattern-color
|
|
28
|
+
* colors it. Ships light + dark variants.
|
|
29
|
+
*/
|
|
30
|
+
export declare const drafterSkin: FlowDropSkin;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drafter — a fresh, modern "drafting workspace" skin for the whole editor
|
|
3
|
+
* environment (see ref-image.png): a fresh white shell around a mint/aqua
|
|
4
|
+
* canvas with soft cyan/teal technical grid lines, crisp engineering geometry
|
|
5
|
+
* and clean teal-ink text — not a neutral admin UI and not an all-cyan shell.
|
|
6
|
+
*
|
|
7
|
+
* Unlike a canvas-only skin, Drafter themes the entire shell so it reads as one
|
|
8
|
+
* designed environment:
|
|
9
|
+
* - App chrome (navbar, sidebars, inspector, status bars, form fields) stays
|
|
10
|
+
* white/near-white for a clean fresh product feel.
|
|
11
|
+
* - The mint/aqua personality lives in the canvas, node glass, focus rings,
|
|
12
|
+
* selected states and small technical accents.
|
|
13
|
+
* - Disabled/read-only surfaces use quiet light grey so state remains obvious.
|
|
14
|
+
* - Borders are mostly neutral hairlines; strong/focus borders + node outlines
|
|
15
|
+
* are teal/cyan ink (--fd-ring / --fd-border-strong / --fd-node-border).
|
|
16
|
+
* - Focus rings are cyan (--fd-ring), not the default blue.
|
|
17
|
+
* - Nodes are frosted glass with a faint inner highlight; the minimap + zoom
|
|
18
|
+
* controls share the same vocabulary.
|
|
19
|
+
* - Note/instruction cards read as cool annotations (translucent tint + slate
|
|
20
|
+
* ink border), never as a filled "success" card.
|
|
21
|
+
* - Emerald/green is reserved for primary actions, selection and live edges
|
|
22
|
+
* (--fd-primary) — it is not used for every outline.
|
|
23
|
+
*
|
|
24
|
+
* Data-type port colors and category icon colors are left untouched so they keep
|
|
25
|
+
* their full color against the draft surface. The square grid is rendered by
|
|
26
|
+
* WorkflowEditor via config.canvas.grid = 'lines'; --fd-grid-pattern-color
|
|
27
|
+
* colors it. Ships light + dark variants.
|
|
28
|
+
*/
|
|
29
|
+
export const drafterSkin = {
|
|
30
|
+
tokens: {
|
|
31
|
+
/* ----- Shell surfaces: fresh white chrome, not all-cyan ----- */
|
|
32
|
+
background: '#ffffff',
|
|
33
|
+
foreground: '#10201c',
|
|
34
|
+
muted: '#f4f6f7',
|
|
35
|
+
'muted-foreground': '#5f6f6b',
|
|
36
|
+
subtle: '#eef3f2',
|
|
37
|
+
card: '#ffffff',
|
|
38
|
+
'card-foreground': '#10201c',
|
|
39
|
+
header: '#ffffff',
|
|
40
|
+
'header-foreground': '#10201c',
|
|
41
|
+
'header-gradient': 'linear-gradient(180deg, #ffffff 0%, #fbfefd 100%)',
|
|
42
|
+
'surface-tint': 'rgba(20, 184, 166, 0.015)',
|
|
43
|
+
/* White sidebars/inspector with only a whisper of technical glass */
|
|
44
|
+
'panel-bg': 'rgba(255, 255, 255, 0.94)',
|
|
45
|
+
'panel-backdrop-filter': 'blur(10px) saturate(1.04)',
|
|
46
|
+
backdrop: 'rgba(255, 255, 255, 0.92)',
|
|
47
|
+
/* ----- Borders: neutral chrome; strong/focus = teal-cyan accent ----- */
|
|
48
|
+
border: '#e3e8e7',
|
|
49
|
+
'border-muted': '#eef2f2',
|
|
50
|
+
'border-strong': 'rgba(15, 118, 110, 0.42)',
|
|
51
|
+
ring: '#06b6d4',
|
|
52
|
+
/* ----- Canvas: fresh aqua-mint, soft cyan/teal square grid ----- */
|
|
53
|
+
'canvas-bg': '#fbfefd',
|
|
54
|
+
'grid-pattern-color': 'rgba(20, 184, 166, 0.08)',
|
|
55
|
+
/* ----- Nodes: frosted glass, faint inner highlight ----- */
|
|
56
|
+
'node-bg': 'rgba(247, 252, 250, 0.7)',
|
|
57
|
+
'node-header-bg': 'rgba(214, 245, 236, 0.6)',
|
|
58
|
+
'node-backdrop-filter': 'blur(10px) saturate(1.15)',
|
|
59
|
+
'node-radius': '2px',
|
|
60
|
+
'node-shadow': 'inset 0 1px 0 0 rgba(255, 255, 255, 0.6)',
|
|
61
|
+
'node-shadow-hover': 'inset 0 1px 0 0 rgba(255, 255, 255, 0.85)',
|
|
62
|
+
'node-border': '#0f766e',
|
|
63
|
+
'node-border-hover': '#0c5f59',
|
|
64
|
+
'node-border-width': '1.25px',
|
|
65
|
+
/* Header divider matches the node outline ink (tracks light/dark node-border) */
|
|
66
|
+
'node-header-divider-color': 'var(--fd-node-border)',
|
|
67
|
+
'handle-border': '#ffffff',
|
|
68
|
+
/* Notes read as cool annotations, not filled success cards */
|
|
69
|
+
'note-border': '#5b7891',
|
|
70
|
+
'note-border-hover': '#48617a',
|
|
71
|
+
/* Translucent status tints so note/instruction cards stay light + glassy */
|
|
72
|
+
/* Status hues tuned to the cool palette: danger = rose (teal's warm
|
|
73
|
+
complement, not a generic red), warning = golden amber. Success/info stay
|
|
74
|
+
on the emerald/cyan family so they read as part of the theme. */
|
|
75
|
+
success: '#0d9488',
|
|
76
|
+
'success-hover': '#0f766e',
|
|
77
|
+
info: '#0891b2',
|
|
78
|
+
'info-hover': '#0e7490',
|
|
79
|
+
warning: '#d97706',
|
|
80
|
+
'warning-hover': '#b45309',
|
|
81
|
+
error: '#e11d48',
|
|
82
|
+
'error-hover': '#be123c',
|
|
83
|
+
'info-muted': 'rgba(8, 145, 178, 0.1)',
|
|
84
|
+
'success-muted': 'rgba(13, 148, 136, 0.1)',
|
|
85
|
+
'warning-muted': 'rgba(217, 119, 6, 0.12)',
|
|
86
|
+
'error-muted': 'rgba(225, 29, 72, 0.1)',
|
|
87
|
+
/* ----- Crisp drafting geometry + flat chrome (2–6px radius, no soft shadow) ----- */
|
|
88
|
+
'radius-sm': '2px',
|
|
89
|
+
'radius-md': '3px',
|
|
90
|
+
'radius-lg': '4px',
|
|
91
|
+
'radius-xl': '6px',
|
|
92
|
+
'scrollbar-radius': '0',
|
|
93
|
+
'shadow-sm': 'none',
|
|
94
|
+
'shadow-md': 'none',
|
|
95
|
+
/* ----- Canvas overlays: light, instrument-like ----- */
|
|
96
|
+
'minimap-bg': 'rgba(255, 255, 255, 0.72)',
|
|
97
|
+
'minimap-mask-bg': 'rgba(15, 118, 110, 0.08)',
|
|
98
|
+
'minimap-mask-stroke': 'rgba(15, 118, 110, 0.35)',
|
|
99
|
+
'minimap-node-bg': 'rgba(15, 118, 110, 0.3)',
|
|
100
|
+
'minimap-node-stroke': 'rgba(15, 118, 110, 0.45)',
|
|
101
|
+
'controls-button-bg': 'rgba(255, 255, 255, 0.86)',
|
|
102
|
+
'controls-button-bg-hover': 'rgba(204, 251, 241, 0.9)',
|
|
103
|
+
'controls-button-color': '#0f766e',
|
|
104
|
+
'controls-button-color-hover': '#0c5f59',
|
|
105
|
+
'controls-button-border': 'rgba(15, 118, 110, 0.25)',
|
|
106
|
+
/* ----- Secondary = mint, accent = cyan, primary = the one allowed green ----- */
|
|
107
|
+
secondary: 'rgba(206, 230, 223, 0.55)',
|
|
108
|
+
'secondary-hover': 'rgba(186, 219, 210, 0.7)',
|
|
109
|
+
'secondary-foreground': '#0f3d36',
|
|
110
|
+
accent: '#06b6d4',
|
|
111
|
+
'accent-hover': '#0891b2',
|
|
112
|
+
'accent-foreground': '#ffffff',
|
|
113
|
+
'accent-muted': 'rgba(6, 182, 212, 0.12)',
|
|
114
|
+
primary: '#10b981',
|
|
115
|
+
'primary-hover': '#059669',
|
|
116
|
+
'primary-foreground': '#ffffff',
|
|
117
|
+
'primary-muted': 'rgba(16, 185, 129, 0.14)'
|
|
118
|
+
},
|
|
119
|
+
darkTokens: {
|
|
120
|
+
/* ----- Shell surfaces: deep teal-slate glass ----- */
|
|
121
|
+
background: '#0e2421',
|
|
122
|
+
foreground: '#d8f0ea',
|
|
123
|
+
muted: 'rgba(20, 60, 54, 0.6)',
|
|
124
|
+
'muted-foreground': '#8fb3aa',
|
|
125
|
+
subtle: 'rgba(13, 47, 44, 0.7)',
|
|
126
|
+
card: 'rgba(18, 42, 38, 0.96)',
|
|
127
|
+
'card-foreground': '#d8f0ea',
|
|
128
|
+
header: 'rgba(13, 40, 37, 0.7)',
|
|
129
|
+
'header-foreground': '#d8f0ea',
|
|
130
|
+
'header-gradient': 'linear-gradient(180deg, rgba(17, 42, 38, 0.9) 0%, rgba(12, 33, 30, 0.9) 100%)',
|
|
131
|
+
'surface-tint': 'rgba(45, 212, 191, 0.04)',
|
|
132
|
+
'panel-bg': 'rgba(14, 36, 33, 0.8)',
|
|
133
|
+
'panel-backdrop-filter': 'blur(14px) saturate(1.2)',
|
|
134
|
+
backdrop: 'rgba(11, 31, 28, 0.8)',
|
|
135
|
+
border: 'rgba(45, 212, 191, 0.22)',
|
|
136
|
+
'border-muted': 'rgba(45, 212, 191, 0.12)',
|
|
137
|
+
'border-strong': 'rgba(45, 212, 191, 0.4)',
|
|
138
|
+
ring: '#22d3ee',
|
|
139
|
+
'canvas-bg': '#0b1f1c',
|
|
140
|
+
'grid-pattern-color': 'rgba(45, 212, 191, 0.16)',
|
|
141
|
+
'node-bg': 'rgba(17, 38, 35, 0.62)',
|
|
142
|
+
'node-header-bg': 'rgba(13, 47, 44, 0.6)',
|
|
143
|
+
'node-backdrop-filter': 'blur(10px) saturate(1.2)',
|
|
144
|
+
'node-shadow': 'inset 0 1px 0 0 rgba(255, 255, 255, 0.08)',
|
|
145
|
+
'node-shadow-hover': 'inset 0 1px 0 0 rgba(255, 255, 255, 0.14)',
|
|
146
|
+
'node-border': '#0d9488',
|
|
147
|
+
'node-border-hover': '#2dd4bf',
|
|
148
|
+
'handle-border': '#0b1f1c',
|
|
149
|
+
'note-border': '#7d96b0',
|
|
150
|
+
'note-border-hover': '#9fb4cb',
|
|
151
|
+
success: '#2dd4bf',
|
|
152
|
+
'success-hover': '#5eead4',
|
|
153
|
+
info: '#22d3ee',
|
|
154
|
+
'info-hover': '#67e8f9',
|
|
155
|
+
warning: '#fbbf24',
|
|
156
|
+
'warning-hover': '#fcd34d',
|
|
157
|
+
error: '#fb7185',
|
|
158
|
+
'error-hover': '#fda4af',
|
|
159
|
+
'info-muted': 'rgba(34, 211, 238, 0.12)',
|
|
160
|
+
'success-muted': 'rgba(45, 212, 191, 0.12)',
|
|
161
|
+
'warning-muted': 'rgba(251, 191, 36, 0.14)',
|
|
162
|
+
'error-muted': 'rgba(251, 113, 133, 0.12)',
|
|
163
|
+
'minimap-bg': 'rgba(11, 31, 28, 0.7)',
|
|
164
|
+
'minimap-mask-bg': 'rgba(45, 212, 191, 0.06)',
|
|
165
|
+
'minimap-mask-stroke': 'rgba(45, 212, 191, 0.3)',
|
|
166
|
+
'minimap-node-bg': 'rgba(45, 212, 191, 0.28)',
|
|
167
|
+
'minimap-node-stroke': 'rgba(45, 212, 191, 0.45)',
|
|
168
|
+
'controls-button-bg': 'rgba(17, 38, 35, 0.8)',
|
|
169
|
+
'controls-button-bg-hover': 'rgba(13, 47, 44, 0.9)',
|
|
170
|
+
'controls-button-color': '#2dd4bf',
|
|
171
|
+
'controls-button-color-hover': '#5eead4',
|
|
172
|
+
'controls-button-border': 'rgba(45, 212, 191, 0.3)',
|
|
173
|
+
secondary: 'rgba(13, 47, 44, 0.8)',
|
|
174
|
+
'secondary-hover': 'rgba(20, 60, 54, 0.9)',
|
|
175
|
+
'secondary-foreground': '#d8f0ea',
|
|
176
|
+
accent: '#22d3ee',
|
|
177
|
+
'accent-hover': '#67e8f9',
|
|
178
|
+
'accent-foreground': '#03291c',
|
|
179
|
+
'accent-muted': 'rgba(34, 211, 238, 0.14)',
|
|
180
|
+
primary: '#34d399',
|
|
181
|
+
'primary-hover': '#6ee7b7',
|
|
182
|
+
'primary-foreground': '#03291c',
|
|
183
|
+
'primary-muted': 'rgba(52, 211, 153, 0.16)'
|
|
184
|
+
}
|
|
185
|
+
};
|
package/dist/skins/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FlowDropSkin, FlowDropSkinName } from '../types/skin';
|
|
2
2
|
import { defaultSkin } from './default';
|
|
3
3
|
import { slateSkin } from './slate';
|
|
4
|
+
import { drafterSkin } from './drafter';
|
|
4
5
|
/**
|
|
5
6
|
* Resolve a skin prop to a complete FlowDropSkin object.
|
|
6
7
|
*
|
|
@@ -10,4 +11,4 @@ import { slateSkin } from './slate';
|
|
|
10
11
|
* → minimal skin tokens + { primary: '#e11d48' }
|
|
11
12
|
*/
|
|
12
13
|
export declare function resolveSkin(skin: FlowDropSkin | FlowDropSkinName | undefined): FlowDropSkin;
|
|
13
|
-
export { defaultSkin, slateSkin };
|
|
14
|
+
export { defaultSkin, slateSkin, drafterSkin };
|
package/dist/skins/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { defaultSkin } from './default';
|
|
2
2
|
import { slateSkin } from './slate';
|
|
3
|
+
import { drafterSkin } from './drafter';
|
|
3
4
|
const builtinSkins = {
|
|
4
5
|
default: defaultSkin,
|
|
5
|
-
slate: slateSkin
|
|
6
|
+
slate: slateSkin,
|
|
7
|
+
drafter: drafterSkin
|
|
6
8
|
};
|
|
7
9
|
/**
|
|
8
10
|
* Resolve a skin prop to a complete FlowDropSkin object.
|
|
@@ -27,4 +29,4 @@ export function resolveSkin(skin) {
|
|
|
27
29
|
}
|
|
28
30
|
return skin;
|
|
29
31
|
}
|
|
30
|
-
export { defaultSkin, slateSkin };
|
|
32
|
+
export { defaultSkin, slateSkin, drafterSkin };
|
|
@@ -40,6 +40,17 @@ export declare class ApiContext {
|
|
|
40
40
|
* Discards any previously built client so the next access rebuilds it.
|
|
41
41
|
*/
|
|
42
42
|
configure(config: EndpointConfig, authProvider?: AuthProvider): void;
|
|
43
|
+
/**
|
|
44
|
+
* Swap the auth provider at runtime — e.g. on login or logout — without
|
|
45
|
+
* reconfiguring endpoints or remounting. Propagates to the live client so
|
|
46
|
+
* in-flight components keep working against the same instance.
|
|
47
|
+
*
|
|
48
|
+
* Per-instance services read `fd.api.authProvider` per request, so they pick
|
|
49
|
+
* up the new provider on their next call automatically.
|
|
50
|
+
*
|
|
51
|
+
* @param authProvider - The new authentication provider
|
|
52
|
+
*/
|
|
53
|
+
setAuthProvider(authProvider: AuthProvider): void;
|
|
43
54
|
/** Whether {@link configure} has been called with a usable config. */
|
|
44
55
|
isConfigured(): boolean;
|
|
45
56
|
}
|
|
@@ -58,6 +58,21 @@ export class ApiContext {
|
|
|
58
58
|
// Drop the cached client so it picks up the new config/auth on next access.
|
|
59
59
|
this.#client = null;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Swap the auth provider at runtime — e.g. on login or logout — without
|
|
63
|
+
* reconfiguring endpoints or remounting. Propagates to the live client so
|
|
64
|
+
* in-flight components keep working against the same instance.
|
|
65
|
+
*
|
|
66
|
+
* Per-instance services read `fd.api.authProvider` per request, so they pick
|
|
67
|
+
* up the new provider on their next call automatically.
|
|
68
|
+
*
|
|
69
|
+
* @param authProvider - The new authentication provider
|
|
70
|
+
*/
|
|
71
|
+
setAuthProvider(authProvider) {
|
|
72
|
+
this.#authProvider = authProvider;
|
|
73
|
+
// Keep the cached client (and its in-flight consumers) in sync.
|
|
74
|
+
this.#client?.setAuthProvider(authProvider);
|
|
75
|
+
}
|
|
61
76
|
/** Whether {@link configure} has been called with a usable config. */
|
|
62
77
|
isConfigured() {
|
|
63
78
|
return Boolean(this.#config);
|
|
@@ -28,7 +28,6 @@ export class CategoriesStore {
|
|
|
28
28
|
#categories = $state([...DEFAULT_CATEGORIES]);
|
|
29
29
|
/** Derived lookup map: category name → CategoryDefinition. */
|
|
30
30
|
#categoryMap = $derived((() => {
|
|
31
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- rebuilt whole inside $derived, never mutated afterwards
|
|
32
31
|
const map = new Map();
|
|
33
32
|
for (const cat of this.#categories) {
|
|
34
33
|
map.set(cat.name, cat);
|
|
@@ -109,7 +109,6 @@ export class PlaygroundStore {
|
|
|
109
109
|
// acknowledged optimistic write, overwritten by the next server response.
|
|
110
110
|
#isExecuting = $derived(this.#currentSession?.status === 'running');
|
|
111
111
|
/** Cleanups for active subscribeToSessionStatus effect roots. */
|
|
112
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- non-reactive bookkeeping registry; nothing renders from it
|
|
113
112
|
#statusSubscriptions = new Set();
|
|
114
113
|
/** Bound mutation facade — see {@link PlaygroundStoreActions}. */
|
|
115
114
|
actions;
|
|
@@ -354,7 +353,6 @@ export class PlaygroundStore {
|
|
|
354
353
|
if (!this.#currentSession)
|
|
355
354
|
return;
|
|
356
355
|
const executions = [...(this.#currentSession.executions ?? [])];
|
|
357
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- transient local for dedup within this call
|
|
358
356
|
const seenIds = new Set(executions.map((e) => e.id));
|
|
359
357
|
let added = false;
|
|
360
358
|
let gainedMainRun = false;
|