@d34dman/flowdrop 0.0.1 → 0.0.2

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.
Files changed (119) hide show
  1. package/README.md +307 -215
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +30 -30
  4. package/dist/api/client.d.ts +24 -1
  5. package/dist/api/client.js +55 -38
  6. package/dist/api/enhanced-client.d.ts +46 -0
  7. package/dist/api/enhanced-client.js +211 -0
  8. package/dist/clients/ApiClient.d.ts +19 -23
  9. package/dist/clients/ApiClient.js +36 -34
  10. package/dist/components/App.svelte +1299 -230
  11. package/dist/components/App.svelte.d.ts +21 -1
  12. package/dist/components/CanvasBanner.svelte +50 -44
  13. package/dist/components/CanvasBanner.svelte.d.ts +5 -19
  14. package/dist/components/ConfigForm.svelte +555 -0
  15. package/dist/components/ConfigForm.svelte.d.ts +32 -0
  16. package/dist/components/ConfigModal.svelte +261 -0
  17. package/dist/components/ConfigModal.svelte.d.ts +31 -0
  18. package/dist/components/ConfigSidebar.svelte +934 -0
  19. package/dist/components/ConfigSidebar.svelte.d.ts +51 -0
  20. package/dist/components/ConnectionLine.svelte +32 -0
  21. package/dist/components/ConnectionLine.svelte.d.ts +3 -0
  22. package/dist/components/GatewayNode.svelte +471 -0
  23. package/dist/components/GatewayNode.svelte.d.ts +15 -0
  24. package/dist/components/LoadingSpinner.svelte +23 -23
  25. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  26. package/dist/components/Logo.svelte +82 -0
  27. package/dist/components/Logo.svelte.d.ts +26 -0
  28. package/dist/components/LogsSidebar.svelte +565 -0
  29. package/dist/components/LogsSidebar.svelte.d.ts +34 -0
  30. package/dist/components/MarkdownDisplay.svelte +28 -0
  31. package/dist/components/MarkdownDisplay.svelte.d.ts +7 -0
  32. package/dist/components/Navbar.svelte +663 -0
  33. package/dist/components/Navbar.svelte.d.ts +21 -0
  34. package/dist/components/NodeSidebar.svelte +629 -488
  35. package/dist/components/NodeSidebar.svelte.d.ts +1 -2
  36. package/dist/components/NodeStatusOverlay.svelte +327 -0
  37. package/dist/components/NodeStatusOverlay.svelte.d.ts +11 -0
  38. package/dist/components/NotesNode.svelte +566 -0
  39. package/dist/components/NotesNode.svelte.d.ts +43 -0
  40. package/dist/components/PipelineStatus.svelte +331 -0
  41. package/dist/components/PipelineStatus.svelte.d.ts +18 -0
  42. package/dist/components/SimpleNode.svelte +447 -0
  43. package/dist/components/SimpleNode.svelte.d.ts +24 -0
  44. package/dist/components/SquareNode.svelte +346 -0
  45. package/dist/components/SquareNode.svelte.d.ts +24 -0
  46. package/dist/components/StatusIcon.svelte +112 -0
  47. package/dist/components/StatusIcon.svelte.d.ts +10 -0
  48. package/dist/components/StatusLabel.svelte +33 -0
  49. package/dist/components/StatusLabel.svelte.d.ts +7 -0
  50. package/dist/components/ToolNode.svelte +385 -0
  51. package/dist/components/ToolNode.svelte.d.ts +36 -0
  52. package/dist/components/UniversalNode.svelte +126 -0
  53. package/dist/components/UniversalNode.svelte.d.ts +15 -0
  54. package/dist/components/WorkflowEditor.svelte +871 -528
  55. package/dist/components/WorkflowEditor.svelte.d.ts +15 -5
  56. package/dist/components/WorkflowNode.svelte +428 -542
  57. package/dist/components/WorkflowNode.svelte.d.ts +7 -3
  58. package/dist/config/apiConfig.d.ts +33 -0
  59. package/dist/config/apiConfig.js +39 -0
  60. package/dist/config/defaultPortConfig.d.ts +6 -0
  61. package/dist/config/defaultPortConfig.js +192 -0
  62. package/dist/config/demo.d.ts +58 -0
  63. package/dist/config/demo.js +142 -0
  64. package/dist/config/endpoints.d.ts +106 -0
  65. package/dist/config/endpoints.js +128 -0
  66. package/dist/data/samples.d.ts +38 -4
  67. package/dist/data/samples.js +2789 -737
  68. package/dist/examples/adapter-usage.d.ts +4 -4
  69. package/dist/examples/adapter-usage.js +21 -26
  70. package/dist/examples/api-client-usage.d.ts +6 -6
  71. package/dist/examples/api-client-usage.js +55 -54
  72. package/dist/index.d.ts +23 -15
  73. package/dist/index.js +23 -15
  74. package/dist/mocks/app-environment.d.ts +8 -0
  75. package/dist/mocks/app-environment.js +16 -0
  76. package/dist/mocks/app-forms.d.ts +2 -0
  77. package/dist/mocks/app-forms.js +21 -0
  78. package/dist/mocks/app-navigation.d.ts +5 -0
  79. package/dist/mocks/app-navigation.js +34 -0
  80. package/dist/mocks/app-stores.d.ts +14 -0
  81. package/dist/mocks/app-stores.js +26 -0
  82. package/dist/services/api.d.ts +13 -3
  83. package/dist/services/api.js +91 -36
  84. package/dist/services/globalSave.d.ts +20 -0
  85. package/dist/services/globalSave.js +165 -0
  86. package/dist/services/nodeExecutionService.d.ts +63 -0
  87. package/dist/services/nodeExecutionService.js +261 -0
  88. package/dist/services/portConfigApi.d.ts +14 -0
  89. package/dist/services/portConfigApi.js +69 -0
  90. package/dist/services/toastService.d.ts +147 -0
  91. package/dist/services/toastService.js +235 -0
  92. package/dist/services/workflowStorage.d.ts +2 -2
  93. package/dist/services/workflowStorage.js +10 -10
  94. package/dist/stores/workflowStore.d.ts +53 -0
  95. package/dist/stores/workflowStore.js +264 -0
  96. package/dist/styles/base.css +896 -363
  97. package/dist/svelte-app.d.ts +52 -5
  98. package/dist/svelte-app.js +128 -6
  99. package/dist/types/config.d.ts +291 -0
  100. package/dist/types/config.js +4 -0
  101. package/dist/types/index.d.ts +231 -19
  102. package/dist/types/index.js +1 -1
  103. package/dist/utils/colors.d.ts +67 -33
  104. package/dist/utils/colors.js +183 -118
  105. package/dist/utils/config.d.ts +41 -0
  106. package/dist/utils/config.js +248 -0
  107. package/dist/utils/connections.d.ts +40 -3
  108. package/dist/utils/connections.js +115 -44
  109. package/dist/utils/icons.d.ts +1 -1
  110. package/dist/utils/icons.js +71 -70
  111. package/dist/utils/nodeStatus.d.ts +53 -0
  112. package/dist/utils/nodeStatus.js +183 -0
  113. package/dist/utils/nodeTypes.d.ts +57 -0
  114. package/dist/utils/nodeTypes.js +109 -0
  115. package/dist/utils/nodeWrapper.d.ts +39 -0
  116. package/dist/utils/nodeWrapper.js +62 -0
  117. package/package.json +129 -97
  118. package/dist/components/Node.svelte +0 -38
  119. package/dist/components/Node.svelte.d.ts +0 -4
