@flowdrop/flowdrop 1.6.0 → 1.8.0
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 +10 -0
- package/dist/components/App.svelte +153 -84
- package/dist/components/App.svelte.d.ts +16 -1
- package/dist/components/ConfigModal.svelte +2 -1
- package/dist/components/ConfigPanel.svelte +3 -2
- package/dist/components/FlowDropZone.svelte +2 -1
- package/dist/components/LogsSidebar.svelte +3 -2
- package/dist/components/Navbar.svelte +10 -6
- package/dist/components/NodeSidebar.svelte +4 -3
- package/dist/components/NodeStatusOverlay.svelte +14 -7
- package/dist/components/NodeSwapPicker.svelte +2 -1
- package/dist/components/PipelineStatus.svelte +10 -7
- package/dist/components/ReadOnlyDetails.svelte +4 -2
- package/dist/components/SchemaForm.svelte +20 -9
- package/dist/components/SchemaForm.svelte.d.ts +2 -4
- package/dist/components/SettingsModal.svelte +4 -3
- package/dist/components/SettingsPanel.svelte +3 -2
- package/dist/components/SwapMappingEditor.svelte +2 -1
- package/dist/components/WorkflowEditor.svelte +3 -2
- package/dist/components/chat/AIChatPanel.svelte +22 -7
- package/dist/components/chat/AIChatPanel.svelte.d.ts +3 -0
- package/dist/components/chat/CommandPreview.svelte +10 -6
- package/dist/components/console/CommandConsole.svelte +4 -3
- package/dist/components/form/FormArray.svelte +33 -20
- package/dist/components/form/FormArray.svelte.d.ts +3 -1
- package/dist/components/form/FormAutocomplete.svelte +18 -7
- package/dist/components/form/FormCodeEditor.svelte +6 -4
- package/dist/components/form/FormFieldWrapper.svelte +2 -1
- package/dist/components/form/FormMarkdownEditor.svelte +152 -108
- package/dist/components/form/FormMarkdownEditor.svelte.d.ts +1 -1
- package/dist/components/form/FormTemplateEditor.svelte +2 -1
- package/dist/components/form/FormToggle.svelte +23 -5
- package/dist/components/form/FormToggle.svelte.d.ts +6 -2
- package/dist/components/interrupt/ChoicePrompt.svelte +14 -5
- package/dist/components/interrupt/ConfirmationPrompt.svelte +8 -5
- package/dist/components/interrupt/FormPrompt.svelte +28 -7
- package/dist/components/interrupt/InterruptBubble.svelte +27 -18
- package/dist/components/interrupt/ReviewPrompt.svelte +32 -22
- package/dist/components/interrupt/TextInputPrompt.svelte +12 -5
- package/dist/components/layouts/MainLayout.svelte +4 -3
- package/dist/components/nodes/GatewayNode.svelte +8 -3
- package/dist/components/nodes/IdeaNode.svelte +2 -1
- package/dist/components/nodes/NotesNode.svelte +18 -12
- package/dist/components/nodes/SimpleNode.svelte +8 -8
- package/dist/components/nodes/WorkflowNode.svelte +8 -3
- package/dist/components/playground/ChatPanel.svelte +36 -24
- package/dist/components/playground/MessageBubble.svelte +15 -7
- package/dist/components/playground/Playground.svelte +2 -1
- package/dist/components/playground/PlaygroundModal.svelte +2 -1
- package/dist/components/playground/SessionManager.svelte +14 -10
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +9 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/messages/context.d.ts +29 -0
- package/dist/messages/context.js +38 -0
- package/dist/messages/defaults.d.ts +396 -0
- package/dist/messages/defaults.js +356 -0
- package/dist/messages/deprecation.d.ts +20 -0
- package/dist/messages/deprecation.js +33 -0
- package/dist/messages/index.d.ts +11 -0
- package/dist/messages/index.js +10 -0
- package/dist/messages/merge.d.ts +28 -0
- package/dist/messages/merge.js +53 -0
- package/dist/messages/types.d.ts +29 -0
- package/dist/messages/types.js +13 -0
- package/dist/schemas/v1/workflow.schema.json +5 -0
- package/dist/services/draftStorage.d.ts +13 -0
- package/dist/services/draftStorage.js +36 -0
- package/dist/stores/workflowStore.svelte.d.ts +1 -0
- package/dist/stores/workflowStore.svelte.js +1 -0
- package/dist/styles/base.css +13 -4
- package/dist/svelte-app.d.ts +11 -0
- package/dist/svelte-app.js +11 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/connections.d.ts +4 -0
- package/dist/utils/connections.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,6 +131,16 @@ const app = await mountFlowDropApp(container, {
|
|
|
131
131
|
});
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
## Customising messages
|
|
135
|
+
|
|
136
|
+
Every user-facing string flows through a typed `Messages` tree. Pass a callback to override any subset:
|
|
137
|
+
|
|
138
|
+
```svelte
|
|
139
|
+
<FlowDrop messages={() => ({ form: { schema: { save: 'Apply' } } })} />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Wire the callback to your i18n library (paraglide-js, sveltekit-i18n, etc.) — locale changes propagate automatically. See the [i18n & Custom Messages guide](https://docs.flowdrop.io/guides/i18n) for the full shape and a paraglide-js worked example.
|
|
143
|
+
|
|
134
144
|
## Sub-Module Exports
|
|
135
145
|
|
|
136
146
|
FlowDrop provides tree-shakeable sub-module exports so you can import only what you need:
|
|
@@ -57,13 +57,16 @@
|
|
|
57
57
|
import { getUiSettings, updateSettings } from '../stores/settingsStore.svelte.js';
|
|
58
58
|
import {
|
|
59
59
|
initializePortCompatibility,
|
|
60
|
-
getPortCompatibilityChecker
|
|
60
|
+
getPortCompatibilityChecker,
|
|
61
|
+
isPortCompatibilityInitialized
|
|
61
62
|
} from '../utils/connections.js';
|
|
62
63
|
import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
|
|
63
64
|
import { workflowFormatRegistry } from '../registry/workflowFormatRegistry.js';
|
|
64
65
|
import { logger } from '../utils/logger.js';
|
|
65
66
|
import { validateWorkflowData } from '../utils/validation.js';
|
|
66
67
|
import type { SettingsCategory } from '../types/settings.js';
|
|
68
|
+
import { defaultMessages, mergeMessages, setMessages } from '../messages/index.js';
|
|
69
|
+
import type { MessagesOverride } from '../messages/index.js';
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
72
|
* Configuration props for runtime customization
|
|
@@ -121,6 +124,20 @@
|
|
|
121
124
|
showSettingsResetButton?: boolean;
|
|
122
125
|
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
123
126
|
swapStrategies?: SwapStrategy[];
|
|
127
|
+
/** Additional JSON Schema properties to show in the Workflow Settings panel. Values are persisted in workflow.config. */
|
|
128
|
+
workflowSettingsSchema?: ConfigSchema;
|
|
129
|
+
/**
|
|
130
|
+
* Override user-facing strings. Pass either a partial of the `Messages`
|
|
131
|
+
* tree directly, or a callback that returns one. Missing keys fall through
|
|
132
|
+
* to English defaults.
|
|
133
|
+
*
|
|
134
|
+
* For static overrides, a value is fine: `messages={{ common: { save: 'Apply' } }}`.
|
|
135
|
+
* For reactive overrides driven by an i18n library (paraglide, etc.),
|
|
136
|
+
* either form works — Svelte 5's prop reactivity propagates locale changes.
|
|
137
|
+
* The callback form is useful when your translations live behind a
|
|
138
|
+
* function call you'd rather not invoke unless the prop is actually read.
|
|
139
|
+
*/
|
|
140
|
+
messages?: MessagesOverride | (() => MessagesOverride);
|
|
124
141
|
}
|
|
125
142
|
|
|
126
143
|
let {
|
|
@@ -146,12 +163,75 @@
|
|
|
146
163
|
settingsCategories,
|
|
147
164
|
showSettingsSyncButton,
|
|
148
165
|
showSettingsResetButton,
|
|
149
|
-
swapStrategies
|
|
166
|
+
swapStrategies,
|
|
167
|
+
workflowSettingsSchema,
|
|
168
|
+
messages: messagesOverride
|
|
150
169
|
}: Props = $props();
|
|
151
170
|
|
|
152
171
|
// svelte-ignore state_referenced_locally — feature flags don't change at runtime
|
|
153
172
|
const features = mergeFeatures(propFeatures);
|
|
154
173
|
|
|
174
|
+
// Messages: merge consumer overrides over defaults; expose via context as a
|
|
175
|
+
// getter so consumer-side reactivity (e.g. paraglide-js locale switches)
|
|
176
|
+
// propagates into every child without a subscription. Accepts either a
|
|
177
|
+
// value or a callback — normalize here so the rest of the component sees
|
|
178
|
+
// the merged tree directly.
|
|
179
|
+
let mergedMessages = $derived(
|
|
180
|
+
mergeMessages(
|
|
181
|
+
defaultMessages,
|
|
182
|
+
typeof messagesOverride === 'function' ? messagesOverride() : messagesOverride
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
// setContext must run during component init (synchronously, not in $effect)
|
|
186
|
+
// — Svelte enforces that. The context value is a getter that closes over
|
|
187
|
+
// the live $derived, so child components always read the current tree.
|
|
188
|
+
setMessages(() => mergedMessages);
|
|
189
|
+
|
|
190
|
+
// Default navbar primary actions — used when no `navbarActions` prop is supplied.
|
|
191
|
+
// Derived so the labels track locale changes.
|
|
192
|
+
const defaultPrimaryActions = $derived([
|
|
193
|
+
{
|
|
194
|
+
label: mergedMessages.navigation.save,
|
|
195
|
+
href: '#save',
|
|
196
|
+
icon: 'heroicons:document-arrow-down',
|
|
197
|
+
variant: 'primary' as const,
|
|
198
|
+
onclick: (e: Event) => {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
saveWorkflow();
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
label: mergedMessages.navigation.export,
|
|
205
|
+
href: '#export',
|
|
206
|
+
icon: 'heroicons:arrow-down-tray',
|
|
207
|
+
variant: 'outline' as const,
|
|
208
|
+
onclick: (e: Event) => {
|
|
209
|
+
e.preventDefault();
|
|
210
|
+
exportWorkflow();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
label: mergedMessages.navigation.import,
|
|
215
|
+
href: '#import',
|
|
216
|
+
icon: 'heroicons:arrow-up-tray',
|
|
217
|
+
variant: 'outline' as const,
|
|
218
|
+
onclick: (e: Event) => {
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
fileInputRef?.click();
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: mergedMessages.navigation.workflowSettings,
|
|
225
|
+
href: '#settings',
|
|
226
|
+
icon: 'heroicons:cog-6-tooth',
|
|
227
|
+
variant: 'outline' as const,
|
|
228
|
+
onclick: (e: Event) => {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
toggleWorkflowSettings();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
|
|
155
235
|
// Theme system — resolve named theme or custom object, inject CSS tokens from skin
|
|
156
236
|
// Explicit prop wins; falls back to user's persisted theme preference from settings
|
|
157
237
|
let resolvedTheme = $derived(resolveTheme(themeProp ?? getUiSettings().theme));
|
|
@@ -223,39 +303,60 @@
|
|
|
223
303
|
let swapTargetMetadata = $state<NodeMetadata | null>(null);
|
|
224
304
|
let swapInteractiveState = $state<InteractiveSwapState | null>(null);
|
|
225
305
|
|
|
306
|
+
// Built-in workflow settings field names — consumer schemas must not reuse these.
|
|
307
|
+
const WORKFLOW_SETTINGS_RESERVED = new Set(['name', 'description', 'format']);
|
|
308
|
+
|
|
226
309
|
// Workflow configuration schema (derived to pick up dynamic format options)
|
|
227
|
-
let workflowConfigSchema: ConfigSchema = $derived({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
310
|
+
let workflowConfigSchema: ConfigSchema = $derived.by(() => {
|
|
311
|
+
const extraProps = Object.fromEntries(
|
|
312
|
+
Object.entries(workflowSettingsSchema?.properties ?? {}).filter(([k]) => {
|
|
313
|
+
if (WORKFLOW_SETTINGS_RESERVED.has(k)) {
|
|
314
|
+
logger.warn(
|
|
315
|
+
`workflowSettingsSchema: property "${k}" is reserved and will be ignored. Choose a different key.`
|
|
316
|
+
);
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
const extraRequired = (workflowSettingsSchema?.required ?? []).filter(
|
|
323
|
+
(k) => !WORKFLOW_SETTINGS_RESERVED.has(k)
|
|
324
|
+
);
|
|
325
|
+
return {
|
|
326
|
+
type: 'object' as const,
|
|
327
|
+
properties: {
|
|
328
|
+
name: {
|
|
329
|
+
type: 'string',
|
|
330
|
+
title: 'Workflow Name',
|
|
331
|
+
description: 'The name of the workflow',
|
|
332
|
+
default: ''
|
|
333
|
+
},
|
|
334
|
+
description: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
title: 'Description',
|
|
337
|
+
description: 'A description of the workflow',
|
|
338
|
+
format: 'multiline',
|
|
339
|
+
default: ''
|
|
340
|
+
},
|
|
341
|
+
format: {
|
|
342
|
+
type: 'string',
|
|
343
|
+
title: 'Workflow Format',
|
|
344
|
+
description: 'The specification format for this workflow',
|
|
345
|
+
oneOf: workflowFormatRegistry.getOneOfOptions(),
|
|
346
|
+
default: 'flowdrop'
|
|
347
|
+
},
|
|
348
|
+
...extraProps
|
|
242
349
|
},
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
title: 'Workflow Format',
|
|
246
|
-
description: 'The specification format for this workflow',
|
|
247
|
-
oneOf: workflowFormatRegistry.getOneOfOptions(),
|
|
248
|
-
default: 'flowdrop'
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
required: ['name']
|
|
350
|
+
required: ['name', ...extraRequired]
|
|
351
|
+
};
|
|
252
352
|
});
|
|
253
353
|
|
|
254
354
|
// Workflow configuration values
|
|
255
355
|
let workflowConfigValues = $derived({
|
|
256
356
|
name: getWorkflowName() || '',
|
|
257
357
|
description: getWorkflowStore()?.description || '',
|
|
258
|
-
format: getWorkflowStore()?.metadata?.format || 'flowdrop'
|
|
358
|
+
format: getWorkflowStore()?.metadata?.format || 'flowdrop',
|
|
359
|
+
...(getWorkflowStore()?.config ?? {})
|
|
259
360
|
});
|
|
260
361
|
|
|
261
362
|
// Get the current node from the workflow store
|
|
@@ -727,7 +828,10 @@
|
|
|
727
828
|
|
|
728
829
|
// Ensure port compatibility checker is initialized (needed for proximity connect, etc.)
|
|
729
830
|
// mountFlowDropApp initializes this before mounting, but SvelteKit routes need it here.
|
|
730
|
-
|
|
831
|
+
// Only initialize with defaults if not already set — preserves custom port configs.
|
|
832
|
+
if (!isPortCompatibilityInitialized()) {
|
|
833
|
+
initializePortCompatibility(DEFAULT_PORT_CONFIG);
|
|
834
|
+
}
|
|
731
835
|
|
|
732
836
|
await fetchNodeTypes();
|
|
733
837
|
|
|
@@ -922,50 +1026,7 @@
|
|
|
922
1026
|
{#snippet header()}
|
|
923
1027
|
<Navbar
|
|
924
1028
|
title={breadcrumbTitle}
|
|
925
|
-
primaryActions={navbarActions.length > 0
|
|
926
|
-
? navbarActions
|
|
927
|
-
: [
|
|
928
|
-
{
|
|
929
|
-
label: 'Save',
|
|
930
|
-
href: '#save',
|
|
931
|
-
icon: 'heroicons:document-arrow-down',
|
|
932
|
-
variant: 'primary',
|
|
933
|
-
onclick: (e) => {
|
|
934
|
-
e.preventDefault();
|
|
935
|
-
saveWorkflow();
|
|
936
|
-
}
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
label: 'Export',
|
|
940
|
-
href: '#export',
|
|
941
|
-
icon: 'heroicons:arrow-down-tray',
|
|
942
|
-
variant: 'outline',
|
|
943
|
-
onclick: (e) => {
|
|
944
|
-
e.preventDefault();
|
|
945
|
-
exportWorkflow();
|
|
946
|
-
}
|
|
947
|
-
},
|
|
948
|
-
{
|
|
949
|
-
label: 'Import',
|
|
950
|
-
href: '#import',
|
|
951
|
-
icon: 'heroicons:arrow-up-tray',
|
|
952
|
-
variant: 'outline',
|
|
953
|
-
onclick: (e) => {
|
|
954
|
-
e.preventDefault();
|
|
955
|
-
fileInputRef?.click();
|
|
956
|
-
}
|
|
957
|
-
},
|
|
958
|
-
{
|
|
959
|
-
label: 'Workflow Settings',
|
|
960
|
-
href: '#settings',
|
|
961
|
-
icon: 'heroicons:cog-6-tooth',
|
|
962
|
-
variant: 'outline',
|
|
963
|
-
onclick: (e) => {
|
|
964
|
-
e.preventDefault();
|
|
965
|
-
toggleWorkflowSettings();
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
]}
|
|
1029
|
+
primaryActions={navbarActions.length > 0 ? navbarActions : defaultPrimaryActions}
|
|
969
1030
|
showStatus={true}
|
|
970
1031
|
{showSettings}
|
|
971
1032
|
{settingsCategories}
|
|
@@ -1014,7 +1075,7 @@
|
|
|
1014
1075
|
/>
|
|
1015
1076
|
{:else if isWorkflowSettingsOpen}
|
|
1016
1077
|
<ConfigPanel
|
|
1017
|
-
title=
|
|
1078
|
+
title={mergedMessages.navigation.workflowSettingsPanelTitle}
|
|
1018
1079
|
id={getWorkflowStore()?.id}
|
|
1019
1080
|
details={[
|
|
1020
1081
|
{
|
|
@@ -1026,7 +1087,7 @@
|
|
|
1026
1087
|
value: String(getWorkflowStore()?.edges?.length ?? 0)
|
|
1027
1088
|
}
|
|
1028
1089
|
]}
|
|
1029
|
-
configTitle=
|
|
1090
|
+
configTitle={mergedMessages.navigation.workflowSettingsPanelSubtitle}
|
|
1030
1091
|
onClose={() => (isWorkflowSettingsOpen = false)}
|
|
1031
1092
|
>
|
|
1032
1093
|
<ConfigForm
|
|
@@ -1055,13 +1116,16 @@
|
|
|
1055
1116
|
}
|
|
1056
1117
|
}
|
|
1057
1118
|
|
|
1119
|
+
// Extract built-in fields; everything else belongs in workflow.config
|
|
1120
|
+
const { name, description, format: _format, ...customConfig } = config;
|
|
1058
1121
|
workflowActions.batchUpdate({
|
|
1059
|
-
name:
|
|
1060
|
-
description:
|
|
1122
|
+
name: name as string,
|
|
1123
|
+
description: description as string | undefined,
|
|
1061
1124
|
metadata: {
|
|
1062
1125
|
...wf.metadata,
|
|
1063
1126
|
format: newFormat
|
|
1064
|
-
}
|
|
1127
|
+
},
|
|
1128
|
+
...(workflowSettingsSchema && { config: customConfig as Record<string, unknown> })
|
|
1065
1129
|
});
|
|
1066
1130
|
}
|
|
1067
1131
|
}}
|
|
@@ -1072,7 +1136,8 @@
|
|
|
1072
1136
|
<ConfigPanel
|
|
1073
1137
|
title={currentNode.data.label}
|
|
1074
1138
|
id={currentNode.id}
|
|
1075
|
-
description={currentNode.data.metadata?.description ||
|
|
1139
|
+
description={currentNode.data.metadata?.description ||
|
|
1140
|
+
mergedMessages.navigation.nodeConfigDescription}
|
|
1076
1141
|
details={[
|
|
1077
1142
|
{
|
|
1078
1143
|
label: 'Type',
|
|
@@ -1129,7 +1194,7 @@
|
|
|
1129
1194
|
{/if}
|
|
1130
1195
|
{/snippet}
|
|
1131
1196
|
|
|
1132
|
-
<!-- Bottom Panel: Tabbed Console / AI
|
|
1197
|
+
<!-- Bottom Panel: Tabbed Console / AI Assistant -->
|
|
1133
1198
|
{#snippet bottomPanel()}
|
|
1134
1199
|
<div class="bottom-panel-tabs">
|
|
1135
1200
|
<div class="bottom-panel-tabs__bar">
|
|
@@ -1139,7 +1204,7 @@
|
|
|
1139
1204
|
: ''}"
|
|
1140
1205
|
onclick={() => updateSettings({ ui: { bottomPanelTab: 'console' } })}
|
|
1141
1206
|
>
|
|
1142
|
-
|
|
1207
|
+
{mergedMessages.navigation.bottomPanel.console}
|
|
1143
1208
|
</button>
|
|
1144
1209
|
<button
|
|
1145
1210
|
class="bottom-panel-tabs__tab {getUiSettings().bottomPanelTab === 'chat'
|
|
@@ -1147,7 +1212,7 @@
|
|
|
1147
1212
|
: ''}"
|
|
1148
1213
|
onclick={() => updateSettings({ ui: { bottomPanelTab: 'chat' } })}
|
|
1149
1214
|
>
|
|
1150
|
-
|
|
1215
|
+
{mergedMessages.navigation.bottomPanel.chat}
|
|
1151
1216
|
</button>
|
|
1152
1217
|
</div>
|
|
1153
1218
|
<div class="bottom-panel-tabs__content">
|
|
@@ -1232,15 +1297,19 @@
|
|
|
1232
1297
|
onclick={handleCanvasClick}
|
|
1233
1298
|
onkeydown={(e) => e.key === 'Escape' && closeConfigSidebar()}
|
|
1234
1299
|
role="region"
|
|
1235
|
-
aria-label=
|
|
1300
|
+
aria-label={mergedMessages.layout.workflowCanvas}
|
|
1236
1301
|
>
|
|
1237
1302
|
<!-- Floating sidebar toggle — always visible on the canvas top-left -->
|
|
1238
1303
|
{#if !disableSidebar}
|
|
1239
1304
|
<button
|
|
1240
1305
|
class="flowdrop-sidebar-fab"
|
|
1241
1306
|
onclick={toggleSidebar}
|
|
1242
|
-
aria-label={isSidebarCollapsed
|
|
1243
|
-
|
|
1307
|
+
aria-label={isSidebarCollapsed
|
|
1308
|
+
? mergedMessages.layout.expandSidebar
|
|
1309
|
+
: mergedMessages.layout.collapseSidebar}
|
|
1310
|
+
title={isSidebarCollapsed
|
|
1311
|
+
? mergedMessages.layout.expandSidebar
|
|
1312
|
+
: mergedMessages.layout.collapseSidebar}
|
|
1244
1313
|
>
|
|
1245
1314
|
<Icon icon={isSidebarCollapsed ? 'mdi:menu' : 'mdi:menu-open'} />
|
|
1246
1315
|
</button>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { NodeMetadata, Workflow } from '../types/index.js';
|
|
1
|
+
import type { NodeMetadata, Workflow, ConfigSchema } from '../types/index.js';
|
|
2
2
|
import type { SwapStrategy } from '../utils/nodeSwap.js';
|
|
3
3
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
4
4
|
import type { AuthProvider } from '../types/auth.js';
|
|
5
5
|
import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
|
|
6
6
|
import type { FlowDropTheme, FlowDropThemeName } from '../types/theme.js';
|
|
7
7
|
import type { SettingsCategory } from '../types/settings.js';
|
|
8
|
+
import type { MessagesOverride } from '../messages/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Configuration props for runtime customization
|
|
10
11
|
*/
|
|
@@ -61,6 +62,20 @@ interface Props {
|
|
|
61
62
|
showSettingsResetButton?: boolean;
|
|
62
63
|
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
63
64
|
swapStrategies?: SwapStrategy[];
|
|
65
|
+
/** Additional JSON Schema properties to show in the Workflow Settings panel. Values are persisted in workflow.config. */
|
|
66
|
+
workflowSettingsSchema?: ConfigSchema;
|
|
67
|
+
/**
|
|
68
|
+
* Override user-facing strings. Pass either a partial of the `Messages`
|
|
69
|
+
* tree directly, or a callback that returns one. Missing keys fall through
|
|
70
|
+
* to English defaults.
|
|
71
|
+
*
|
|
72
|
+
* For static overrides, a value is fine: `messages={{ common: { save: 'Apply' } }}`.
|
|
73
|
+
* For reactive overrides driven by an i18n library (paraglide, etc.),
|
|
74
|
+
* either form works — Svelte 5's prop reactivity propagates locale changes.
|
|
75
|
+
* The callback form is useful when your translations live behind a
|
|
76
|
+
* function call you'd rather not invoke unless the prop is actually read.
|
|
77
|
+
*/
|
|
78
|
+
messages?: MessagesOverride | (() => MessagesOverride);
|
|
64
79
|
}
|
|
65
80
|
declare const App: import("svelte").Component<Props, {}, "">;
|
|
66
81
|
type App = ReturnType<typeof App>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ConfigSchema, ConfigValues } from '../types';
|
|
3
3
|
import ConfigForm from './ConfigForm.svelte';
|
|
4
|
+
import { m } from '../messages/index.js';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
isOpen: boolean;
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
type="button"
|
|
61
62
|
class="config-modal__close-btn"
|
|
62
63
|
onclick={handleClose}
|
|
63
|
-
aria-label=
|
|
64
|
+
aria-label={m().navigation.closeConfigModal}
|
|
64
65
|
>
|
|
65
66
|
<span aria-hidden="true">×</span>
|
|
66
67
|
</button>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import Icon from '@iconify/svelte';
|
|
12
12
|
import ReadOnlyDetails from './ReadOnlyDetails.svelte';
|
|
13
13
|
import { getUiSettings } from '../stores/settingsStore.svelte.js';
|
|
14
|
+
import { m } from '../messages/index.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* A single detail item with label and value
|
|
@@ -70,13 +71,13 @@
|
|
|
70
71
|
<button
|
|
71
72
|
class="config-panel__action-btn"
|
|
72
73
|
onclick={onSwap}
|
|
73
|
-
aria-label=
|
|
74
|
+
aria-label={m().layout.swapNode}
|
|
74
75
|
title="Swap node type"
|
|
75
76
|
>
|
|
76
77
|
<Icon icon="heroicons:arrows-right-left" />
|
|
77
78
|
</button>
|
|
78
79
|
{/if}
|
|
79
|
-
<button class="config-panel__close" onclick={onClose} aria-label=
|
|
80
|
+
<button class="config-panel__close" onclick={onClose} aria-label={m().layout.closeConfigPanel}> × </button>
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
82
83
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import { useSvelteFlow } from '@xyflow/svelte';
|
|
9
9
|
import type { Snippet } from 'svelte';
|
|
10
|
+
import { m } from '../messages/index.js';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
ondrop: (nodeTypeData: string, position: { x: number; y: number }) => void;
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
<div
|
|
69
70
|
class="flow-drop-zone"
|
|
70
71
|
role="application"
|
|
71
|
-
aria-label=
|
|
72
|
+
aria-label={m().layout.workflowCanvas}
|
|
72
73
|
ondragover={handleDragOver}
|
|
73
74
|
ondrop={handleDrop}
|
|
74
75
|
>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import Icon from '@iconify/svelte';
|
|
10
10
|
import type { WorkflowNode as WorkflowNodeType } from '../types/index.js';
|
|
11
|
+
import { m } from '../messages/index.js';
|
|
11
12
|
|
|
12
13
|
interface LogEntry {
|
|
13
14
|
timestamp: string;
|
|
@@ -163,7 +164,7 @@
|
|
|
163
164
|
class="logs-sidebar"
|
|
164
165
|
class:logs-sidebar--open={props.isOpen}
|
|
165
166
|
role="dialog"
|
|
166
|
-
aria-label=
|
|
167
|
+
aria-label={m().layout.executionLogs}
|
|
167
168
|
aria-modal="true"
|
|
168
169
|
tabindex="-1"
|
|
169
170
|
onkeydown={handleKeydown}
|
|
@@ -189,7 +190,7 @@
|
|
|
189
190
|
class="logs-sidebar__close-btn"
|
|
190
191
|
onclick={handleClose}
|
|
191
192
|
title="Close logs sidebar (Esc)"
|
|
192
|
-
aria-label=
|
|
193
|
+
aria-label={m().layout.closeLogsSidebar}
|
|
193
194
|
>
|
|
194
195
|
<Icon icon="mdi:close" />
|
|
195
196
|
</button>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import Logo from './Logo.svelte';
|
|
12
12
|
import SettingsModal from './SettingsModal.svelte';
|
|
13
13
|
import type { SettingsCategory } from '../types/settings.js';
|
|
14
|
+
import { m } from '../messages/index.js';
|
|
14
15
|
|
|
15
16
|
interface NavbarAction {
|
|
16
17
|
label: string;
|
|
@@ -64,6 +65,9 @@
|
|
|
64
65
|
// Settings modal state
|
|
65
66
|
let isSettingsOpen = $state(false);
|
|
66
67
|
|
|
68
|
+
// Hoist the navigation branch — six reads in the template.
|
|
69
|
+
const nav = $derived(m().navigation);
|
|
70
|
+
|
|
67
71
|
// Close dropdown when clicking outside
|
|
68
72
|
function handleClickOutside(event: MouseEvent) {
|
|
69
73
|
const target = event.target as HTMLElement;
|
|
@@ -90,8 +94,8 @@
|
|
|
90
94
|
<Logo />
|
|
91
95
|
</div>
|
|
92
96
|
<div>
|
|
93
|
-
<h1 class="flowdrop-text--logo flowdrop-font--bold">
|
|
94
|
-
<p class="flowdrop-text--tagline flowdrop-text--gray">
|
|
97
|
+
<h1 class="flowdrop-text--logo flowdrop-font--bold">{nav.appName}</h1>
|
|
98
|
+
<p class="flowdrop-text--tagline flowdrop-text--gray">{nav.tagline}</p>
|
|
95
99
|
</div>
|
|
96
100
|
</div>
|
|
97
101
|
</div>
|
|
@@ -104,7 +108,7 @@
|
|
|
104
108
|
<div class="flowdrop-navbar__status-container">
|
|
105
109
|
<div class="flowdrop-navbar__status">
|
|
106
110
|
<div class="flowdrop-navbar__status-indicator"></div>
|
|
107
|
-
<span class="flowdrop-navbar__status-text">
|
|
111
|
+
<span class="flowdrop-navbar__status-text">{nav.connected}</span>
|
|
108
112
|
</div>
|
|
109
113
|
</div>
|
|
110
114
|
{/if}
|
|
@@ -112,7 +116,7 @@
|
|
|
112
116
|
<!-- Title or Breadcrumbs on bottom -->
|
|
113
117
|
{#if breadcrumbs.length > 0}
|
|
114
118
|
<div class="flowdrop-navbar__breadcrumb-container">
|
|
115
|
-
<nav class="flowdrop-navbar__breadcrumb" aria-label=
|
|
119
|
+
<nav class="flowdrop-navbar__breadcrumb" aria-label={nav.breadcrumbAriaLabel}>
|
|
116
120
|
<ol class="flowdrop-navbar__breadcrumb-list">
|
|
117
121
|
{#each breadcrumbs as breadcrumb, index (index)}
|
|
118
122
|
<li class="flowdrop-navbar__breadcrumb-item">
|
|
@@ -241,8 +245,8 @@
|
|
|
241
245
|
<button
|
|
242
246
|
class="flowdrop-navbar__settings-btn"
|
|
243
247
|
onclick={() => (isSettingsOpen = true)}
|
|
244
|
-
title=
|
|
245
|
-
aria-label=
|
|
248
|
+
title={nav.settingsTitle}
|
|
249
|
+
aria-label={nav.settingsAriaLabel}
|
|
246
250
|
>
|
|
247
251
|
<Icon icon="mdi:cog" />
|
|
248
252
|
</button>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { getCategoryLabel } from '../stores/categoriesStore.svelte.js';
|
|
14
14
|
import { getUiSettings } from '../stores/settingsStore.svelte.js';
|
|
15
15
|
import { extractConfigDefaults } from '../utils/nodeIds.js';
|
|
16
|
+
import { m } from '../messages/index.js';
|
|
16
17
|
|
|
17
18
|
interface Props {
|
|
18
19
|
nodes: NodeMetadata[];
|
|
@@ -168,7 +169,7 @@
|
|
|
168
169
|
class:flowdrop-sidebar--collapsed={isCollapsed}
|
|
169
170
|
class:flowdrop-sidebar--compact={getUiSettings().compactMode}
|
|
170
171
|
style:width="{isCollapsed ? 0 : getUiSettings().sidebarWidth}px"
|
|
171
|
-
aria-label=
|
|
172
|
+
aria-label={m().layout.componentsSidebar}
|
|
172
173
|
>
|
|
173
174
|
<!-- Search Section — visibility controlled by --fd-sidebar-search-display -->
|
|
174
175
|
<div class="flowdrop-sidebar__search">
|
|
@@ -176,13 +177,13 @@
|
|
|
176
177
|
<div class="flowdrop-join__item flowdrop-flex--1">
|
|
177
178
|
<input
|
|
178
179
|
type="text"
|
|
179
|
-
placeholder=
|
|
180
|
+
placeholder={m().layout.searchComponents}
|
|
180
181
|
class="flowdrop-input flowdrop-join__item flowdrop-w--full"
|
|
181
182
|
bind:value={searchInput}
|
|
182
183
|
oninput={handleSearchChange}
|
|
183
184
|
/>
|
|
184
185
|
</div>
|
|
185
|
-
<button class="flowdrop-btn flowdrop-join__item" aria-label=
|
|
186
|
+
<button class="flowdrop-btn flowdrop-join__item" aria-label={m().layout.searchComponents}>
|
|
186
187
|
<Icon icon="mdi:magnify" class="flowdrop-icon" />
|
|
187
188
|
</button>
|
|
188
189
|
</div>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
formatExecutionDuration,
|
|
18
18
|
formatLastExecuted
|
|
19
19
|
} from '../utils/nodeStatus.js';
|
|
20
|
+
import { m } from '../messages/index.js';
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
nodeId?: string;
|
|
@@ -79,6 +80,9 @@
|
|
|
79
80
|
let shouldShow = $derived(
|
|
80
81
|
executionInfo.status !== 'idle' || executionInfo.executionCount > 0 || executionInfo.isExecuting
|
|
81
82
|
);
|
|
83
|
+
|
|
84
|
+
// Hoist the overlay branch — seven reads in the template.
|
|
85
|
+
const overlay = $derived(m().status.overlay);
|
|
82
86
|
</script>
|
|
83
87
|
|
|
84
88
|
{#if shouldShow}
|
|
@@ -98,9 +102,12 @@
|
|
|
98
102
|
"
|
|
99
103
|
onmouseenter={() => (isHovered = true)}
|
|
100
104
|
onmouseleave={() => (isHovered = false)}
|
|
101
|
-
title=
|
|
105
|
+
title={overlay.tooltip({
|
|
106
|
+
status: getStatusLabel(executionInfo.status),
|
|
107
|
+
count: executionInfo.executionCount
|
|
108
|
+
})}
|
|
102
109
|
role="status"
|
|
103
|
-
aria-label=
|
|
110
|
+
aria-label={overlay.ariaLabel({ status: getStatusLabel(executionInfo.status) })}
|
|
104
111
|
>
|
|
105
112
|
<!-- Status Display: [icon] [label] -->
|
|
106
113
|
<div
|
|
@@ -130,18 +137,18 @@
|
|
|
130
137
|
{#if showDetails && isHovered}
|
|
131
138
|
<div class="node-status-overlay__details">
|
|
132
139
|
<div class="node-status-overlay__detail-item">
|
|
133
|
-
<span class="node-status-overlay__detail-label">
|
|
140
|
+
<span class="node-status-overlay__detail-label">{overlay.statusLabel}</span>
|
|
134
141
|
<span class="node-status-overlay__detail-value"
|
|
135
142
|
>{getStatusLabel(executionInfo.status)}</span
|
|
136
143
|
>
|
|
137
144
|
</div>
|
|
138
145
|
<div class="node-status-overlay__detail-item">
|
|
139
|
-
<span class="node-status-overlay__detail-label">
|
|
146
|
+
<span class="node-status-overlay__detail-label">{overlay.executionsLabel}</span>
|
|
140
147
|
<span class="node-status-overlay__detail-value">{executionInfo.executionCount}</span>
|
|
141
148
|
</div>
|
|
142
149
|
{#if executionInfo.lastExecuted}
|
|
143
150
|
<div class="node-status-overlay__detail-item">
|
|
144
|
-
<span class="node-status-overlay__detail-label">
|
|
151
|
+
<span class="node-status-overlay__detail-label">{overlay.lastRunLabel}</span>
|
|
145
152
|
<span class="node-status-overlay__detail-value"
|
|
146
153
|
>{formatLastExecuted(executionInfo.lastExecuted)}</span
|
|
147
154
|
>
|
|
@@ -149,7 +156,7 @@
|
|
|
149
156
|
{/if}
|
|
150
157
|
{#if executionInfo.lastExecutionDuration}
|
|
151
158
|
<div class="node-status-overlay__detail-item">
|
|
152
|
-
<span class="node-status-overlay__detail-label">
|
|
159
|
+
<span class="node-status-overlay__detail-label">{overlay.durationLabel}</span>
|
|
153
160
|
<span class="node-status-overlay__detail-value"
|
|
154
161
|
>{formatExecutionDuration(executionInfo.lastExecutionDuration)}</span
|
|
155
162
|
>
|
|
@@ -157,7 +164,7 @@
|
|
|
157
164
|
{/if}
|
|
158
165
|
{#if executionInfo.lastError}
|
|
159
166
|
<div class="node-status-overlay__detail-item node-status-overlay__detail-item--error">
|
|
160
|
-
<span class="node-status-overlay__detail-label">
|
|
167
|
+
<span class="node-status-overlay__detail-label">{overlay.errorLabel}</span>
|
|
161
168
|
<span class="node-status-overlay__detail-value">{executionInfo.lastError}</span>
|
|
162
169
|
</div>
|
|
163
170
|
{/if}
|