@flowdrop/flowdrop 1.2.2 → 1.4.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/dist/components/App.svelte +80 -5
- package/dist/components/App.svelte.d.ts +7 -0
- package/dist/components/CanvasBanner.stories.svelte +6 -2
- package/dist/components/ConfigForm.svelte +421 -0
- package/dist/components/FlowDropEdge.svelte +7 -48
- package/dist/components/Logo.svelte +14 -14
- package/dist/components/Navbar.svelte +58 -10
- package/dist/components/Navbar.svelte.d.ts +7 -0
- package/dist/components/NodeSidebar.svelte +236 -304
- package/dist/components/WorkflowEditor.svelte +16 -6
- package/dist/components/nodes/GatewayNode.svelte +8 -9
- package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
- package/dist/components/nodes/SimpleNode.svelte +93 -104
- package/dist/components/nodes/SquareNode.stories.svelte +45 -0
- package/dist/components/nodes/SquareNode.svelte +94 -104
- package/dist/components/nodes/TerminalNode.svelte +6 -7
- package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
- package/dist/components/nodes/WorkflowNode.svelte +54 -19
- package/dist/schemas/v1/workflow.schema.json +22 -107
- package/dist/skins/slate.js +16 -0
- package/dist/stories/EdgeDecorator.svelte +4 -4
- package/dist/styles/base.css +48 -0
- package/dist/svelte-app.d.ts +7 -1
- package/dist/svelte-app.js +4 -1
- package/dist/types/index.d.ts +17 -0
- package/dist/utils/portUtils.d.ts +24 -0
- package/dist/utils/portUtils.js +42 -0
- package/package.json +3 -3
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import MainLayout from "./layouts/MainLayout.svelte";
|
|
10
10
|
import WorkflowEditor from "./WorkflowEditor.svelte";
|
|
11
11
|
import NodeSidebar from "./NodeSidebar.svelte";
|
|
12
|
+
import Icon from "@iconify/svelte";
|
|
12
13
|
import ConfigForm from "./ConfigForm.svelte";
|
|
13
14
|
import ConfigPanel from "./ConfigPanel.svelte";
|
|
14
15
|
import Navbar from "./Navbar.svelte";
|
|
@@ -46,12 +47,16 @@
|
|
|
46
47
|
} from "../services/globalSave.js";
|
|
47
48
|
import { apiToasts, dismissToast } from "../services/toastService.js";
|
|
48
49
|
import { initAutoSave } from "../services/autoSaveService.js";
|
|
49
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
getUiSettings,
|
|
52
|
+
updateSettings,
|
|
53
|
+
} from "../stores/settingsStore.svelte.js";
|
|
50
54
|
import { initializePortCompatibility } from "../utils/connections.js";
|
|
51
55
|
import { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
|
|
52
56
|
import { workflowFormatRegistry } from "../registry/workflowFormatRegistry.js";
|
|
53
57
|
import { logger } from "../utils/logger.js";
|
|
54
58
|
import { validateWorkflowData } from "../utils/validation.js";
|
|
59
|
+
import type { SettingsCategory } from "../types/settings.js";
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
62
|
* Configuration props for runtime customization
|
|
@@ -104,6 +109,12 @@
|
|
|
104
109
|
features?: FlowDropFeatures;
|
|
105
110
|
/** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
|
|
106
111
|
theme?: FlowDropTheme | FlowDropThemeName;
|
|
112
|
+
/** Which settings tabs to show in the modal */
|
|
113
|
+
settingsCategories?: SettingsCategory[];
|
|
114
|
+
/** Show the "Sync to Cloud" button in the settings modal */
|
|
115
|
+
showSettingsSyncButton?: boolean;
|
|
116
|
+
/** Show the reset buttons in the settings modal */
|
|
117
|
+
showSettingsResetButton?: boolean;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
let {
|
|
@@ -126,6 +137,9 @@
|
|
|
126
137
|
eventHandlers,
|
|
127
138
|
features: propFeatures,
|
|
128
139
|
theme: themeProp,
|
|
140
|
+
settingsCategories,
|
|
141
|
+
showSettingsSyncButton,
|
|
142
|
+
showSettingsResetButton,
|
|
129
143
|
}: Props = $props();
|
|
130
144
|
|
|
131
145
|
// svelte-ignore state_referenced_locally — feature flags don't change at runtime
|
|
@@ -667,12 +681,22 @@
|
|
|
667
681
|
|
|
668
682
|
/**
|
|
669
683
|
* Calculate left sidebar width based on collapsed state
|
|
670
|
-
* When collapsed, use
|
|
684
|
+
* When collapsed, use 0; otherwise use user-configured width
|
|
671
685
|
*/
|
|
672
686
|
const leftSidebarWidth = $derived(
|
|
673
|
-
getUiSettings().sidebarCollapsed ?
|
|
687
|
+
getUiSettings().sidebarCollapsed ? 0 : getUiSettings().sidebarWidth,
|
|
674
688
|
);
|
|
675
689
|
|
|
690
|
+
/** Whether the sidebar is collapsed */
|
|
691
|
+
const isSidebarCollapsed = $derived(getUiSettings().sidebarCollapsed);
|
|
692
|
+
|
|
693
|
+
/** Toggle sidebar collapsed state */
|
|
694
|
+
function toggleSidebar(): void {
|
|
695
|
+
updateSettings({
|
|
696
|
+
ui: { sidebarCollapsed: !getUiSettings().sidebarCollapsed },
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
676
700
|
// File input reference for workflow import
|
|
677
701
|
let fileInputRef = $state<HTMLInputElement | null>(null);
|
|
678
702
|
</script>
|
|
@@ -705,8 +729,8 @@
|
|
|
705
729
|
headerHeight={60}
|
|
706
730
|
{leftSidebarWidth}
|
|
707
731
|
rightSidebarWidth={400}
|
|
708
|
-
leftSidebarMinWidth={getUiSettings().sidebarCollapsed ?
|
|
709
|
-
leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ?
|
|
732
|
+
leftSidebarMinWidth={getUiSettings().sidebarCollapsed ? 0 : 280}
|
|
733
|
+
leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ? 0 : 450}
|
|
710
734
|
rightSidebarMinWidth={320}
|
|
711
735
|
rightSidebarMaxWidth={550}
|
|
712
736
|
enableLeftSplitPane={false}
|
|
@@ -763,6 +787,9 @@
|
|
|
763
787
|
]}
|
|
764
788
|
showStatus={true}
|
|
765
789
|
{showSettings}
|
|
790
|
+
{settingsCategories}
|
|
791
|
+
{showSettingsSyncButton}
|
|
792
|
+
{showSettingsResetButton}
|
|
766
793
|
/>
|
|
767
794
|
{/snippet}
|
|
768
795
|
|
|
@@ -974,6 +1001,20 @@
|
|
|
974
1001
|
role="region"
|
|
975
1002
|
aria-label="Workflow canvas"
|
|
976
1003
|
>
|
|
1004
|
+
<!-- Floating sidebar toggle — always visible on the canvas top-left -->
|
|
1005
|
+
{#if !disableSidebar}
|
|
1006
|
+
<button
|
|
1007
|
+
class="flowdrop-sidebar-fab"
|
|
1008
|
+
onclick={toggleSidebar}
|
|
1009
|
+
aria-label={isSidebarCollapsed
|
|
1010
|
+
? "Expand sidebar"
|
|
1011
|
+
: "Collapse sidebar"}
|
|
1012
|
+
title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1013
|
+
>
|
|
1014
|
+
<Icon icon={isSidebarCollapsed ? "mdi:menu" : "mdi:menu-open"} />
|
|
1015
|
+
</button>
|
|
1016
|
+
{/if}
|
|
1017
|
+
|
|
977
1018
|
<WorkflowEditor
|
|
978
1019
|
bind:this={workflowEditorRef}
|
|
979
1020
|
{nodes}
|
|
@@ -1098,6 +1139,40 @@
|
|
|
1098
1139
|
font-weight: 500;
|
|
1099
1140
|
}
|
|
1100
1141
|
|
|
1142
|
+
/* Floating sidebar toggle button */
|
|
1143
|
+
.flowdrop-sidebar-fab {
|
|
1144
|
+
position: absolute;
|
|
1145
|
+
top: 12px;
|
|
1146
|
+
left: 12px;
|
|
1147
|
+
z-index: 50;
|
|
1148
|
+
display: flex;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
justify-content: center;
|
|
1151
|
+
width: 2.25rem;
|
|
1152
|
+
height: 2.25rem;
|
|
1153
|
+
border: 1px solid var(--fd-border);
|
|
1154
|
+
border-radius: var(--fd-radius-md);
|
|
1155
|
+
background-color: var(--fd-background);
|
|
1156
|
+
color: var(--fd-muted-foreground);
|
|
1157
|
+
cursor: pointer;
|
|
1158
|
+
box-shadow: var(--fd-shadow-md);
|
|
1159
|
+
transition:
|
|
1160
|
+
color var(--fd-transition-fast),
|
|
1161
|
+
background-color var(--fd-transition-fast),
|
|
1162
|
+
box-shadow var(--fd-transition-fast);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.flowdrop-sidebar-fab:hover {
|
|
1166
|
+
color: var(--fd-foreground);
|
|
1167
|
+
background-color: var(--fd-subtle);
|
|
1168
|
+
box-shadow: var(--fd-shadow-lg);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.flowdrop-sidebar-fab:focus {
|
|
1172
|
+
outline: none;
|
|
1173
|
+
box-shadow: 0 0 0 2px var(--fd-ring);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1101
1176
|
/* Main editor area */
|
|
1102
1177
|
.flowdrop-editor-main {
|
|
1103
1178
|
flex: 1;
|
|
@@ -3,6 +3,7 @@ import type { EndpointConfig } from "../config/endpoints.js";
|
|
|
3
3
|
import type { AuthProvider } from "../types/auth.js";
|
|
4
4
|
import type { FlowDropEventHandlers, FlowDropFeatures } from "../types/events.js";
|
|
5
5
|
import type { FlowDropTheme, FlowDropThemeName } from "../types/theme.js";
|
|
6
|
+
import type { SettingsCategory } from "../types/settings.js";
|
|
6
7
|
/**
|
|
7
8
|
* Configuration props for runtime customization
|
|
8
9
|
*/
|
|
@@ -51,6 +52,12 @@ interface Props {
|
|
|
51
52
|
features?: FlowDropFeatures;
|
|
52
53
|
/** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
|
|
53
54
|
theme?: FlowDropTheme | FlowDropThemeName;
|
|
55
|
+
/** Which settings tabs to show in the modal */
|
|
56
|
+
settingsCategories?: SettingsCategory[];
|
|
57
|
+
/** Show the "Sync to Cloud" button in the settings modal */
|
|
58
|
+
showSettingsSyncButton?: boolean;
|
|
59
|
+
/** Show the reset buttons in the settings modal */
|
|
60
|
+
showSettingsResetButton?: boolean;
|
|
54
61
|
}
|
|
55
62
|
declare const App: import("svelte").Component<Props, {}, "">;
|
|
56
63
|
type App = ReturnType<typeof App>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
3
|
import CanvasBanner from "./CanvasBanner.svelte";
|
|
4
4
|
import CanvasDecorator from "../stories/CanvasDecorator.svelte";
|
|
5
|
+
import Icon from "@iconify/svelte";
|
|
5
6
|
|
|
6
7
|
const { Story } = defineMeta({
|
|
7
8
|
title: "Display/CanvasBanner",
|
|
@@ -17,8 +18,11 @@
|
|
|
17
18
|
<CanvasBanner
|
|
18
19
|
title="Empty Canvas"
|
|
19
20
|
description="Drag nodes from the sidebar to get started"
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
>
|
|
22
|
+
{#snippet icon()}
|
|
23
|
+
<Icon icon="heroicons:squares-plus" />
|
|
24
|
+
{/snippet}
|
|
25
|
+
</CanvasBanner>
|
|
22
26
|
</CanvasDecorator>
|
|
23
27
|
</Story>
|
|
24
28
|
|
|
@@ -25,9 +25,12 @@
|
|
|
25
25
|
WorkflowNode,
|
|
26
26
|
WorkflowEdge,
|
|
27
27
|
NodeUIExtensions,
|
|
28
|
+
NodePort,
|
|
29
|
+
DynamicPort,
|
|
28
30
|
ConfigEditOptions,
|
|
29
31
|
AuthProvider,
|
|
30
32
|
} from "../types/index.js";
|
|
33
|
+
import { dynamicPortToNodePort } from "../types/index.js";
|
|
31
34
|
import type { UISchemaElement } from "../types/uischema.js";
|
|
32
35
|
import {
|
|
33
36
|
FormField,
|
|
@@ -46,6 +49,11 @@
|
|
|
46
49
|
import { globalSaveWorkflow } from "../services/globalSave.js";
|
|
47
50
|
import { getAvailableVariables } from "../services/variableService.js";
|
|
48
51
|
import { logger } from "../utils/logger.js";
|
|
52
|
+
import {
|
|
53
|
+
getDataTypeColorToken,
|
|
54
|
+
getPortBackgroundColor,
|
|
55
|
+
} from "../utils/colors.js";
|
|
56
|
+
import { applyPortOrder } from "../utils/portUtils.js";
|
|
49
57
|
|
|
50
58
|
interface Props {
|
|
51
59
|
/** Optional workflow node (if provided, schema and values are derived from it) */
|
|
@@ -338,9 +346,118 @@
|
|
|
338
346
|
uiExtensionValues = {
|
|
339
347
|
hideUnconnectedHandles:
|
|
340
348
|
initialUIExtensions.hideUnconnectedHandles ?? false,
|
|
349
|
+
portOrder: initialUIExtensions.portOrder
|
|
350
|
+
? {
|
|
351
|
+
inputs: initialUIExtensions.portOrder.inputs
|
|
352
|
+
? [...initialUIExtensions.portOrder.inputs]
|
|
353
|
+
: undefined,
|
|
354
|
+
outputs: initialUIExtensions.portOrder.outputs
|
|
355
|
+
? [...initialUIExtensions.portOrder.outputs]
|
|
356
|
+
: undefined,
|
|
357
|
+
}
|
|
358
|
+
: undefined,
|
|
359
|
+
hiddenPorts: initialUIExtensions.hiddenPorts
|
|
360
|
+
? {
|
|
361
|
+
inputs: initialUIExtensions.hiddenPorts.inputs
|
|
362
|
+
? [...initialUIExtensions.hiddenPorts.inputs]
|
|
363
|
+
: undefined,
|
|
364
|
+
outputs: initialUIExtensions.hiddenPorts.outputs
|
|
365
|
+
? [...initialUIExtensions.hiddenPorts.outputs]
|
|
366
|
+
: undefined,
|
|
367
|
+
}
|
|
368
|
+
: undefined,
|
|
341
369
|
};
|
|
342
370
|
});
|
|
343
371
|
|
|
372
|
+
/**
|
|
373
|
+
* All input ports in current display order for the port management UI.
|
|
374
|
+
* Combines static metadata inputs + dynamic config inputs, sorted by portOrder.
|
|
375
|
+
*/
|
|
376
|
+
const allInputPortsForUI = $derived.by<NodePort[]>(() => {
|
|
377
|
+
if (!node) return [];
|
|
378
|
+
const staticInputs = node.data.metadata.inputs ?? [];
|
|
379
|
+
const dynInputs = (
|
|
380
|
+
(node.data.config?.dynamicInputs as DynamicPort[]) || []
|
|
381
|
+
).map((p) => dynamicPortToNodePort(p, "input"));
|
|
382
|
+
return applyPortOrder(
|
|
383
|
+
[...staticInputs, ...dynInputs],
|
|
384
|
+
uiExtensionValues.portOrder?.inputs,
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* All output ports in current display order for the port management UI.
|
|
390
|
+
* Combines static metadata outputs + dynamic config outputs, sorted by portOrder.
|
|
391
|
+
*/
|
|
392
|
+
const allOutputPortsForUI = $derived.by<NodePort[]>(() => {
|
|
393
|
+
if (!node) return [];
|
|
394
|
+
const staticOutputs = node.data.metadata.outputs ?? [];
|
|
395
|
+
const dynOutputs = (
|
|
396
|
+
(node.data.config?.dynamicOutputs as DynamicPort[]) || []
|
|
397
|
+
).map((p) => dynamicPortToNodePort(p, "output"));
|
|
398
|
+
return applyPortOrder(
|
|
399
|
+
[...staticOutputs, ...dynOutputs],
|
|
400
|
+
uiExtensionValues.portOrder?.outputs,
|
|
401
|
+
);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Move a port one position up or down in the display order.
|
|
406
|
+
*/
|
|
407
|
+
function movePort(
|
|
408
|
+
direction: "inputs" | "outputs",
|
|
409
|
+
portId: string,
|
|
410
|
+
delta: -1 | 1,
|
|
411
|
+
): void {
|
|
412
|
+
const list =
|
|
413
|
+
direction === "inputs" ? allInputPortsForUI : allOutputPortsForUI;
|
|
414
|
+
const idx = list.findIndex((p) => p.id === portId);
|
|
415
|
+
if (idx === -1) return;
|
|
416
|
+
const newIdx = idx + delta;
|
|
417
|
+
if (newIdx < 0 || newIdx >= list.length) return;
|
|
418
|
+
const newOrder = list.map((p) => p.id);
|
|
419
|
+
[newOrder[idx], newOrder[newIdx]] = [newOrder[newIdx], newOrder[idx]];
|
|
420
|
+
uiExtensionValues.portOrder = {
|
|
421
|
+
...uiExtensionValues.portOrder,
|
|
422
|
+
[direction]: newOrder,
|
|
423
|
+
};
|
|
424
|
+
handleFormBlur();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Toggle manual visibility of a port. Required ports cannot be hidden.
|
|
429
|
+
*/
|
|
430
|
+
function togglePortHidden(
|
|
431
|
+
direction: "inputs" | "outputs",
|
|
432
|
+
portId: string,
|
|
433
|
+
): void {
|
|
434
|
+
const current = uiExtensionValues.hiddenPorts?.[direction] ?? [];
|
|
435
|
+
const isHidden = current.includes(portId);
|
|
436
|
+
const next = isHidden
|
|
437
|
+
? current.filter((id) => id !== portId)
|
|
438
|
+
: [...current, portId];
|
|
439
|
+
uiExtensionValues.hiddenPorts = {
|
|
440
|
+
...uiExtensionValues.hiddenPorts,
|
|
441
|
+
[direction]: next.length > 0 ? next : undefined,
|
|
442
|
+
};
|
|
443
|
+
handleFormBlur();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Reset all port customizations (order + hidden) for a direction back to defaults.
|
|
448
|
+
*/
|
|
449
|
+
function resetPortCustomizations(direction: "inputs" | "outputs"): void {
|
|
450
|
+
const order = { ...uiExtensionValues.portOrder };
|
|
451
|
+
const hidden = { ...uiExtensionValues.hiddenPorts };
|
|
452
|
+
delete order[direction];
|
|
453
|
+
delete hidden[direction];
|
|
454
|
+
uiExtensionValues.portOrder =
|
|
455
|
+
Object.keys(order).length > 0 ? order : undefined;
|
|
456
|
+
uiExtensionValues.hiddenPorts =
|
|
457
|
+
Object.keys(hidden).length > 0 ? hidden : undefined;
|
|
458
|
+
handleFormBlur();
|
|
459
|
+
}
|
|
460
|
+
|
|
344
461
|
/**
|
|
345
462
|
* Check if a field is required based on schema
|
|
346
463
|
*/
|
|
@@ -708,6 +825,176 @@
|
|
|
708
825
|
}}
|
|
709
826
|
/>
|
|
710
827
|
</FormFieldWrapper>
|
|
828
|
+
|
|
829
|
+
<!-- Input Port Order & Visibility -->
|
|
830
|
+
{#if allInputPortsForUI.length > 0}
|
|
831
|
+
<div class="config-form__port-order">
|
|
832
|
+
<div class="config-form__port-order-header">
|
|
833
|
+
<span class="config-form__port-order-label">Input Ports</span>
|
|
834
|
+
{#if uiExtensionValues.portOrder?.inputs?.length || uiExtensionValues.hiddenPorts?.inputs?.length}
|
|
835
|
+
<button
|
|
836
|
+
type="button"
|
|
837
|
+
class="config-form__port-order-reset"
|
|
838
|
+
onclick={() => resetPortCustomizations("inputs")}
|
|
839
|
+
title="Reset to default order and visibility"
|
|
840
|
+
>
|
|
841
|
+
<Icon icon="heroicons:arrow-uturn-left" />
|
|
842
|
+
Reset
|
|
843
|
+
</button>
|
|
844
|
+
{/if}
|
|
845
|
+
</div>
|
|
846
|
+
<ul class="config-form__port-order-list">
|
|
847
|
+
{#each allInputPortsForUI as port, i (port.id)}
|
|
848
|
+
{@const isHidden =
|
|
849
|
+
uiExtensionValues.hiddenPorts?.inputs?.includes(port.id) ??
|
|
850
|
+
false}
|
|
851
|
+
{@const isRequired = port.required ?? false}
|
|
852
|
+
<li
|
|
853
|
+
class="config-form__port-order-item"
|
|
854
|
+
class:config-form__port-order-item--hidden={isHidden}
|
|
855
|
+
>
|
|
856
|
+
<span class="config-form__port-order-name">{port.name}</span
|
|
857
|
+
>
|
|
858
|
+
<span
|
|
859
|
+
class="config-form__port-order-badge"
|
|
860
|
+
style="background-color:{getPortBackgroundColor(
|
|
861
|
+
port.dataType,
|
|
862
|
+
15,
|
|
863
|
+
)};color:{getDataTypeColorToken(
|
|
864
|
+
port.dataType,
|
|
865
|
+
)};border:1px solid {getPortBackgroundColor(
|
|
866
|
+
port.dataType,
|
|
867
|
+
30,
|
|
868
|
+
)}"
|
|
869
|
+
>
|
|
870
|
+
{port.dataType}
|
|
871
|
+
</span>
|
|
872
|
+
<div class="config-form__port-order-actions">
|
|
873
|
+
<button
|
|
874
|
+
type="button"
|
|
875
|
+
disabled={isRequired}
|
|
876
|
+
title={isRequired
|
|
877
|
+
? "Required ports cannot be hidden"
|
|
878
|
+
: isHidden
|
|
879
|
+
? "Show port"
|
|
880
|
+
: "Hide port"}
|
|
881
|
+
class:active={isHidden}
|
|
882
|
+
onclick={() => togglePortHidden("inputs", port.id)}
|
|
883
|
+
>
|
|
884
|
+
<Icon
|
|
885
|
+
icon={isHidden
|
|
886
|
+
? "heroicons:eye-slash"
|
|
887
|
+
: "heroicons:eye"}
|
|
888
|
+
/>
|
|
889
|
+
</button>
|
|
890
|
+
<button
|
|
891
|
+
type="button"
|
|
892
|
+
disabled={i === 0 || allInputPortsForUI.length === 1}
|
|
893
|
+
onclick={() => movePort("inputs", port.id, -1)}
|
|
894
|
+
title="Move up"
|
|
895
|
+
>
|
|
896
|
+
<Icon icon="heroicons:chevron-up" />
|
|
897
|
+
</button>
|
|
898
|
+
<button
|
|
899
|
+
type="button"
|
|
900
|
+
disabled={i === allInputPortsForUI.length - 1 ||
|
|
901
|
+
allInputPortsForUI.length === 1}
|
|
902
|
+
onclick={() => movePort("inputs", port.id, 1)}
|
|
903
|
+
title="Move down"
|
|
904
|
+
>
|
|
905
|
+
<Icon icon="heroicons:chevron-down" />
|
|
906
|
+
</button>
|
|
907
|
+
</div>
|
|
908
|
+
</li>
|
|
909
|
+
{/each}
|
|
910
|
+
</ul>
|
|
911
|
+
</div>
|
|
912
|
+
{/if}
|
|
913
|
+
|
|
914
|
+
<!-- Output Port Order & Visibility -->
|
|
915
|
+
{#if allOutputPortsForUI.length > 0}
|
|
916
|
+
<div class="config-form__port-order">
|
|
917
|
+
<div class="config-form__port-order-header">
|
|
918
|
+
<span class="config-form__port-order-label">Output Ports</span>
|
|
919
|
+
{#if uiExtensionValues.portOrder?.outputs?.length || uiExtensionValues.hiddenPorts?.outputs?.length}
|
|
920
|
+
<button
|
|
921
|
+
type="button"
|
|
922
|
+
class="config-form__port-order-reset"
|
|
923
|
+
onclick={() => resetPortCustomizations("outputs")}
|
|
924
|
+
title="Reset to default order and visibility"
|
|
925
|
+
>
|
|
926
|
+
<Icon icon="heroicons:arrow-uturn-left" />
|
|
927
|
+
Reset
|
|
928
|
+
</button>
|
|
929
|
+
{/if}
|
|
930
|
+
</div>
|
|
931
|
+
<ul class="config-form__port-order-list">
|
|
932
|
+
{#each allOutputPortsForUI as port, i (port.id)}
|
|
933
|
+
{@const isHidden =
|
|
934
|
+
uiExtensionValues.hiddenPorts?.outputs?.includes(port.id) ??
|
|
935
|
+
false}
|
|
936
|
+
{@const isRequired = port.required ?? false}
|
|
937
|
+
<li
|
|
938
|
+
class="config-form__port-order-item"
|
|
939
|
+
class:config-form__port-order-item--hidden={isHidden}
|
|
940
|
+
>
|
|
941
|
+
<span class="config-form__port-order-name">{port.name}</span
|
|
942
|
+
>
|
|
943
|
+
<span
|
|
944
|
+
class="config-form__port-order-badge"
|
|
945
|
+
style="background-color:{getPortBackgroundColor(
|
|
946
|
+
port.dataType,
|
|
947
|
+
15,
|
|
948
|
+
)};color:{getDataTypeColorToken(
|
|
949
|
+
port.dataType,
|
|
950
|
+
)};border:1px solid {getPortBackgroundColor(
|
|
951
|
+
port.dataType,
|
|
952
|
+
30,
|
|
953
|
+
)}"
|
|
954
|
+
>
|
|
955
|
+
{port.dataType}
|
|
956
|
+
</span>
|
|
957
|
+
<div class="config-form__port-order-actions">
|
|
958
|
+
<button
|
|
959
|
+
type="button"
|
|
960
|
+
disabled={isRequired}
|
|
961
|
+
title={isRequired
|
|
962
|
+
? "Required ports cannot be hidden"
|
|
963
|
+
: isHidden
|
|
964
|
+
? "Show port"
|
|
965
|
+
: "Hide port"}
|
|
966
|
+
class:active={isHidden}
|
|
967
|
+
onclick={() => togglePortHidden("outputs", port.id)}
|
|
968
|
+
>
|
|
969
|
+
<Icon
|
|
970
|
+
icon={isHidden
|
|
971
|
+
? "heroicons:eye-slash"
|
|
972
|
+
: "heroicons:eye"}
|
|
973
|
+
/>
|
|
974
|
+
</button>
|
|
975
|
+
<button
|
|
976
|
+
type="button"
|
|
977
|
+
disabled={i === 0 || allOutputPortsForUI.length === 1}
|
|
978
|
+
onclick={() => movePort("outputs", port.id, -1)}
|
|
979
|
+
title="Move up"
|
|
980
|
+
>
|
|
981
|
+
<Icon icon="heroicons:chevron-up" />
|
|
982
|
+
</button>
|
|
983
|
+
<button
|
|
984
|
+
type="button"
|
|
985
|
+
disabled={i === allOutputPortsForUI.length - 1 ||
|
|
986
|
+
allOutputPortsForUI.length === 1}
|
|
987
|
+
onclick={() => movePort("outputs", port.id, 1)}
|
|
988
|
+
title="Move down"
|
|
989
|
+
>
|
|
990
|
+
<Icon icon="heroicons:chevron-down" />
|
|
991
|
+
</button>
|
|
992
|
+
</div>
|
|
993
|
+
</li>
|
|
994
|
+
{/each}
|
|
995
|
+
</ul>
|
|
996
|
+
</div>
|
|
997
|
+
{/if}
|
|
711
998
|
</div>
|
|
712
999
|
</div>
|
|
713
1000
|
{/if}
|
|
@@ -928,6 +1215,140 @@
|
|
|
928
1215
|
gap: var(--fd-space-xl);
|
|
929
1216
|
}
|
|
930
1217
|
|
|
1218
|
+
/* ============================================
|
|
1219
|
+
PORT ORDER & VISIBILITY
|
|
1220
|
+
============================================ */
|
|
1221
|
+
|
|
1222
|
+
.config-form__port-order {
|
|
1223
|
+
border-top: 1px solid var(--fd-border-muted);
|
|
1224
|
+
padding-top: var(--fd-space-md);
|
|
1225
|
+
margin-top: calc(var(--fd-space-xl) * -0.25);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.config-form__port-order-header {
|
|
1229
|
+
display: flex;
|
|
1230
|
+
align-items: center;
|
|
1231
|
+
justify-content: space-between;
|
|
1232
|
+
margin-bottom: var(--fd-space-xs);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.config-form__port-order-label {
|
|
1236
|
+
font-size: var(--fd-text-xs);
|
|
1237
|
+
font-weight: 600;
|
|
1238
|
+
color: var(--fd-muted-foreground);
|
|
1239
|
+
text-transform: uppercase;
|
|
1240
|
+
letter-spacing: 0.05em;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.config-form__port-order-reset {
|
|
1244
|
+
background: none;
|
|
1245
|
+
border: none;
|
|
1246
|
+
font-size: var(--fd-text-xs);
|
|
1247
|
+
color: var(--fd-muted-foreground);
|
|
1248
|
+
cursor: pointer;
|
|
1249
|
+
display: inline-flex;
|
|
1250
|
+
align-items: center;
|
|
1251
|
+
gap: var(--fd-space-3xs);
|
|
1252
|
+
padding: 0;
|
|
1253
|
+
transition: color var(--fd-transition-fast);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.config-form__port-order-reset:hover {
|
|
1257
|
+
color: var(--fd-foreground);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.config-form__port-order-reset :global(svg) {
|
|
1261
|
+
width: 0.75rem;
|
|
1262
|
+
height: 0.75rem;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.config-form__port-order-list {
|
|
1266
|
+
list-style: none;
|
|
1267
|
+
margin: 0;
|
|
1268
|
+
padding: 0;
|
|
1269
|
+
display: flex;
|
|
1270
|
+
flex-direction: column;
|
|
1271
|
+
gap: var(--fd-space-3xs);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.config-form__port-order-item {
|
|
1275
|
+
display: flex;
|
|
1276
|
+
align-items: center;
|
|
1277
|
+
gap: var(--fd-space-xs);
|
|
1278
|
+
padding: var(--fd-space-3xs) var(--fd-space-xs);
|
|
1279
|
+
background: var(--fd-muted);
|
|
1280
|
+
border-radius: var(--fd-radius-sm);
|
|
1281
|
+
border: 1px solid var(--fd-border-muted);
|
|
1282
|
+
transition: opacity var(--fd-transition-fast);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.config-form__port-order-item--hidden {
|
|
1286
|
+
opacity: 0.4;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.config-form__port-order-name {
|
|
1290
|
+
flex: 1;
|
|
1291
|
+
font-size: var(--fd-text-xs);
|
|
1292
|
+
font-weight: 500;
|
|
1293
|
+
color: var(--fd-foreground);
|
|
1294
|
+
min-width: 0;
|
|
1295
|
+
overflow: hidden;
|
|
1296
|
+
text-overflow: ellipsis;
|
|
1297
|
+
white-space: nowrap;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.config-form__port-order-badge {
|
|
1301
|
+
padding: 0.125rem var(--fd-space-3xs);
|
|
1302
|
+
border-radius: var(--fd-radius-sm);
|
|
1303
|
+
font-size: 0.625rem;
|
|
1304
|
+
font-weight: 500;
|
|
1305
|
+
text-transform: uppercase;
|
|
1306
|
+
letter-spacing: 0.04em;
|
|
1307
|
+
flex-shrink: 0;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.config-form__port-order-actions {
|
|
1311
|
+
display: flex;
|
|
1312
|
+
gap: var(--fd-space-3xs);
|
|
1313
|
+
flex-shrink: 0;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.config-form__port-order-actions button {
|
|
1317
|
+
width: 1.25rem;
|
|
1318
|
+
height: 1.25rem;
|
|
1319
|
+
display: flex;
|
|
1320
|
+
align-items: center;
|
|
1321
|
+
justify-content: center;
|
|
1322
|
+
background: var(--fd-card);
|
|
1323
|
+
border: 1px solid var(--fd-border);
|
|
1324
|
+
border-radius: var(--fd-radius-sm);
|
|
1325
|
+
color: var(--fd-muted-foreground);
|
|
1326
|
+
cursor: pointer;
|
|
1327
|
+
padding: 0;
|
|
1328
|
+
transition: all var(--fd-transition-fast);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
.config-form__port-order-actions button:hover:not(:disabled) {
|
|
1332
|
+
background: var(--fd-backdrop);
|
|
1333
|
+
color: var(--fd-foreground);
|
|
1334
|
+
border-color: var(--fd-border-strong);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.config-form__port-order-actions button:disabled {
|
|
1338
|
+
opacity: 0.3;
|
|
1339
|
+
cursor: not-allowed;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
.config-form__port-order-actions button.active {
|
|
1343
|
+
color: var(--fd-foreground);
|
|
1344
|
+
border-color: var(--fd-border-strong);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.config-form__port-order-actions button :global(svg) {
|
|
1348
|
+
width: 0.75rem;
|
|
1349
|
+
height: 0.75rem;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
931
1352
|
/* ============================================
|
|
932
1353
|
DEBUG SECTION
|
|
933
1354
|
============================================ */
|