@@ -6,553 +6,439 @@
6
6
  -->
7
7
 
8
8
  <script lang="ts">
9
- import {
10
- Position,
11
- // @ts-ignore
12
- Handle,
13
- type NodeProps
14
- } from "@xyflow/svelte";
15
- import type { WorkflowNode, NodePort } from "../types/index.js";
16
- import Icon from "@iconify/svelte";
17
- import { getNodeIcon } from "../utils/icons.js";
18
- import { getDataTypeColorToken, getCategoryColorToken } from "../utils/colors.js";
19
-
20
- interface Props {
21
- data: WorkflowNode["data"] & { nodeId?: string };
22
- selected?: boolean;
23
- onPortClick?: (nodeId: string, portId: string, isOutput: boolean, event: MouseEvent) => void;
24
- }
25
-
26
- let props: Props = $props();
27
- let isExpanded = $state(false);
28
- let configValues = $state({ ...props.data.config });
29
- let isHandleInteraction = $state(false);
30
-
31
- /**
32
- * Handle configuration value changes
33
- */
34
- function handleConfigChange(key: string, value: unknown): void {
35
- configValues = { ...configValues, [key]: value };
36
- }
37
-
38
- /**
39
- * Handle node selection
40
- */
41
- function handleNodeClick(): void {
42
- // Node selection is handled through events
43
- }
44
-
45
- /**
46
- * Toggle node expansion
47
- */
48
- function toggleExpansion(): void {
49
- isExpanded = !isExpanded;
50
- }
51
-
52
- /**
53
- * Handle node drag start
54
- */
55
- function handleDragStart(event: DragEvent): void {
56
- // Prevent default drag behavior
57
- event.preventDefault();
58
- }
9
+ import { Position, Handle } from '@xyflow/svelte';
10
+ import type { WorkflowNode, NodeExecutionStatus } from '../types/index.js';
11
+ import Icon from '@iconify/svelte';
12
+ import { getNodeIcon } from '../utils/icons.js';
13
+ import { getDataTypeColorToken, getCategoryColorToken } from '../utils/colors.js';
14
+ import {
15
+ getStatusColor,
16
+ getStatusIcon,
17
+ getStatusLabel,
18
+ formatExecutionDuration,
19
+ formatLastExecuted
20
+ } from '../utils/nodeStatus.js';
21
+
22
+ interface Props {
23
+ data: WorkflowNode['data'] & {
24
+ nodeId?: string;
25
+ onConfigOpen?: (node: { id: string; type: string; data: WorkflowNode['data'] }) => void;
26
+ };
27
+ selected?: boolean;
28
+ }
29
+
30
+ let props: Props = $props();
31
+ let isHandleInteraction = $state(false);
32
+
33
+ /**
34
+ * Handle configuration value changes - now handled by global ConfigSidebar
35
+ */
36
+ // Removed local config handling - now using global ConfigSidebar
37
+
38
+ /**
39
+ * Handle node click - only handle selection, no config opening
40
+ */
41
+ function handleNodeClick(): void {
42
+ // Node selection is handled by Svelte Flow
43
+ }
44
+
45
+ /**
46
+ * Handle double-click to open config
47
+ */
48
+ function handleDoubleClick(): void {
49
+ openConfigSidebar();
50
+ }
51
+
52
+ /**
53
+ * Handle configuration sidebar - now using global ConfigSidebar
54
+ */
55
+ function openConfigSidebar(): void {
56
+ if (props.data.onConfigOpen) {
57
+ // Create a WorkflowNodeType-like object for the global ConfigSidebar
58
+ const nodeForConfig = {
59
+ id: props.data.nodeId || 'unknown',
60
+ type: 'workflowNode',
61
+ data: props.data
62
+ };
63
+ props.data.onConfigOpen(nodeForConfig);
64
+ }
65
+ }
59
66
  </script>
