@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.
- package/README.md +307 -215
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/WorkflowAdapter.js +30 -30
- package/dist/api/client.d.ts +24 -1
- package/dist/api/client.js +55 -38
- package/dist/api/enhanced-client.d.ts +46 -0
- package/dist/api/enhanced-client.js +211 -0
- package/dist/clients/ApiClient.d.ts +19 -23
- package/dist/clients/ApiClient.js +36 -34
- package/dist/components/App.svelte +1299 -230
- package/dist/components/App.svelte.d.ts +21 -1
- package/dist/components/CanvasBanner.svelte +50 -44
- package/dist/components/CanvasBanner.svelte.d.ts +5 -19
- package/dist/components/ConfigForm.svelte +555 -0
- package/dist/components/ConfigForm.svelte.d.ts +32 -0
- package/dist/components/ConfigModal.svelte +261 -0
- package/dist/components/ConfigModal.svelte.d.ts +31 -0
- package/dist/components/ConfigSidebar.svelte +934 -0
- package/dist/components/ConfigSidebar.svelte.d.ts +51 -0
- package/dist/components/ConnectionLine.svelte +32 -0
- package/dist/components/ConnectionLine.svelte.d.ts +3 -0
- package/dist/components/GatewayNode.svelte +471 -0
- package/dist/components/GatewayNode.svelte.d.ts +15 -0
- package/dist/components/LoadingSpinner.svelte +23 -23
- package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
- package/dist/components/Logo.svelte +82 -0
- package/dist/components/Logo.svelte.d.ts +26 -0
- package/dist/components/LogsSidebar.svelte +565 -0
- package/dist/components/LogsSidebar.svelte.d.ts +34 -0
- package/dist/components/MarkdownDisplay.svelte +28 -0
- package/dist/components/MarkdownDisplay.svelte.d.ts +7 -0
- package/dist/components/Navbar.svelte +663 -0
- package/dist/components/Navbar.svelte.d.ts +21 -0
- package/dist/components/NodeSidebar.svelte +629 -488
- package/dist/components/NodeSidebar.svelte.d.ts +1 -2
- package/dist/components/NodeStatusOverlay.svelte +327 -0
- package/dist/components/NodeStatusOverlay.svelte.d.ts +11 -0
- package/dist/components/NotesNode.svelte +566 -0
- package/dist/components/NotesNode.svelte.d.ts +43 -0
- package/dist/components/PipelineStatus.svelte +331 -0
- package/dist/components/PipelineStatus.svelte.d.ts +18 -0
- package/dist/components/SimpleNode.svelte +447 -0
- package/dist/components/SimpleNode.svelte.d.ts +24 -0
- package/dist/components/SquareNode.svelte +346 -0
- package/dist/components/SquareNode.svelte.d.ts +24 -0
- package/dist/components/StatusIcon.svelte +112 -0
- package/dist/components/StatusIcon.svelte.d.ts +10 -0
- package/dist/components/StatusLabel.svelte +33 -0
- package/dist/components/StatusLabel.svelte.d.ts +7 -0
- package/dist/components/ToolNode.svelte +385 -0
- package/dist/components/ToolNode.svelte.d.ts +36 -0
- package/dist/components/UniversalNode.svelte +126 -0
- package/dist/components/UniversalNode.svelte.d.ts +15 -0
- package/dist/components/WorkflowEditor.svelte +871 -528
- package/dist/components/WorkflowEditor.svelte.d.ts +15 -5
- package/dist/components/WorkflowNode.svelte +428 -542
- package/dist/components/WorkflowNode.svelte.d.ts +7 -3
- package/dist/config/apiConfig.d.ts +33 -0
- package/dist/config/apiConfig.js +39 -0
- package/dist/config/defaultPortConfig.d.ts +6 -0
- package/dist/config/defaultPortConfig.js +192 -0
- package/dist/config/demo.d.ts +58 -0
- package/dist/config/demo.js +142 -0
- package/dist/config/endpoints.d.ts +106 -0
- package/dist/config/endpoints.js +128 -0
- package/dist/data/samples.d.ts +38 -4
- package/dist/data/samples.js +2789 -737
- package/dist/examples/adapter-usage.d.ts +4 -4
- package/dist/examples/adapter-usage.js +21 -26
- package/dist/examples/api-client-usage.d.ts +6 -6
- package/dist/examples/api-client-usage.js +55 -54
- package/dist/index.d.ts +23 -15
- package/dist/index.js +23 -15
- package/dist/mocks/app-environment.d.ts +8 -0
- package/dist/mocks/app-environment.js +16 -0
- package/dist/mocks/app-forms.d.ts +2 -0
- package/dist/mocks/app-forms.js +21 -0
- package/dist/mocks/app-navigation.d.ts +5 -0
- package/dist/mocks/app-navigation.js +34 -0
- package/dist/mocks/app-stores.d.ts +14 -0
- package/dist/mocks/app-stores.js +26 -0
- package/dist/services/api.d.ts +13 -3
- package/dist/services/api.js +91 -36
- package/dist/services/globalSave.d.ts +20 -0
- package/dist/services/globalSave.js +165 -0
- package/dist/services/nodeExecutionService.d.ts +63 -0
- package/dist/services/nodeExecutionService.js +261 -0
- package/dist/services/portConfigApi.d.ts +14 -0
- package/dist/services/portConfigApi.js +69 -0
- package/dist/services/toastService.d.ts +147 -0
- package/dist/services/toastService.js +235 -0
- package/dist/services/workflowStorage.d.ts +2 -2
- package/dist/services/workflowStorage.js +10 -10
- package/dist/stores/workflowStore.d.ts +53 -0
- package/dist/stores/workflowStore.js +264 -0
- package/dist/styles/base.css +896 -363
- package/dist/svelte-app.d.ts +52 -5
- package/dist/svelte-app.js +128 -6
- package/dist/types/config.d.ts +291 -0
- package/dist/types/config.js +4 -0
- package/dist/types/index.d.ts +231 -19
- package/dist/types/index.js +1 -1
- package/dist/utils/colors.d.ts +67 -33
- package/dist/utils/colors.js +183 -118
- package/dist/utils/config.d.ts +41 -0
- package/dist/utils/config.js +248 -0
- package/dist/utils/connections.d.ts +40 -3
- package/dist/utils/connections.js +115 -44
- package/dist/utils/icons.d.ts +1 -1
- package/dist/utils/icons.js +71 -70
- package/dist/utils/nodeStatus.d.ts +53 -0
- package/dist/utils/nodeStatus.js +183 -0
- package/dist/utils/nodeTypes.d.ts +57 -0
- package/dist/utils/nodeTypes.js +109 -0
- package/dist/utils/nodeWrapper.d.ts +39 -0
- package/dist/utils/nodeWrapper.js +62 -0
- package/package.json +129 -97
- package/dist/components/Node.svelte +0 -38
- package/dist/components/Node.svelte.d.ts +0 -4
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Workflow } from '../types/index.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
workflow?: Workflow;
|
|
4
|
+
height?: string | number;
|
|
5
|
+
width?: string | number;
|
|
6
|
+
showNavbar?: boolean;
|
|
7
|
+
disableSidebar?: boolean;
|
|
8
|
+
lockWorkflow?: boolean;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
|
|
11
|
+
pipelineId?: string;
|
|
12
|
+
navbarTitle?: string;
|
|
13
|
+
navbarActions?: Array<{
|
|
14
|
+
label: string;
|
|
15
|
+
href: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
variant?: 'primary' | 'secondary' | 'outline';
|
|
18
|
+
onclick?: (event: Event) => void;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
declare const App: import("svelte").Component<Props, {}, "">;
|
|
2
22
|
type App = ReturnType<typeof App>;
|
|
3
23
|
export default App;
|
|
@@ -1,51 +1,57 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import Icon from '@iconify/svelte';
|
|
3
|
+
let {
|
|
4
|
+
title,
|
|
5
|
+
description,
|
|
6
|
+
iconName
|
|
7
|
+
}: {
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
iconName?: string;
|
|
11
|
+
} = $props();
|
|
6
12
|
</script>
|
|
7
13
|
|
|
8
14
|
<div class="flowdrop-canvas-banner">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<div class="flowdrop-card">
|
|
16
|
+
<div class="flowdrop-card__body flowdrop-text--center">
|
|
17
|
+
<div class="flowdrop-canvas-banner__icon">
|
|
18
|
+
{#if iconName}
|
|
19
|
+
<Icon icon={iconName} class="flowdrop-canvas-banner__icon-svg" />
|
|
20
|
+
{/if}
|
|
21
|
+
</div>
|
|
22
|
+
<h3 class="flowdrop-canvas-banner__title">{title}</h3>
|
|
23
|
+
<p class="flowdrop-canvas-banner__description">{description}</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
20
26
|
</div>
|
|
21
27
|
|
|
22
28
|
<style>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
</style>
|
|
29
|
+
.flowdrop-canvas-banner {
|
|
30
|
+
position: absolute;
|
|
31
|
+
inset: 0;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.flowdrop-canvas-banner__icon {
|
|
39
|
+
font-size: 3.75rem;
|
|
40
|
+
margin-bottom: 1rem;
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
align-items: center;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.flowdrop-canvas-banner__title {
|
|
47
|
+
font-size: 1.125rem;
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
margin-bottom: 0.5rem;
|
|
50
|
+
color: #111827;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.flowdrop-canvas-banner__description {
|
|
54
|
+
font-size: 0.875rem;
|
|
55
|
+
color: #6b7280;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
-
$$bindings?: Bindings;
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: Props & {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const CanvasBanner: $$__sveltets_2_IsomorphicComponent<{
|
|
1
|
+
type $$ComponentProps = {
|
|
15
2
|
title: string;
|
|
16
3
|
description: string;
|
|
17
|
-
iconName
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type CanvasBanner = InstanceType<typeof CanvasBanner>;
|
|
4
|
+
iconName?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const CanvasBanner: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type CanvasBanner = ReturnType<typeof CanvasBanner>;
|
|
22
8
|
export default CanvasBanner;
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
ConfigForm Component
|
|
3
|
+
Generates configuration forms from JSON Schema and manages user values
|
|
4
|
+
Separates schema (form structure) from values (user input)
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import type { ConfigSchema, ConfigProperty, ConfigValues } from '../types/index.js';
|
|
9
|
+
import { createEventDispatcher } from 'svelte';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
schema: ConfigSchema;
|
|
13
|
+
values: ConfigValues;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let props: Props = $props();
|
|
18
|
+
let dispatch = createEventDispatcher<{
|
|
19
|
+
change: { values: ConfigValues };
|
|
20
|
+
validate: { isValid: boolean; errors: string[] };
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
// Local copy of values for editing
|
|
24
|
+
let localValues = $state<ConfigValues>({ ...props.values });
|
|
25
|
+
|
|
26
|
+
// Validation errors
|
|
27
|
+
let validationErrors = $state<Record<string, string>>({});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate a single field against its schema
|
|
31
|
+
*/
|
|
32
|
+
function validateField(
|
|
33
|
+
fieldName: string,
|
|
34
|
+
value: unknown,
|
|
35
|
+
property: ConfigProperty
|
|
36
|
+
): string | null {
|
|
37
|
+
// Check if schema exists
|
|
38
|
+
if (!props.schema) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Required field validation
|
|
43
|
+
if (
|
|
44
|
+
props.schema.required?.includes(fieldName) &&
|
|
45
|
+
(value === null || value === undefined || value === '')
|
|
46
|
+
) {
|
|
47
|
+
return `${property.title || fieldName} is required`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Type validation
|
|
51
|
+
if (value !== null && value !== undefined) {
|
|
52
|
+
switch (property.type) {
|
|
53
|
+
case 'string':
|
|
54
|
+
if (typeof value !== 'string') {
|
|
55
|
+
return `${property.title || fieldName} must be a string`;
|
|
56
|
+
}
|
|
57
|
+
if (property.minLength && value.length < property.minLength) {
|
|
58
|
+
return `${property.title || fieldName} must be at least ${property.minLength} characters`;
|
|
59
|
+
}
|
|
60
|
+
if (property.maxLength && value.length > property.maxLength) {
|
|
61
|
+
return `${property.title || fieldName} must be at most ${property.maxLength} characters`;
|
|
62
|
+
}
|
|
63
|
+
if (property.pattern && !new RegExp(property.pattern).test(value)) {
|
|
64
|
+
return `${property.title || fieldName} format is invalid`;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'number':
|
|
69
|
+
if (typeof value !== 'number') {
|
|
70
|
+
return `${property.title || fieldName} must be a number`;
|
|
71
|
+
}
|
|
72
|
+
if (property.minimum !== undefined && value < property.minimum) {
|
|
73
|
+
return `${property.title || fieldName} must be at least ${property.minimum}`;
|
|
74
|
+
}
|
|
75
|
+
if (property.maximum !== undefined && value > property.maximum) {
|
|
76
|
+
return `${property.title || fieldName} must be at most ${property.maximum}`;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case 'boolean':
|
|
81
|
+
if (typeof value !== 'boolean') {
|
|
82
|
+
return `${property.title || fieldName} must be a boolean`;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'array':
|
|
87
|
+
if (!Array.isArray(value)) {
|
|
88
|
+
return `${property.title || fieldName} must be an array`;
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate all fields
|
|
99
|
+
*/
|
|
100
|
+
function validateForm(): { isValid: boolean; errors: string[] } {
|
|
101
|
+
const errors: string[] = [];
|
|
102
|
+
const newValidationErrors: Record<string, string> = {};
|
|
103
|
+
|
|
104
|
+
// Check if schema and properties exist
|
|
105
|
+
if (!props.schema || !props.schema.properties || typeof props.schema.properties !== 'object') {
|
|
106
|
+
console.warn('ConfigForm: Invalid schema or properties:', {
|
|
107
|
+
schema: props.schema,
|
|
108
|
+
properties: props.schema?.properties
|
|
109
|
+
});
|
|
110
|
+
return { isValid: true, errors: [] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Object.entries(props.schema.properties).forEach(([fieldName, property]) => {
|
|
114
|
+
const value = localValues[fieldName];
|
|
115
|
+
const error = validateField(fieldName, value, property);
|
|
116
|
+
|
|
117
|
+
if (error) {
|
|
118
|
+
errors.push(error);
|
|
119
|
+
newValidationErrors[fieldName] = error;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
validationErrors = newValidationErrors;
|
|
124
|
+
|
|
125
|
+
const isValid = errors.length === 0;
|
|
126
|
+
dispatch('validate', { isValid, errors });
|
|
127
|
+
|
|
128
|
+
return { isValid, errors };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Handle field value change
|
|
133
|
+
* Preserves hidden field values from the original configuration
|
|
134
|
+
*/
|
|
135
|
+
function handleFieldChange(fieldName: string, value: unknown): void {
|
|
136
|
+
localValues[fieldName] = value;
|
|
137
|
+
|
|
138
|
+
// Validate the changed field
|
|
139
|
+
if (props.schema && props.schema.properties && props.schema.properties[fieldName]) {
|
|
140
|
+
const property = props.schema.properties[fieldName];
|
|
141
|
+
const error = validateField(fieldName, value, property);
|
|
142
|
+
|
|
143
|
+
if (error) {
|
|
144
|
+
validationErrors[fieldName] = error;
|
|
145
|
+
} else {
|
|
146
|
+
delete validationErrors[fieldName];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Merge hidden field values from original props to ensure they're preserved
|
|
151
|
+
const hiddenFieldValues: ConfigValues = {};
|
|
152
|
+
if (props.schema?.properties) {
|
|
153
|
+
Object.entries(props.schema.properties).forEach(([key, property]) => {
|
|
154
|
+
if (property.format === 'hidden' && key in props.values) {
|
|
155
|
+
hiddenFieldValues[key] = props.values[key];
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Emit change event with hidden fields preserved
|
|
161
|
+
dispatch('change', { values: { ...localValues, ...hiddenFieldValues } });
|
|
162
|
+
|
|
163
|
+
// Validate entire form
|
|
164
|
+
validateForm();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get default value for a field
|
|
169
|
+
*/
|
|
170
|
+
function getDefaultValue(property: ConfigProperty): unknown {
|
|
171
|
+
if (property.default !== undefined) {
|
|
172
|
+
return property.default;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
switch (property.type) {
|
|
176
|
+
case 'string':
|
|
177
|
+
// If enum with multiple selection, default to empty array
|
|
178
|
+
if (property.enum && property.multiple) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
case 'number':
|
|
183
|
+
return 0;
|
|
184
|
+
case 'boolean':
|
|
185
|
+
return false;
|
|
186
|
+
case 'array':
|
|
187
|
+
return [];
|
|
188
|
+
case 'object':
|
|
189
|
+
return {};
|
|
190
|
+
default:
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Sync local values with props and initialize with defaults
|
|
196
|
+
$effect(() => {
|
|
197
|
+
// Update local values when props change
|
|
198
|
+
localValues = { ...props.values };
|
|
199
|
+
|
|
200
|
+
if (props.schema) {
|
|
201
|
+
Object.entries(props.schema.properties).forEach(([fieldName, property]) => {
|
|
202
|
+
if (localValues[fieldName] === undefined) {
|
|
203
|
+
localValues[fieldName] = getDefaultValue(property);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Validate on initialization
|
|
208
|
+
validateForm();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<div class="flowdrop-config-form">
|
|
214
|
+
{#if props.schema && props.schema.properties && typeof props.schema.properties === 'object'}
|
|
215
|
+
<form class="flowdrop-form" onsubmit={(e) => e.preventDefault()}>
|
|
216
|
+
{#each Object.entries(props.schema.properties) as [fieldName, property] (fieldName)}
|
|
217
|
+
{#if property.format === 'hidden'}
|
|
218
|
+
<!-- Hidden field to preserve value -->
|
|
219
|
+
<input
|
|
220
|
+
type="hidden"
|
|
221
|
+
id={fieldName}
|
|
222
|
+
value={typeof (localValues[fieldName] ?? property.default) === 'object'
|
|
223
|
+
? JSON.stringify(localValues[fieldName] ?? property.default ?? {})
|
|
224
|
+
: (localValues[fieldName] ?? property.default ?? '')}
|
|
225
|
+
/>
|
|
226
|
+
{:else}
|
|
227
|
+
<div class="flowdrop-form-field">
|
|
228
|
+
<label class="flowdrop-form-label" for={fieldName}>
|
|
229
|
+
{property.title || fieldName}
|
|
230
|
+
{#if props.schema.required?.includes(fieldName)}
|
|
231
|
+
<span class="flowdrop-form-required">*</span>
|
|
232
|
+
{/if}
|
|
233
|
+
</label>
|
|
234
|
+
|
|
235
|
+
{#if property.type === 'string'}
|
|
236
|
+
{#if property.enum && property.multiple}
|
|
237
|
+
<!-- Checkboxes for enum with multiple selection -->
|
|
238
|
+
<div class="flowdrop-form-checkbox-group">
|
|
239
|
+
{#each property.enum as option (option)}
|
|
240
|
+
<label class="flowdrop-form-checkbox">
|
|
241
|
+
<input
|
|
242
|
+
type="checkbox"
|
|
243
|
+
class="flowdrop-form-checkbox__input"
|
|
244
|
+
value={option}
|
|
245
|
+
checked={Array.isArray(localValues[fieldName]) &&
|
|
246
|
+
localValues[fieldName].includes(option)}
|
|
247
|
+
disabled={props.disabled || false}
|
|
248
|
+
onchange={(e) => {
|
|
249
|
+
const checked = (e.target as HTMLInputElement).checked;
|
|
250
|
+
const currentValues = Array.isArray(localValues[fieldName])
|
|
251
|
+
? [...localValues[fieldName]]
|
|
252
|
+
: [];
|
|
253
|
+
if (checked) {
|
|
254
|
+
if (!currentValues.includes(option)) {
|
|
255
|
+
handleFieldChange(fieldName, [...currentValues, option]);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
handleFieldChange(
|
|
259
|
+
fieldName,
|
|
260
|
+
currentValues.filter((v) => v !== option)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
<span class="flowdrop-form-checkbox__label">{option}</span>
|
|
266
|
+
</label>
|
|
267
|
+
{/each}
|
|
268
|
+
</div>
|
|
269
|
+
{:else if property.enum}
|
|
270
|
+
<!-- Select field for enum with single selection -->
|
|
271
|
+
<select
|
|
272
|
+
id={fieldName}
|
|
273
|
+
class="flowdrop-form-select {validationErrors[fieldName]
|
|
274
|
+
? 'flowdrop-form-select--error'
|
|
275
|
+
: ''}"
|
|
276
|
+
disabled={props.disabled || false}
|
|
277
|
+
onchange={(e) =>
|
|
278
|
+
handleFieldChange(fieldName, (e.target as HTMLSelectElement).value)}
|
|
279
|
+
>
|
|
280
|
+
{#each property.enum as option (option)}
|
|
281
|
+
<option value={option} selected={localValues[fieldName] === option}>
|
|
282
|
+
{option}
|
|
283
|
+
</option>
|
|
284
|
+
{/each}
|
|
285
|
+
</select>
|
|
286
|
+
{:else if property.format === 'multiline' || (property.maxLength && property.maxLength > 100)}
|
|
287
|
+
<!-- Textarea for multiline or long text -->
|
|
288
|
+
<textarea
|
|
289
|
+
id={fieldName}
|
|
290
|
+
class="flowdrop-form-textarea {validationErrors[fieldName]
|
|
291
|
+
? 'flowdrop-form-textarea--error'
|
|
292
|
+
: ''}"
|
|
293
|
+
placeholder={property.description || ''}
|
|
294
|
+
rows="4"
|
|
295
|
+
disabled={props.disabled || false}
|
|
296
|
+
onchange={(e) =>
|
|
297
|
+
handleFieldChange(fieldName, (e.target as HTMLTextAreaElement).value)}
|
|
298
|
+
>{localValues[fieldName] || ''}</textarea
|
|
299
|
+
>
|
|
300
|
+
{:else}
|
|
301
|
+
<!-- Regular text input -->
|
|
302
|
+
<input
|
|
303
|
+
id={fieldName}
|
|
304
|
+
type="text"
|
|
305
|
+
class="flowdrop-form-input {validationErrors[fieldName]
|
|
306
|
+
? 'flowdrop-form-input--error'
|
|
307
|
+
: ''}"
|
|
308
|
+
value={localValues[fieldName] || ''}
|
|
309
|
+
placeholder={property.description || ''}
|
|
310
|
+
disabled={props.disabled || false}
|
|
311
|
+
onchange={(e) =>
|
|
312
|
+
handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
|
|
313
|
+
/>
|
|
314
|
+
{/if}
|
|
315
|
+
{:else if property.type === 'number'}
|
|
316
|
+
<!-- Number input as text field -->
|
|
317
|
+
<input
|
|
318
|
+
id={fieldName}
|
|
319
|
+
type="text"
|
|
320
|
+
class="flowdrop-form-input {validationErrors[fieldName]
|
|
321
|
+
? 'flowdrop-form-input--error'
|
|
322
|
+
: ''}"
|
|
323
|
+
value={localValues[fieldName] || ''}
|
|
324
|
+
placeholder="Enter a number"
|
|
325
|
+
disabled={props.disabled || false}
|
|
326
|
+
oninput={(e) => {
|
|
327
|
+
const value = (e.target as HTMLInputElement).value;
|
|
328
|
+
const numValue = value === '' ? 0 : parseFloat(value);
|
|
329
|
+
if (!isNaN(numValue)) {
|
|
330
|
+
handleFieldChange(fieldName, numValue);
|
|
331
|
+
}
|
|
332
|
+
}}
|
|
333
|
+
onblur={(e) => {
|
|
334
|
+
const value = (e.target as HTMLInputElement).value;
|
|
335
|
+
const numValue = value === '' ? 0 : parseFloat(value);
|
|
336
|
+
if (!isNaN(numValue)) {
|
|
337
|
+
handleFieldChange(fieldName, numValue);
|
|
338
|
+
}
|
|
339
|
+
}}
|
|
340
|
+
/>
|
|
341
|
+
{:else if property.type === 'boolean'}
|
|
342
|
+
<!-- Checkbox -->
|
|
343
|
+
<label class="flowdrop-form-checkbox">
|
|
344
|
+
<input
|
|
345
|
+
id={fieldName}
|
|
346
|
+
type="checkbox"
|
|
347
|
+
class="flowdrop-form-checkbox__input"
|
|
348
|
+
checked={Boolean(localValues[fieldName])}
|
|
349
|
+
disabled={props.disabled || false}
|
|
350
|
+
onchange={(e) =>
|
|
351
|
+
handleFieldChange(fieldName, (e.target as HTMLInputElement).checked)}
|
|
352
|
+
/>
|
|
353
|
+
<span class="flowdrop-form-checkbox__label">
|
|
354
|
+
{property.description || ''}
|
|
355
|
+
</span>
|
|
356
|
+
</label>
|
|
357
|
+
{:else if property.type === 'array'}
|
|
358
|
+
<!-- Array input (comma-separated) -->
|
|
359
|
+
<textarea
|
|
360
|
+
id={fieldName}
|
|
361
|
+
class="flowdrop-form-textarea {validationErrors[fieldName]
|
|
362
|
+
? 'flowdrop-form-textarea--error'
|
|
363
|
+
: ''}"
|
|
364
|
+
placeholder="Enter values separated by commas"
|
|
365
|
+
rows="3"
|
|
366
|
+
disabled={props.disabled || false}
|
|
367
|
+
onchange={(e) =>
|
|
368
|
+
handleFieldChange(
|
|
369
|
+
fieldName,
|
|
370
|
+
(e.target as HTMLTextAreaElement).value
|
|
371
|
+
.split(',')
|
|
372
|
+
.map((v) => v.trim())
|
|
373
|
+
.filter((v) => v)
|
|
374
|
+
)}
|
|
375
|
+
>{Array.isArray(localValues[fieldName])
|
|
376
|
+
? localValues[fieldName].join(', ')
|
|
377
|
+
: ''}</textarea
|
|
378
|
+
>
|
|
379
|
+
{:else if property.type === 'object'}
|
|
380
|
+
<!-- JSON object input -->
|
|
381
|
+
<textarea
|
|
382
|
+
id={fieldName}
|
|
383
|
+
class="flowdrop-form-textarea {validationErrors[fieldName]
|
|
384
|
+
? 'flowdrop-form-textarea--error'
|
|
385
|
+
: ''}"
|
|
386
|
+
placeholder="Enter JSON object"
|
|
387
|
+
rows="4"
|
|
388
|
+
disabled={props.disabled || false}
|
|
389
|
+
onchange={(e) => {
|
|
390
|
+
try {
|
|
391
|
+
handleFieldChange(
|
|
392
|
+
fieldName,
|
|
393
|
+
JSON.parse((e.target as HTMLTextAreaElement).value)
|
|
394
|
+
);
|
|
395
|
+
} catch {
|
|
396
|
+
// Handle JSON parse error
|
|
397
|
+
}
|
|
398
|
+
}}
|
|
399
|
+
>{typeof localValues[fieldName] === 'object'
|
|
400
|
+
? JSON.stringify(localValues[fieldName], null, 2)
|
|
401
|
+
: ''}</textarea
|
|
402
|
+
>
|
|
403
|
+
{:else}
|
|
404
|
+
<!-- Default text input -->
|
|
405
|
+
<input
|
|
406
|
+
id={fieldName}
|
|
407
|
+
type="text"
|
|
408
|
+
class="flowdrop-form-input {validationErrors[fieldName]
|
|
409
|
+
? 'flowdrop-form-input--error'
|
|
410
|
+
: ''}"
|
|
411
|
+
value={localValues[fieldName] || ''}
|
|
412
|
+
placeholder={property.description || ''}
|
|
413
|
+
disabled={props.disabled || false}
|
|
414
|
+
onchange={(e) => handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
|
|
415
|
+
/>
|
|
416
|
+
{/if}
|
|
417
|
+
|
|
418
|
+
{#if validationErrors[fieldName]}
|
|
419
|
+
<div class="flowdrop-form-error">{validationErrors[fieldName]}</div>
|
|
420
|
+
{/if}
|
|
421
|
+
|
|
422
|
+
{#if property.description}
|
|
423
|
+
<div class="flowdrop-form-help">{property.description}</div>
|
|
424
|
+
{/if}
|
|
425
|
+
</div>
|
|
426
|
+
{/if}
|
|
427
|
+
{/each}
|
|
428
|
+
</form>
|
|
429
|
+
{:else}
|
|
430
|
+
<div class="flowdrop-form-empty">
|
|
431
|
+
<p class="flowdrop-text--sm flowdrop-text--gray">
|
|
432
|
+
No configuration schema available for this node.
|
|
433
|
+
</p>
|
|
434
|
+
</div>
|
|
435
|
+
{/if}
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<style>
|
|
439
|
+
.flowdrop-config-form {
|
|
440
|
+
padding: 1rem;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.flowdrop-form {
|
|
444
|
+
display: flex;
|
|
445
|
+
flex-direction: column;
|
|
446
|
+
gap: 1rem;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.flowdrop-form-field {
|
|
450
|
+
display: flex;
|
|
451
|
+
flex-direction: column;
|
|
452
|
+
gap: 0.5rem;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.flowdrop-form-label {
|
|
456
|
+
font-size: 0.875rem;
|
|
457
|
+
font-weight: 500;
|
|
458
|
+
color: #374151;
|
|
459
|
+
display: flex;
|
|
460
|
+
align-items: center;
|
|
461
|
+
gap: 0.25rem;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.flowdrop-form-required {
|
|
465
|
+
color: #dc2626;
|
|
466
|
+
font-weight: 700;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.flowdrop-form-input,
|
|
470
|
+
.flowdrop-form-textarea,
|
|
471
|
+
.flowdrop-form-select {
|
|
472
|
+
padding: 0.5rem 0.75rem;
|
|
473
|
+
border: 1px solid #d1d5db;
|
|
474
|
+
border-radius: 0.375rem;
|
|
475
|
+
font-size: 0.875rem;
|
|
476
|
+
background-color: #ffffff;
|
|
477
|
+
transition: all 0.2s ease-in-out;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.flowdrop-form-input:focus,
|
|
481
|
+
.flowdrop-form-textarea:focus,
|
|
482
|
+
.flowdrop-form-select:focus {
|
|
483
|
+
outline: none;
|
|
484
|
+
border-color: #3b82f6;
|
|
485
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.flowdrop-form-input--error,
|
|
489
|
+
.flowdrop-form-textarea--error,
|
|
490
|
+
.flowdrop-form-select--error {
|
|
491
|
+
border-color: #dc2626;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.flowdrop-form-input--error:focus,
|
|
495
|
+
.flowdrop-form-textarea--error:focus,
|
|
496
|
+
.flowdrop-form-select--error:focus {
|
|
497
|
+
border-color: #dc2626;
|
|
498
|
+
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.flowdrop-form-textarea {
|
|
502
|
+
resize: vertical;
|
|
503
|
+
min-height: 4rem;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.flowdrop-form-checkbox-group {
|
|
507
|
+
display: flex;
|
|
508
|
+
flex-direction: column;
|
|
509
|
+
gap: 0.5rem;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.flowdrop-form-checkbox {
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
gap: 0.5rem;
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.flowdrop-form-checkbox__input {
|
|
520
|
+
width: 1rem;
|
|
521
|
+
height: 1rem;
|
|
522
|
+
accent-color: #3b82f6;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.flowdrop-form-checkbox__label {
|
|
526
|
+
font-size: 0.875rem;
|
|
527
|
+
color: #374151;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.flowdrop-form-error {
|
|
531
|
+
font-size: 0.75rem;
|
|
532
|
+
color: #dc2626;
|
|
533
|
+
margin-top: 0.25rem;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.flowdrop-form-help {
|
|
537
|
+
font-size: 0.75rem;
|
|
538
|
+
color: #6b7280;
|
|
539
|
+
margin-top: 0.25rem;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.flowdrop-form-empty {
|
|
543
|
+
text-align: center;
|
|
544
|
+
padding: 2rem;
|
|
545
|
+
color: #6b7280;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.flowdrop-form-input:disabled,
|
|
549
|
+
.flowdrop-form-textarea:disabled,
|
|
550
|
+
.flowdrop-form-select:disabled {
|
|
551
|
+
background-color: #f9fafb;
|
|
552
|
+
color: #9ca3af;
|
|
553
|
+
cursor: not-allowed;
|
|
554
|
+
}
|
|
555
|
+
</style>
|