@d34dman/flowdrop 0.0.1
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 +293 -0
- package/dist/adapters/WorkflowAdapter.d.ts +166 -0
- package/dist/adapters/WorkflowAdapter.js +337 -0
- package/dist/api/client.d.ts +79 -0
- package/dist/api/client.js +208 -0
- package/dist/app.css +0 -0
- package/dist/clients/ApiClient.d.ts +203 -0
- package/dist/clients/ApiClient.js +212 -0
- package/dist/components/App.svelte +237 -0
- package/dist/components/App.svelte.d.ts +3 -0
- package/dist/components/CanvasBanner.svelte +51 -0
- package/dist/components/CanvasBanner.svelte.d.ts +22 -0
- package/dist/components/LoadingSpinner.svelte +36 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +8 -0
- package/dist/components/Node.svelte +38 -0
- package/dist/components/Node.svelte.d.ts +4 -0
- package/dist/components/NodeSidebar.svelte +500 -0
- package/dist/components/NodeSidebar.svelte.d.ts +9 -0
- package/dist/components/WorkflowEditor.svelte +542 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +10 -0
- package/dist/components/WorkflowNode.svelte +558 -0
- package/dist/components/WorkflowNode.svelte.d.ts +11 -0
- package/dist/data/samples.d.ts +17 -0
- package/dist/data/samples.js +1193 -0
- package/dist/examples/adapter-usage.d.ts +66 -0
- package/dist/examples/adapter-usage.js +138 -0
- package/dist/examples/api-client-usage.d.ts +31 -0
- package/dist/examples/api-client-usage.js +241 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +27 -0
- package/dist/services/api.d.ts +110 -0
- package/dist/services/api.js +149 -0
- package/dist/services/workflowStorage.d.ts +37 -0
- package/dist/services/workflowStorage.js +116 -0
- package/dist/styles/base.css +858 -0
- package/dist/svelte-app.d.ts +17 -0
- package/dist/svelte-app.js +30 -0
- package/dist/types/index.d.ts +179 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +121 -0
- package/dist/utils/colors.js +240 -0
- package/dist/utils/connections.d.ts +47 -0
- package/dist/utils/connections.js +240 -0
- package/dist/utils/icons.d.ts +102 -0
- package/dist/utils/icons.js +149 -0
- package/package.json +99 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
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<{
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
iconName: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
}, {}, {}, string>;
|
|
21
|
+
type CanvasBanner = InstanceType<typeof CanvasBanner>;
|
|
22
|
+
export default CanvasBanner;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Loading Spinner Component
|
|
3
|
+
Reusable loading indicator with customizable size and text
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
interface Props {
|
|
8
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
9
|
+
text?: string;
|
|
10
|
+
showText?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let props: Props = $props();
|
|
14
|
+
|
|
15
|
+
// Default values
|
|
16
|
+
let size = $derived(props.size || "md");
|
|
17
|
+
let text = $derived(props.text || "Loading...");
|
|
18
|
+
let showText = $derived(props.showText !== false);
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div class="flowdrop-loading">
|
|
22
|
+
<div class="flowdrop-spinner flowdrop-spinner--{size}"></div>
|
|
23
|
+
{#if showText}
|
|
24
|
+
<p class="flowdrop-text--xs flowdrop-text--gray">{text}</p>
|
|
25
|
+
{/if}
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
.flowdrop-loading {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
gap: 0.5rem;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
|
|
4
|
+
|
|
5
|
+
let { positionAbsoluteX, positionAbsoluteY }: NodeProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="card">
|
|
9
|
+
<Handle type="target" position={Position.Right} />
|
|
10
|
+
<div>
|
|
11
|
+
Custom Node
|
|
12
|
+
<div>
|
|
13
|
+
<strong>position: {~~positionAbsoluteX},{~~positionAbsoluteY}</strong>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<Handle type="source" position={Position.Left} id="a" style="left: 10px;" />
|
|
17
|
+
<Handle type="source" position={Position.Left} id="b" style="left: auto; right: 10px;" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
<div class="card bg-base-100 w-96 shadow-sm">
|
|
22
|
+
<figure>
|
|
23
|
+
<img
|
|
24
|
+
src="https://img.daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.webp"
|
|
25
|
+
alt="Shoes" />
|
|
26
|
+
</figure>
|
|
27
|
+
<div class="card-body">
|
|
28
|
+
<h2 class="card-title">
|
|
29
|
+
Card Title
|
|
30
|
+
<div class="badge badge-secondary">NEW</div>
|
|
31
|
+
</h2>
|
|
32
|
+
<p>A card component has a figure, a body part, and inside body there are title and actions parts</p>
|
|
33
|
+
<div class="card-actions justify-end">
|
|
34
|
+
<div class="badge badge-outline">Fashion</div>
|
|
35
|
+
<div class="badge badge-outline">Products</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Node Sidebar Component
|
|
3
|
+
Displays available node types organized by category with accordion-style groups
|
|
4
|
+
Styled with BEM syntax
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import type { NodeMetadata, NodeCategory } from "../types/index.js";
|
|
9
|
+
import LoadingSpinner from "./LoadingSpinner.svelte";
|
|
10
|
+
import Icon from "@iconify/svelte";
|
|
11
|
+
import { getNodeIcon, getCategoryIcon, getDefaultIcon } from "../utils/icons.js";
|
|
12
|
+
import { getCategoryColorToken } from "../utils/colors.js";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
nodes: NodeMetadata[];
|
|
16
|
+
selectedCategory?: NodeCategory;
|
|
17
|
+
searchQuery?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let props: Props = $props();
|
|
21
|
+
let searchInput = $state("");
|
|
22
|
+
let selectedCategory = $state(props.selectedCategory || "all");
|
|
23
|
+
|
|
24
|
+
let filteredNodes = $derived(getFilteredNodes());
|
|
25
|
+
let categories = $derived(getCategories());
|
|
26
|
+
|
|
27
|
+
// Debug logging
|
|
28
|
+
$effect(() => {
|
|
29
|
+
console.log('NodeSidebar: props.nodes received:', props.nodes?.length || 0, 'nodes');
|
|
30
|
+
console.log('NodeSidebar: props.nodes content:', props.nodes);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get all unique categories from node types
|
|
35
|
+
*/
|
|
36
|
+
function getCategories(): NodeCategory[] {
|
|
37
|
+
const nodes = props.nodes || [];
|
|
38
|
+
if (nodes.length === 0) return [];
|
|
39
|
+
const categories = new Set<NodeCategory>();
|
|
40
|
+
nodes.forEach(node => categories.add(node.category));
|
|
41
|
+
return Array.from(categories).sort();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Filter node types based on search query and selected category
|
|
46
|
+
*/
|
|
47
|
+
function getFilteredNodes(): NodeMetadata[] {
|
|
48
|
+
// Use actual node types from props
|
|
49
|
+
let filtered = props.nodes || [];
|
|
50
|
+
|
|
51
|
+
// Filter by category
|
|
52
|
+
if (selectedCategory !== "all") {
|
|
53
|
+
filtered = filtered.filter(node => node.category === selectedCategory);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Filter by search query
|
|
57
|
+
if (searchInput.trim()) {
|
|
58
|
+
const query = searchInput.toLowerCase();
|
|
59
|
+
filtered = filtered.filter(node =>
|
|
60
|
+
node.name.toLowerCase().includes(query) ||
|
|
61
|
+
node.description.toLowerCase().includes(query) ||
|
|
62
|
+
node.tags?.some(tag => tag.toLowerCase().includes(query))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create a new array and sort it to avoid mutating the original
|
|
67
|
+
return [...filtered].sort((a, b) => a.name.localeCompare(b.name));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle node type drag start - creates a new node instance
|
|
72
|
+
*/
|
|
73
|
+
function handleNodeDragStart(event: DragEvent, nodeType: NodeMetadata): void {
|
|
74
|
+
if (!event.dataTransfer) return;
|
|
75
|
+
|
|
76
|
+
// Create a new node instance from the node type
|
|
77
|
+
const newNodeData = {
|
|
78
|
+
type: "node",
|
|
79
|
+
nodeType: nodeType.id,
|
|
80
|
+
nodeData: {
|
|
81
|
+
label: nodeType.name,
|
|
82
|
+
config: { ...nodeType.configSchema },
|
|
83
|
+
metadata: nodeType
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const jsonData = JSON.stringify(newNodeData);
|
|
88
|
+
|
|
89
|
+
// Set the data that SvelteFlow will receive
|
|
90
|
+
event.dataTransfer.setData("application/json", jsonData);
|
|
91
|
+
event.dataTransfer.setData("text/plain", nodeType.name);
|
|
92
|
+
event.dataTransfer.effectAllowed = "copy";
|
|
93
|
+
|
|
94
|
+
// Set drag image
|
|
95
|
+
if (event.target) {
|
|
96
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
97
|
+
event.dataTransfer.setDragImage(event.target as HTMLElement, rect.width / 2, rect.height / 2);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Handle category selection
|
|
103
|
+
*/
|
|
104
|
+
function handleCategorySelect(category: NodeCategory | "all"): void {
|
|
105
|
+
selectedCategory = category;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handle search input change
|
|
110
|
+
*/
|
|
111
|
+
function handleSearchChange(): void {
|
|
112
|
+
// Search is handled reactively through the derived filteredNodes
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle node type click
|
|
117
|
+
*/
|
|
118
|
+
function handleNodeClick(nodeType: NodeMetadata): void {
|
|
119
|
+
// Node type selection is handled through events
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get category display name
|
|
124
|
+
*/
|
|
125
|
+
function getCategoryDisplayName(category: NodeCategory): string {
|
|
126
|
+
const names: Record<NodeCategory, string> = {
|
|
127
|
+
"inputs": "Inputs",
|
|
128
|
+
"outputs": "Outputs",
|
|
129
|
+
"prompts": "Prompts",
|
|
130
|
+
"models": "Models",
|
|
131
|
+
"processing": "Processing",
|
|
132
|
+
"logic": "Logic",
|
|
133
|
+
"data": "Data",
|
|
134
|
+
"helpers": "Helpers",
|
|
135
|
+
"tools": "Tools",
|
|
136
|
+
"vector stores": "Vector Stores",
|
|
137
|
+
"embeddings": "Embeddings",
|
|
138
|
+
"memories": "Memories",
|
|
139
|
+
"agents": "Agents",
|
|
140
|
+
"bundles": "Bundles"
|
|
141
|
+
};
|
|
142
|
+
return names[category] || category;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get node type count for category
|
|
147
|
+
*/
|
|
148
|
+
function getNodeCount(category: NodeCategory): number {
|
|
149
|
+
const nodes = props.nodes || [];
|
|
150
|
+
return nodes.filter(node => node.category === category).length;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get node types for category
|
|
155
|
+
*/
|
|
156
|
+
function getNodesForCategory(category: NodeCategory): NodeMetadata[] {
|
|
157
|
+
const nodes = props.nodes || [];
|
|
158
|
+
return [...nodes]
|
|
159
|
+
.filter(node => node.category === category)
|
|
160
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get filtered nodes for category
|
|
165
|
+
*/
|
|
166
|
+
function getFilteredNodesForCategory(category: NodeCategory): NodeMetadata[] {
|
|
167
|
+
let nodes = getNodesForCategory(category);
|
|
168
|
+
|
|
169
|
+
// Filter by search query
|
|
170
|
+
if (searchInput.trim()) {
|
|
171
|
+
const query = searchInput.toLowerCase();
|
|
172
|
+
nodes = nodes.filter(node =>
|
|
173
|
+
node.name.toLowerCase().includes(query) ||
|
|
174
|
+
node.description.toLowerCase().includes(query) ||
|
|
175
|
+
node.tags?.some(tag => tag.toLowerCase().includes(query))
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return nodes;
|
|
180
|
+
}
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<div class="flowdrop-sidebar">
|
|
184
|
+
<!-- Header -->
|
|
185
|
+
<div class="flowdrop-sidebar__header">
|
|
186
|
+
<div class="flowdrop-sidebar__title">
|
|
187
|
+
<h2 class="flowdrop-text--lg flowdrop-font--bold">Components</h2>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<!-- Search Section -->
|
|
192
|
+
<div class="flowdrop-sidebar__search">
|
|
193
|
+
<div class="flowdrop-join flowdrop-w--full">
|
|
194
|
+
<div class="flowdrop-join__item flowdrop-flex--1">
|
|
195
|
+
<input
|
|
196
|
+
type="text"
|
|
197
|
+
placeholder="Search components..."
|
|
198
|
+
class="flowdrop-input flowdrop-join__item flowdrop-w--full"
|
|
199
|
+
bind:value={searchInput}
|
|
200
|
+
oninput={handleSearchChange}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
<button class="flowdrop-btn flowdrop-join__item" aria-label="Search components">
|
|
204
|
+
<svg class="flowdrop-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
205
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
206
|
+
</svg>
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Node Types List -->
|
|
212
|
+
<div class="flowdrop-sidebar__content">
|
|
213
|
+
{#if props.nodes?.length === 0}
|
|
214
|
+
<!-- No node types available -->
|
|
215
|
+
<div class="flowdrop-hero">
|
|
216
|
+
<div class="flowdrop-hero__content">
|
|
217
|
+
<div class="flowdrop-hero__icon">📦</div>
|
|
218
|
+
<h3 class="flowdrop-hero__title">No node types available</h3>
|
|
219
|
+
<p class="flowdrop-hero__description">Node type definitions will appear here</p>
|
|
220
|
+
<div class="flowdrop-mb--4">
|
|
221
|
+
<LoadingSpinner size="md" text="Loading from server..." />
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
{:else if searchInput.trim()}
|
|
226
|
+
<!-- Search Results -->
|
|
227
|
+
<div class="flowdrop-p--4">
|
|
228
|
+
<div class="flowdrop-divider">
|
|
229
|
+
<h3 class="flowdrop-divider__text">Search Results</h3>
|
|
230
|
+
</div>
|
|
231
|
+
{#if filteredNodes.length === 0}
|
|
232
|
+
<div class="flowdrop-hero">
|
|
233
|
+
<div class="flowdrop-hero__content">
|
|
234
|
+
<div class="flowdrop-hero__icon">🔍</div>
|
|
235
|
+
<h3 class="flowdrop-hero__title">No components found</h3>
|
|
236
|
+
<p class="flowdrop-hero__description">Try adjusting your search</p>
|
|
237
|
+
{#if props.nodes?.length === 0}
|
|
238
|
+
<div class="flowdrop-mb--4">
|
|
239
|
+
<LoadingSpinner size="sm" text="Loading components..." />
|
|
240
|
+
</div>
|
|
241
|
+
{/if}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
{:else}
|
|
245
|
+
<div class="flowdrop-node-list">
|
|
246
|
+
{#each filteredNodes as nodeType (nodeType.id)}
|
|
247
|
+
<div
|
|
248
|
+
class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
|
|
249
|
+
draggable="true"
|
|
250
|
+
ondragstart={(e) => handleNodeDragStart(e, nodeType)}
|
|
251
|
+
onclick={() => handleNodeClick(nodeType)}
|
|
252
|
+
role="button"
|
|
253
|
+
tabindex="0"
|
|
254
|
+
onkeydown={(e) => {
|
|
255
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
handleNodeClick(nodeType);
|
|
258
|
+
}
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
<div class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1">
|
|
262
|
+
<div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
|
|
263
|
+
<!-- Node Type Icon -->
|
|
264
|
+
<div class="flowdrop-node-icon" style="background-color: {getCategoryColorToken(nodeType.category)}">
|
|
265
|
+
<Icon icon={getNodeIcon(nodeType.icon, nodeType.category)} />
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- Node Type Info - Icon and Title only -->
|
|
269
|
+
<h4 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
|
|
270
|
+
{nodeType.name}
|
|
271
|
+
</h4>
|
|
272
|
+
</div>
|
|
273
|
+
<p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0">
|
|
274
|
+
{nodeType.description}
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
{/each}
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
281
|
+
</div>
|
|
282
|
+
{:else}
|
|
283
|
+
<!-- Show categories with details when no search is active -->
|
|
284
|
+
<div class="flowdrop-p--4">
|
|
285
|
+
|
|
286
|
+
<!-- Category-specific details -->
|
|
287
|
+
<div class="flowdrop-category-list">
|
|
288
|
+
{#each categories as category (category)}
|
|
289
|
+
{@const categoryNodes = getFilteredNodesForCategory(category)}
|
|
290
|
+
{#if categoryNodes.length > 0}
|
|
291
|
+
<details class="flowdrop-details">
|
|
292
|
+
<summary class="flowdrop-details__summary">
|
|
293
|
+
<div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
|
|
294
|
+
<span class="flowdrop-node-icon" style="background-color: {getCategoryColorToken(category)}">
|
|
295
|
+
<Icon icon={getCategoryIcon(category)} />
|
|
296
|
+
</span>
|
|
297
|
+
<span>{getCategoryDisplayName(category)}</span>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="flowdrop-badge flowdrop-badge--secondary">{categoryNodes.length}</div>
|
|
300
|
+
</summary>
|
|
301
|
+
<div class="flowdrop-details__content">
|
|
302
|
+
<div class="flowdrop-node-list">
|
|
303
|
+
{#each categoryNodes as nodeType (nodeType.id)}
|
|
304
|
+
<div
|
|
305
|
+
class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
|
|
306
|
+
draggable="true"
|
|
307
|
+
ondragstart={(e) => handleNodeDragStart(e, nodeType)}
|
|
308
|
+
onclick={() => handleNodeClick(nodeType)}
|
|
309
|
+
role="button"
|
|
310
|
+
tabindex="0"
|
|
311
|
+
onkeydown={(e) => {
|
|
312
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
handleNodeClick(nodeType);
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
318
|
+
<div class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1">
|
|
319
|
+
<div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
|
|
320
|
+
<!-- Node Type Icon -->
|
|
321
|
+
<div class="flowdrop-node-icon" style="background-color: {getCategoryColorToken(nodeType.category)}">
|
|
322
|
+
<Icon icon={getNodeIcon(nodeType.icon, nodeType.category)} />
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<!-- Node Type Info - Icon and Title only -->
|
|
326
|
+
<h4 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
|
|
327
|
+
{nodeType.name}
|
|
328
|
+
</h4>
|
|
329
|
+
</div>
|
|
330
|
+
<p class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0">
|
|
331
|
+
{nodeType.description}
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
{/each}
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</details>
|
|
339
|
+
{/if}
|
|
340
|
+
{/each}
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
{/if}
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<!-- Footer -->
|
|
347
|
+
<div class="flowdrop-sidebar__footer">
|
|
348
|
+
<div class="flowdrop-flex flowdrop-gap--4">
|
|
349
|
+
<div class="flowdrop-flex flowdrop-gap--4">
|
|
350
|
+
{#if props.nodes?.length === 0}
|
|
351
|
+
<span class="flowdrop-text--xs flowdrop-text--gray">Loading components...</span>
|
|
352
|
+
{:else}
|
|
353
|
+
<span class="flowdrop-text--xs flowdrop-text--gray">Total: {props.nodes?.length || 0} components</span>
|
|
354
|
+
<span class="flowdrop-text--xs flowdrop-text--gray">Showing: {filteredNodes.length}</span>
|
|
355
|
+
{/if}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<style>
|
|
362
|
+
.flowdrop-sidebar {
|
|
363
|
+
background-color: #f3f4f6;
|
|
364
|
+
border-right: 1px solid #e5e7eb;
|
|
365
|
+
width: 320px;
|
|
366
|
+
height: 100%;
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.flowdrop-sidebar__header {
|
|
373
|
+
background-color: #ffffff;
|
|
374
|
+
border-bottom: 1px solid #e5e7eb;
|
|
375
|
+
padding: 0.5rem 1rem;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.flowdrop-sidebar__title {
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.flowdrop-sidebar__title h2 {
|
|
384
|
+
font-size: 1rem;
|
|
385
|
+
font-weight: 600;
|
|
386
|
+
margin: 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.flowdrop-sidebar__search {
|
|
390
|
+
padding: 0.75rem 1rem;
|
|
391
|
+
background-color: #ffffff;
|
|
392
|
+
border-bottom: 1px solid #e5e7eb;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.flowdrop-sidebar__content {
|
|
396
|
+
flex: 1;
|
|
397
|
+
overflow-y: auto;
|
|
398
|
+
scrollbar-width: thin;
|
|
399
|
+
scrollbar-color: #d1d5db #f3f4f6;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.flowdrop-sidebar__content::-webkit-scrollbar {
|
|
403
|
+
width: 8px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.flowdrop-sidebar__content::-webkit-scrollbar-track {
|
|
407
|
+
background: #f3f4f6;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.flowdrop-sidebar__content::-webkit-scrollbar-thumb {
|
|
411
|
+
background: #d1d5db;
|
|
412
|
+
border-radius: 4px;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.flowdrop-sidebar__content::-webkit-scrollbar-thumb:hover {
|
|
416
|
+
background: #9ca3af;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.flowdrop-sidebar__footer {
|
|
420
|
+
background-color: rgba(255, 255, 255, 0.8);
|
|
421
|
+
backdrop-filter: blur(8px);
|
|
422
|
+
border-top: 1px solid #e5e7eb;
|
|
423
|
+
padding: 0.5rem 0.75rem;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.flowdrop-node-list {
|
|
427
|
+
display: flex;
|
|
428
|
+
flex-direction: column;
|
|
429
|
+
gap: 0.375rem;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.flowdrop-node-item {
|
|
433
|
+
cursor: grab;
|
|
434
|
+
transition: all 0.2s ease-in-out;
|
|
435
|
+
border-radius: 0.375rem;
|
|
436
|
+
border: 1px solid transparent;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.flowdrop-node-item:hover {
|
|
440
|
+
border-color: #d1d5db;
|
|
441
|
+
background-color: #f9fafb;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.flowdrop-node-item:active {
|
|
445
|
+
cursor: grabbing;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.flowdrop-node-icon {
|
|
449
|
+
width: 2rem;
|
|
450
|
+
height: 2rem;
|
|
451
|
+
border-radius: 0.375rem;
|
|
452
|
+
color: #ffffff;
|
|
453
|
+
font-size: 0.875rem;
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
justify-content: center;
|
|
457
|
+
flex-shrink: 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.flowdrop-category-list {
|
|
461
|
+
display: flex;
|
|
462
|
+
flex-direction: column;
|
|
463
|
+
gap: 0.375rem;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.flowdrop-icon {
|
|
467
|
+
width: 1rem;
|
|
468
|
+
height: 1rem;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.flowdrop-items--center {
|
|
472
|
+
align-items: center;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
.flowdrop-truncate {
|
|
477
|
+
overflow: hidden;
|
|
478
|
+
text-overflow: ellipsis;
|
|
479
|
+
white-space: nowrap;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.flowdrop-card__body h4 {
|
|
483
|
+
margin: 0;
|
|
484
|
+
line-height: 1;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.flowdrop-p--4 {
|
|
488
|
+
padding: 1rem;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.flowdrop-py--1 {
|
|
492
|
+
padding-top: 0.25rem;
|
|
493
|
+
padding-bottom: 0.25rem;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.flowdrop-ml--0 {
|
|
497
|
+
margin-left: 0;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NodeMetadata, NodeCategory } from "../types/index.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
nodes: NodeMetadata[];
|
|
4
|
+
selectedCategory?: NodeCategory;
|
|
5
|
+
searchQuery?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const NodeSidebar: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type NodeSidebar = ReturnType<typeof NodeSidebar>;
|
|
9
|
+
export default NodeSidebar;
|