60
67
 
61
68
  <!-- Node Container -->
62
69
  <div
63
- class="flowdrop-workflow-node"
64
- class:flowdrop-workflow-node--selected={props.selected}
65
- onclick={handleNodeClick}
66
- onmouseup={() => {
67
- isHandleInteraction = false;
68
- }}
69
- data-handle-interaction={isHandleInteraction}
70
- role="button"
71
- tabindex="0"
72
- onkeydown={(e) => {
73
- if (e.key === "Enter" || e.key === " ") {
74
- e.preventDefault();
75
- handleNodeClick();
76
- }
77
- }}
78
- aria-label="Workflow node: {props.data.metadata.name}"
79
- aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
70
+ class="flowdrop-workflow-node"
71
+ class:flowdrop-workflow-node--selected={props.selected}
72
+ onclick={handleNodeClick}
73
+ ondblclick={handleDoubleClick}
74
+ onmouseup={() => {
75
+ isHandleInteraction = false;
76
+ }}
77
+ data-handle-interaction={isHandleInteraction}
78
+ role="button"
79
+ tabindex="0"
80
+ onkeydown={(e) => {
81
+ if (e.key === 'Enter' || e.key === ' ') {
82
+ e.preventDefault();
83
+ handleDoubleClick();
84
+ }
85
+ }}
86
+ aria-label="Workflow node: {props.data.metadata.name}"
87
+ aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
80
88
  >
81
- <!-- Node Header -->
82
- <div class="flowdrop-workflow-node__header">
83
- <div class="flowdrop-flex flowdrop-gap--3 flowdrop-items--center">
84
- <!-- Node Icon -->
85
- <div class="flowdrop-workflow-node__icon" style="background-color: {getCategoryColorToken(props.data.metadata.category)}">
86
- <Icon icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)} />
87
- </div>
88
-
89
- <!-- Node Title - Icon and Title on same line -->
90
- <h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
91
- {props.data.label}
92
- </h3>
93
-
94
- <!-- Status Indicators -->
95
- <div class="flowdrop-flex flowdrop-gap--2">
96
- {#if props.data.isProcessing}
97
- <div class="flowdrop-workflow-node__status flowdrop-workflow-node__status--processing" title="Processing"></div>
98
- {/if}
99
- {#if props.data.error}
100
- <div class="flowdrop-workflow-node__status flowdrop-workflow-node__status--error" title="Error"></div>
101
- {/if}
102
- <button
103
- class="flowdrop-workflow-node__expand-btn"
104
- onclick={(e) => {
105
- e.stopPropagation();
106
- toggleExpansion();
107
- }}
108
- type="button"
109
- aria-label="{isExpanded ? 'Collapse' : 'Expand'} node configuration"
110
- >
111
- <span class="flowdrop-text--xs flowdrop-font--medium">{isExpanded ? "−" : "+"}</span>
112
- </button>
113
- </div>
114
- </div>
115
- <!-- Node Description - on new line below -->
116
- <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1" id="node-description-{props.data.nodeId || 'unknown'}">
117
- {props.data.metadata.description}
118
- </p>
119
- </div>
120
-
121
- <!-- Node Configuration (Expanded) -->
122
- {#if isExpanded}
123
- <div class="flowdrop-workflow-node__config">
124
- <h4 class="flowdrop-workflow-node__config-title">Configuration</h4>
125
- <div class="flowdrop-workflow-node__config-content">
126
- {#each Object.entries(configValues) as [key, value]}
127
- <div class="flowdrop-form-control">
128
- <label class="flowdrop-form-control__label" for={`config-${key}`}>
129
- <span class="flowdrop-text--xs flowdrop-font--medium">{key}</span>
130
- </label>
131
- {#if typeof value === "string"}
132
- <input
133
- id={`config-${key}`}
134
- type="text"
135
- class="flowdrop-input flowdrop-input--sm"
136
- value={value}
137
- oninput={(e) => {
138
- const target = e.target as HTMLInputElement;
139
- if (target) handleConfigChange(key, target.value);
140
- }}
141
- />
142
- {:else if typeof value === "number"}
143
- <input
144
- id={`config-${key}`}
145
- type="number"
146
- class="flowdrop-input flowdrop-input--sm"
147
- value={value}
148
- oninput={(e) => {
149
- const target = e.target as HTMLInputElement;
150
- if (target) {
151
- const value = Number(target.value);
152
- if (!isNaN(value)) {
153
- handleConfigChange(key, value);
154
- }
155
- }
156
- }}
157
- />
158
- {:else if typeof value === "boolean"}
159
- <div class="flowdrop-flex flowdrop-gap--3">
160
- <input
161
- id={`config-${key}`}
162
- type="checkbox"
163
- class="flowdrop-toggle flowdrop-toggle--sm"
164
- checked={value}
165
- onchange={(e) => {
166
- const target = e.target as HTMLInputElement;
167
- if (target) handleConfigChange(key, target.checked);
168
- }}
169
- />
170
- <span class="flowdrop-text--xs">{key}</span>
171
- </div>
172
- {:else}
173
- <textarea
174
- id={`config-${key}`}
175
- class="flowdrop-textarea flowdrop-textarea--sm"
176
- placeholder="Enter value..."
177
- value={JSON.stringify(value, null, 2)}
178
- oninput={(e) => {
179
- const target = e.target as HTMLInputElement;
180
- if (target) {
181
- try {
182
- handleConfigChange(key, JSON.parse(target.value));
183
- } catch {
184
- handleConfigChange(key, target.value);
185
- }
186
- }
187
- }}
188
- ></textarea>
189
- {/if}
190
- </div>
191
- {/each}
192
- </div>
193
- </div>
194
- {/if}
195
-
196
- <!-- Input Ports Container -->
197
- {#if props.data.metadata.inputs.length > 0}
198
- <div class="flowdrop-workflow-node__ports">
199
- <div class="flowdrop-workflow-node__ports-header">
200
- <h5 class="flowdrop-workflow-node__ports-title">Inputs</h5>
201
- </div>
202
- <div class="flowdrop-workflow-node__ports-list">
203
- {#each props.data.metadata.inputs as port (port.id)}
204
- <div class="flowdrop-workflow-node__port">
205
- <!-- Input Handle -->
206
- <Handle
207
- type="target"
208
- position={Position.Left}
209
- id={port.id}
210
- class="flowdrop-workflow-node__handle"
211
- style="top: 50%; transform: translateY(-50%); margin-left: -16px;"
212
- role="button"
213
- tabindex={0}
214
- aria-label="Connect to {port.name} input port"
215
- />
216
-
217
- <!-- Port Info -->
218
- <div class="flowdrop-flex--1 flowdrop-min-w--0">
219
- <div class="flowdrop-flex flowdrop-gap--2">
220
- <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
221
- <span class="flowdrop-badge flowdrop-badge--sm" style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;">
222
- {port.dataType}
223
- </span>
224
- {#if port.required}
225
- <span class="flowdrop-badge flowdrop-badge--error flowdrop-badge--sm">Required</span>
226
- {/if}
227
- </div>
228
- {#if port.description}
229
- <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">{port.description}</p>
230
- {/if}
231
- </div>
232
- </div>
233
- {/each}
234
- </div>
235
- </div>
236
- {/if}
237
-
238
- <!-- Output Ports Container -->
239
- {#if props.data.metadata.outputs.length > 0}
240
- <div class="flowdrop-workflow-node__ports">
241
- <div class="flowdrop-workflow-node__ports-header">
242
- <h5 class="flowdrop-workflow-node__ports-title">Outputs</h5>
243
- </div>
244
- <div class="flowdrop-workflow-node__ports-list">
245
- {#each props.data.metadata.outputs as port (port.id)}
246
- <div class="flowdrop-workflow-node__port">
247
- <!-- Port Info -->
248
- <div class="flowdrop-flex--1 flowdrop-min-w--0 flowdrop-text--right">
249
- <div class="flowdrop-flex flowdrop-gap--2 flowdrop-justify--end">
250
- <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
251
- <span class="flowdrop-badge flowdrop-badge--sm" style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;">
252
- {port.dataType}
253
- </span>
254
- </div>
255
- {#if port.description}
256
- <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">{port.description}</p>
257
- {/if}
258
- </div>
259
-
260
- <!-- Output Handle -->
261
- <Handle
262
- type="source"
263
- position={Position.Right}
264
- id={port.id}
265
- class="flowdrop-workflow-node__handle"
266
- style="top: 50%; transform: translateY(-50%); margin-right: -16px;"
267
- role="button"
268
- tabindex={0}
269
- aria-label="Connect from {port.name} output port"
270
- />
271
- </div>
272
- {/each}
273
- </div>
274
- </div>
275
- {/if}
89
+ <!-- Default Node Header -->
90
+ <div class="flowdrop-workflow-node__header">
91
+ <div class="flowdrop-flex flowdrop-gap--3 flowdrop-items--center">
92
+ <!-- Node Icon -->
93
+ <div
94
+ class="flowdrop-workflow-node__icon"
95
+ style="background-color: {getCategoryColorToken(props.data.metadata.category)}"
96
+ >
97
+ <Icon icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)} />
98
+ </div>
99
+
100
+ <!-- Node Title - Icon and Title on same line -->
101
+ <h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
102
+ {props.data.label}
103
+ </h3>
104
+
105
+ <!-- Status Indicators -->
106
+ <div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
107
+ <!-- Status indicators removed - using outer NodeStatusOverlay instead -->
108
+ </div>
109
+ </div>
110
+ <!-- Node Description - on new line below -->
111
+ <p
112
+ class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1"
113
+ id="node-description-{props.data.nodeId || 'unknown'}"
114
+ >
115
+ {props.data.metadata.description}
116
+ </p>
117
+ </div>
118
+
119
+ <!-- Input Ports Container -->
120
+ {#if props.data.metadata.inputs.length > 0}
121
+ <div class="flowdrop-workflow-node__ports">
122
+ <div class="flowdrop-workflow-node__ports-header">
123
+ <h5 class="flowdrop-workflow-node__ports-title">Inputs</h5>
124
+ </div>
125
+ <div class="flowdrop-workflow-node__ports-list">
126
+ {#each props.data.metadata.inputs as port (port.id)}
127
+ <div class="flowdrop-workflow-node__port">
128
+ <!-- Input Handle -->
129
+ <Handle
130
+ type="target"
131
+ position={Position.Left}
132
+ id={`${props.data.nodeId}-input-${port.id}`}
133
+ class="flowdrop-workflow-node__handle"
134
+ style="top: 50%; transform: translateY(-50%); margin-left: -32px; background-color: {getDataTypeColorToken(
135
+ port.dataType
136
+ )}; border-color: '#ffffff';"
137
+ role="button"
138
+ tabindex={0}
139
+ aria-label="Connect to {port.name} input port"
140
+ />
141
+
142
+ <!-- Port Info -->
143
+ <div class="flowdrop-flex--1 flowdrop-min-w--0">
144
+ <div class="flowdrop-flex flowdrop-gap--2">
145
+ <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
146
+ <span
147
+ class="flowdrop-badge flowdrop-badge--sm"
148
+ style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;"
149
+ >
150
+ {port.dataType}
151
+ </span>
152
+ {#if port.required}
153
+ <span class="flowdrop-badge flowdrop-badge--error flowdrop-badge--sm"
154
+ >Required</span
155
+ >
156
+ {/if}
157
+ </div>
158
+ {#if port.description}
159
+ <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">
160
+ {port.description}
161
+ </p>
162
+ {/if}
163
+ </div>
164
+ </div>
165
+ {/each}
166
+ </div>
167
+ </div>
168
+ {/if}
169
+
170
+ <!-- Output Ports Container -->
171
+ {#if props.data.metadata.outputs.length > 0}
172
+ <div class="flowdrop-workflow-node__ports">
173
+ <div class="flowdrop-workflow-node__ports-header">
174
+ <h5 class="flowdrop-workflow-node__ports-title">Outputs</h5>
175
+ </div>
176
+ <div class="flowdrop-workflow-node__ports-list">
177
+ {#each props.data.metadata.outputs as port (port.id)}
178
+ <div class="flowdrop-workflow-node__port">
179
+ <!-- Port Info -->
180
+ <div class="flowdrop-flex--1 flowdrop-min-w--0 flowdrop-text--right">
181
+ <div class="flowdrop-flex flowdrop-gap--2 flowdrop-justify--end">
182
+ <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
183
+ <span
184
+ class="flowdrop-badge flowdrop-badge--sm"
185
+ style="background-color: {getDataTypeColorToken(port.dataType)}; color: #fff;"
186
+ >
187
+ {port.dataType}
188
+ </span>
189
+ </div>
190
+ {#if port.description}
191
+ <p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate">
192
+ {port.description}
193
+ </p>
194
+ {/if}
195
+ </div>
196
+
197
+ <!-- Output Handle -->
198
+ <Handle
199
+ type="source"
200
+ position={Position.Right}
201
+ id={`${props.data.nodeId}-output-${port.id}`}
202
+ class="flowdrop-workflow-node__handle"
203
+ style="top: 50%; transform: translateY(-50%); margin-right: -32px; background-color: {getDataTypeColorToken(
204
+ port.dataType
205
+ )}; border-color: '#ffffff';"
206
+ role="button"
207
+ tabindex={0}
208
+ aria-label="Connect from {port.name} output port"
209
+ />
210
+ </div>
211
+ {/each}
212
+ </div>
213
+ </div>
214
+ {/if}
215
+
216
+ <!-- Config button -->
217
+ <button
218
+ class="flowdrop-workflow-node__config-btn"
219
+ onclick={openConfigSidebar}
220
+ title="Configure node"
221
+ >
222
+ <Icon icon="mdi:cog" />
223
+ </button>
276
224
  </div>
277
225
 
226
+ <!-- ConfigSidebar removed - now using global ConfigSidebar in WorkflowEditor -->
227
+
278
228
  <style>
279
- .flowdrop-workflow-node {
280
- position: relative;
281
- background-color: #ffffff;
282
- border: 2px solid #e5e7eb;
283
- border-radius: 0.75rem;
284
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
285
- min-width: 14rem;
286
- max-width: 18rem;
287
- }
288
-
289
- .flowdrop-workflow-node--selected {
290
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
291
- border: 2px solid #3b82f6;
292
- }
293
-
294
- .flowdrop-workflow-node__header {
295
- padding: 1rem;
296
- border-bottom: 1px solid #e5e7eb;
297
- background-color: #f9fafb;
298
- border-top-left-radius: 0.75rem;
299
- border-top-right-radius: 0.75rem;
300
- }
301
-
302
- .flowdrop-workflow-node__icon {
303
- width: 2rem;
304
- height: 2rem;
305
- border-radius: 0.5rem;
306
- color: #ffffff;
307
- font-size: 0.875rem;
308
- font-weight: 500;
309
- display: flex;
310
- align-items: center;
311
- justify-content: center;
312
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
313
- }
314
-
315
- .flowdrop-workflow-node__header h3 {
316
- margin: 0;
317
- line-height: 1;
318
- }
319
-
320
- .flowdrop-workflow-node__status {
321
- width: 0.5rem;
322
- height: 0.5rem;
323
- border-radius: 50%;
324
- }
325
-
326
- .flowdrop-workflow-node__status--processing {
327
- background-color: #f59e0b;
328
- animation: flowdrop-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
329
- }
330
-
331
- .flowdrop-workflow-node__status--error {
332
- background-color: #ef4444;
333
- }
334
-
335
- .flowdrop-workflow-node__expand-btn {
336
- width: 1.5rem;
337
- height: 1.5rem;
338
- border-radius: 0.375rem;
339
- background-color: #f3f4f6;
340
- display: flex;
341
- align-items: center;
342
- justify-content: center;
343
- color: #6b7280;
344
- transition: all 0.2s ease-in-out;
345
- border: none;
346
- cursor: pointer;
347
- }
348
-
349
- .flowdrop-workflow-node__expand-btn:hover {
350
- background-color: #e5e7eb;
351
- color: #374151;
352
- }
353
-
354
- .flowdrop-workflow-node__config {
355
- padding: 1rem;
356
- border-bottom: 1px solid #e5e7eb;
357
- background-color: #f9fafb;
358
- }
359
-
360
- .flowdrop-workflow-node__config-title {
361
- font-size: 0.75rem;
362
- font-weight: 600;
363
- color: #374151;
364
- margin-bottom: 0.75rem;
365
- text-transform: uppercase;
366
- letter-spacing: 0.05em;
367
- }
368
-
369
- .flowdrop-workflow-node__config-content {
370
- display: flex;
371
- flex-direction: column;
372
- gap: 0.75rem;
373
- }
374
-
375
- .flowdrop-form-control {
376
- display: flex;
377
- flex-direction: column;
378
- }
379
-
380
- .flowdrop-form-control__label {
381
- padding-bottom: 0.25rem;
382
- display: flex;
383
- flex-direction: column;
384
- }
385
-
386
- .flowdrop-textarea {
387
- display: block;
388
- width: 100%;
389
- padding: 0.375rem 0.5rem;
390
- border: 1px solid #d1d5db;
391
- border-radius: 0.375rem;
392
- font-size: 0.75rem;
393
- line-height: 1.25rem;
394
- color: #111827;
395
- background-color: rgba(255, 255, 255, 0.7);
396
- transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
397
- resize: vertical;
398
- min-height: 2.5rem;
399
- }
400
-
401
- .flowdrop-textarea:focus {
402
- outline: none;
403
- border-color: #3b82f6;
404
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
405
- }
406
-
407
- .flowdrop-textarea--sm {
408
- padding: 0.375rem 0.5rem;
409
- font-size: 0.75rem;
410
- }
411
-
412
- .flowdrop-toggle {
413
- appearance: none;
414
- width: 2rem;
415
- height: 1rem;
416
- background-color: #d1d5db;
417
- border-radius: 9999px;
418
- position: relative;
419
- cursor: pointer;
420
- transition: background-color 0.2s ease-in-out;
421
- }
422
-
423
- .flowdrop-toggle:checked {
424
- background-color: #3b82f6;
425
- }
426
-
427
- .flowdrop-toggle::before {
428
- content: "";
429
- position: absolute;
430
- width: 0.75rem;
431
- height: 0.75rem;
432
- background-color: #ffffff;
433
- border-radius: 50%;
434
- top: 0.125rem;
435
- left: 0.125rem;
436
- transition: transform 0.2s ease-in-out;
437
- }
438
-
439
- .flowdrop-toggle:checked::before {
440
- transform: translateX(1rem);
441
- }
442
-
443
- .flowdrop-toggle--sm {
444
- width: 1.5rem;
445
- height: 0.75rem;
446
- }
447
-
448
- .flowdrop-toggle--sm::before {
449
- width: 0.5rem;
450
- height: 0.5rem;
451
- top: 0.125rem;
452
- left: 0.125rem;
453
- }
454
-
455
- .flowdrop-toggle--sm:checked::before {
456
- transform: translateX(0.75rem);
457
- }
458
-
459
- .flowdrop-workflow-node__ports {
460
- border-bottom: 1px solid #e5e7eb;
461
- }
462
-
463
- .flowdrop-workflow-node__ports:last-child {
464
- border-bottom: none;
465
- }
466
-
467
- .flowdrop-workflow-node__ports-header {
468
- padding: 0.5rem 1rem;
469
- background-color: #f3f4f6;
470
- }
471
-
472
- .flowdrop-workflow-node__ports-title {
473
- font-size: 0.75rem;
474
- font-weight: 600;
475
- color: #6b7280;
476
- text-transform: uppercase;
477
- letter-spacing: 0.05em;
478
- }
479
-
480
- .flowdrop-workflow-node__ports-list {
481
- display: flex;
482
- flex-direction: column;
483
- gap: 0.25rem;
484
- }
485
-
486
- .flowdrop-workflow-node__port {
487
- position: relative;
488
- display: flex;
489
- align-items: center;
490
- gap: 0.5rem;
491
- padding: 0.5rem 1rem;
492
- transition: background-color 0.2s ease-in-out;
493
- }
494
-
495
- .flowdrop-workflow-node__port:hover {
496
- background-color: #f9fafb;
497
- }
498
-
499
-
500
- .flowdrop-badge--sm {
501
- padding: 0.125rem 0.25rem;
502
- font-size: 0.625rem;
503
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
504
- }
505
-
506
- .flowdrop-badge--error {
507
- background-color: #fee2e2;
508
- color: #991b1b;
509
- }
510
-
511
- .flowdrop-justify--end {
512
- justify-content: flex-end;
513
- }
514
-
515
- .flowdrop-text--right {
516
- text-align: right;
517
- }
518
-
519
- .flowdrop-min-w--0 {
520
- min-width: 0;
521
- }
522
-
523
- .flowdrop-truncate {
524
- overflow: hidden;
525
- text-overflow: ellipsis;
526
- white-space: nowrap;
527
- }
528
-
529
- .flowdrop-mt--1 {
530
- margin-top: 0.25rem;
531
- }
532
-
533
- @keyframes flowdrop-pulse {
534
- 0%, 100% {
535
- opacity: 1;
536
- }
537
- 50% {
538
- opacity: 0.5;
539
- }
540
- }
541
-
542
- /* Focus styles for accessibility */
543
- .flowdrop-workflow-node:focus {
544
- outline: 2px solid #3b82f6;
545
- outline-offset: 2px;
546
- }
547
-
548
- .flowdrop-workflow-node:focus:not(:focus-visible) {
549
- outline: none;
550
- }
551
-
552
- /* Configuration form styling */
553
- .flowdrop-form-control input:focus,
554
- .flowdrop-form-control textarea:focus {
555
- transform: translateY(-1px);
556
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
557
- }
558
- </style>
229
+ .flowdrop-workflow-node {
230
+ position: relative;
231
+ background-color: #ffffff;
232
+ border: 2px solid #e5e7eb;
233
+ border-radius: 0.75rem;
234
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
235
+ width: 18rem;
236
+ z-index: 10;
237
+ }
238
+
239
+ .flowdrop-workflow-node--selected {
240
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
241
+ border: 2px solid #3b82f6;
242
+ }
243
+
244
+ .flowdrop-workflow-node__header {
245
+ padding: 1rem;
246
+ border-bottom: 1px solid #e5e7eb;
247
+ background-color: #f9fafb;
248
+ border-top-left-radius: 0.75rem;
249
+ border-top-right-radius: 0.75rem;
250
+ }
251
+
252
+ .flowdrop-workflow-node__icon {
253
+ width: 2rem;
254
+ height: 2rem;
255
+ border-radius: 0.5rem;
256
+ color: #ffffff;
257
+ font-size: 0.875rem;
258
+ font-weight: 500;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
263
+ }
264
+
265
+ .flowdrop-workflow-node__header h3 {
266
+ margin: 0;
267
+ line-height: 1;
268
+ }
269
+
270
+ /* Status indicator styles removed - using outer NodeStatusOverlay instead */
271
+
272
+ @keyframes pulse {
273
+ 0%,
274
+ 100% {
275
+ opacity: 1;
276
+ }
277
+ 50% {
278
+ opacity: 0.5;
279
+ }
280
+ }
281
+
282
+ .flowdrop-workflow-node__ports {
283
+ padding: 0.75rem 1rem;
284
+ }
285
+
286
+ .flowdrop-workflow-node__ports-header {
287
+ margin-bottom: 0.5rem;
288
+ }
289
+
290
+ .flowdrop-workflow-node__ports-title {
291
+ margin: 0;
292
+ font-size: 0.75rem;
293
+ font-weight: 600;
294
+ color: #374151;
295
+ text-transform: uppercase;
296
+ letter-spacing: 0.05em;
297
+ }
298
+
299
+ .flowdrop-workflow-node__ports-list {
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 0.5rem;
303
+ }
304
+
305
+ .flowdrop-workflow-node__port {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 0.5rem;
309
+ padding: 0.25rem 0;
310
+ position: relative;
311
+ }
312
+
313
+ .flowdrop-badge {
314
+ padding: 0.125rem 0.375rem;
315
+ border-radius: 0.25rem;
316
+ font-size: 0.625rem;
317
+ font-weight: 500;
318
+ text-transform: uppercase;
319
+ letter-spacing: 0.05em;
320
+ }
321
+
322
+ .flowdrop-badge--error {
323
+ background-color: #ef4444;
324
+ color: #ffffff;
325
+ }
326
+
327
+ .flowdrop-badge--sm {
328
+ font-size: 0.625rem;
329
+ padding: 0.125rem 0.25rem;
330
+ }
331
+
332
+ /* Handle styles */
333
+ :global(.flowdrop-workflow-node__handle) {
334
+ width: 0.75rem;
335
+ height: 0.75rem;
336
+ background-color: #6b7280;
337
+ border: 2px solid #ffffff;
338
+ border-radius: 50%;
339
+ transition: all 0.2s ease-in-out;
340
+ cursor: pointer;
341
+ }
342
+
343
+ :global(.flowdrop-workflow-node__handle:hover) {
344
+ background-color: #3b82f6;
345
+ transform: scale(1.2);
346
+ }
347
+
348
+ :global(.flowdrop-workflow-node__handle:focus) {
349
+ outline: 2px solid #3b82f6;
350
+ outline-offset: 2px;
351
+ }
352
+
353
+ /* Utility classes */
354
+ .flowdrop-flex {
355
+ display: flex;
356
+ }
357
+
358
+ .flowdrop-flex--1 {
359
+ flex: 1;
360
+ }
361
+
362
+ .flowdrop-gap--2 {
363
+ gap: 0.5rem;
364
+ }
365
+
366
+ .flowdrop-gap--3 {
367
+ gap: 0.75rem;
368
+ }
369
+
370
+ .flowdrop-items--center {
371
+ align-items: center;
372
+ }
373
+
374
+ .flowdrop-justify--end {
375
+ justify-content: flex-end;
376
+ }
377
+
378
+ .flowdrop-min-w--0 {
379
+ min-width: 0;
380
+ }
381
+
382
+ .flowdrop-text--xs {
383
+ font-size: 0.75rem;
384
+ line-height: 1rem;
385
+ }
386
+
387
+ .flowdrop-text--sm {
388
+ font-size: 0.875rem;
389
+ line-height: 1.25rem;
390
+ }
391
+
392
+ .flowdrop-text--gray {
393
+ color: #6b7280;
394
+ }
395
+
396
+ .flowdrop-font--medium {
397
+ font-weight: 500;
398
+ }
399
+
400
+ .flowdrop-truncate {
401
+ overflow: hidden;
402
+ text-overflow: ellipsis;
403
+ white-space: nowrap;
404
+ }
405
+
406
+ .flowdrop-mt--1 {
407
+ margin-top: 0.25rem;
408
+ }
409
+
410
+ .flowdrop-text--right {
411
+ text-align: right;
412
+ }
413
+
414
+ .flowdrop-workflow-node__config-btn {
415
+ position: absolute;
416
+ top: 0.5rem;
417
+ right: 0.5rem;
418
+ width: 1.5rem;
419
+ height: 1.5rem;
420
+ background-color: rgba(255, 255, 255, 0.9);
421
+ border: 1px solid #e5e7eb;
422
+ border-radius: 0.25rem;
423
+ color: #6b7280;
424
+ cursor: pointer;
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: center;
428
+ opacity: 0;
429
+ transition: all 0.2s ease-in-out;
430
+ backdrop-filter: blur(4px);
431
+ z-index: 15;
432
+ font-size: 0.875rem;
433
+ }
434
+
435
+ .flowdrop-workflow-node:hover .flowdrop-workflow-node__config-btn {
436
+ opacity: 1;
437
+ }
438
+
439
+ .flowdrop-workflow-node__config-btn:hover {
440
+ background-color: #f9fafb;
441
+ border-color: #d1d5db;
442
+ color: #374151;
443
+ }
444
+ </style>