@d34dman/flowdrop 0.0.13 → 0.0.15

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 CHANGED
@@ -7,10 +7,12 @@ A visual workflow editor component library built with Svelte 5 and @xyflow/svelt
7
7
  - **Visual Workflow Editor**: Drag-and-drop interface for building node-based workflows
8
8
  - **Svelte 5 Components**: Modern, reactive components with runes
9
9
  - **Configurable API Client**: Flexible endpoint configuration for backend integration
10
+ - **Runtime Configuration**: Build once, deploy anywhere with environment variables
10
11
  - **Node System**: Extensible node types with customizable configuration
11
12
  - **Real-time Updates**: Live workflow state management with stores
12
13
  - **Framework Agnostic**: Can be integrated into any web application
13
14
  - **TypeScript Support**: Full type definitions included
15
+ - **Docker Ready**: Production-ready Dockerfile and Docker Compose configuration
14
16
 
15
17
  ## 📦 Installation
16
18
 
@@ -325,6 +327,53 @@ MIT
325
327
  - Create an issue in the project repository
326
328
  - Review the examples in `src/lib/examples/`
327
329
 
330
+ ## 🚢 Deployment
331
+
332
+ FlowDrop uses **runtime configuration** instead of build-time environment variables, allowing you to build once and deploy to multiple environments.
333
+
334
+ ### Quick Start with Docker
335
+
336
+ ```bash
337
+ # Copy environment file
338
+ cp env.example .env
339
+
340
+ # Edit .env with your configuration
341
+ # Set FLOWDROP_API_BASE_URL to your backend API URL
342
+
343
+ # Start with Docker Compose
344
+ docker-compose up -d
345
+ ```
346
+
347
+ ### Environment Variables
348
+
349
+ **Production (Runtime):**
350
+ - `FLOWDROP_API_BASE_URL` - Backend API URL
351
+ - `FLOWDROP_THEME` - UI theme (light/dark/auto)
352
+ - `FLOWDROP_TIMEOUT` - Request timeout in milliseconds
353
+ - `FLOWDROP_AUTH_TYPE` - Authentication type (none/bearer/api_key/custom)
354
+ - `FLOWDROP_AUTH_TOKEN` - Authentication token
355
+
356
+ **Development (Build-time):**
357
+ - `VITE_API_BASE_URL` - Dev API URL (used only during `npm run dev`)
358
+
359
+ ### Build for Production
360
+
361
+ ```bash
362
+ # Install dependencies
363
+ npm ci
364
+
365
+ # Build the application
366
+ npm run build
367
+
368
+ # Set environment variables and start
369
+ export FLOWDROP_API_BASE_URL=http://your-backend:8080/api/flowdrop
370
+ node build
371
+ ```
372
+
373
+ For detailed deployment instructions, see:
374
+ - [DEPLOYMENT.md](./DEPLOYMENT.md) - Complete deployment guide
375
+ - [DOCKER.md](./DOCKER.md) - Docker quick start
376
+
328
377
  ---
329
378
 
330
379
  **FlowDrop** - Visual workflow editing for modern web applications
@@ -0,0 +1,68 @@
1
+ <!--
2
+ Flow Drop Zone Component
3
+ Handles drag and drop with proper coordinate transformation
4
+ Must be used inside SvelteFlowProvider
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import { useSvelteFlow } from "@xyflow/svelte";
9
+ import type { Snippet } from "svelte";
10
+
11
+ interface Props {
12
+ ondrop: (nodeTypeData: string, position: { x: number; y: number }) => void;
13
+ children: Snippet;
14
+ }
15
+
16
+ let props: Props = $props();
17
+
18
+ // Access SvelteFlow instance for coordinate transformation
19
+ const { screenToFlowPosition } = useSvelteFlow();
20
+
21
+ /**
22
+ * Handle drag over event
23
+ */
24
+ function handleDragOver(e: DragEvent): void {
25
+ e.preventDefault();
26
+ if (e.dataTransfer) {
27
+ e.dataTransfer.dropEffect = "copy";
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Handle drop event with proper coordinate transformation
33
+ */
34
+ function handleDrop(e: DragEvent): void {
35
+ e.preventDefault();
36
+
37
+ // Get the data from the drag event
38
+ const nodeTypeData = e.dataTransfer?.getData("application/json");
39
+ if (nodeTypeData) {
40
+ // Convert screen coordinates to flow coordinates (accounts for zoom and pan)
41
+ const position = screenToFlowPosition({
42
+ x: e.clientX,
43
+ y: e.clientY
44
+ });
45
+
46
+ // Call the parent handler with the converted position
47
+ props.ondrop(nodeTypeData, position);
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <div
53
+ class="flow-drop-zone"
54
+ role="application"
55
+ aria-label="Workflow canvas"
56
+ ondragover={handleDragOver}
57
+ ondrop={handleDrop}
58
+ >
59
+ {@render props.children()}
60
+ </div>
61
+
62
+ <style>
63
+ .flow-drop-zone {
64
+ width: 100%;
65
+ height: 100%;
66
+ }
67
+ </style>
68
+
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ ondrop: (nodeTypeData: string, position: {
4
+ x: number;
5
+ y: number;
6
+ }) => void;
7
+ children: Snippet;
8
+ }
9
+ declare const FlowDropZone: import("svelte").Component<Props, {}, "">;
10
+ type FlowDropZone = ReturnType<typeof FlowDropZone>;
11
+ export default FlowDropZone;
@@ -22,6 +22,7 @@
22
22
  WorkflowEdge
23
23
  } from '../types/index.js';
24
24
  import CanvasBanner from './CanvasBanner.svelte';
25
+ import FlowDropZone from './FlowDropZone.svelte';
25
26
  import { tick } from 'svelte';
26
27
  import type { EndpointConfig } from '../config/endpoints.js';
27
28
  import ConnectionLine from './ConnectionLine.svelte';
@@ -277,8 +278,8 @@
277
278
  /**
278
279
  * Handle node deletion - automatically remove connected edges
279
280
  */
280
- function handleNodesDelete(event: { detail: { nodes: WorkflowNodeType[] } }): void {
281
- const deletedNodeIds = new Set(event.detail.nodes.map((node) => node.id));
281
+ function handleNodesDelete(params: { nodes: WorkflowNodeType[]; edges: WorkflowEdge[] }): void {
282
+ const deletedNodeIds = new Set(params.nodes.map((node) => node.id));
282
283
 
283
284
  // Filter out edges connected to deleted nodes
284
285
  flowEdges = flowEdges.filter(
@@ -329,6 +330,30 @@
329
330
  function checkWorkflowCycles(): boolean {
330
331
  return WorkflowOperationsHelper.checkWorkflowCycles(flowNodes, flowEdges);
331
332
  }
333
+
334
+ /**
335
+ * Handle drop event and add new node to canvas
336
+ * This will be called from the inner DropZone component
337
+ */
338
+ async function handleNodeDrop(
339
+ nodeTypeData: string,
340
+ position: { x: number; y: number }
341
+ ): Promise<void> {
342
+ // Create the node using the helper, passing existing nodes for ID generation
343
+ const newNode = NodeOperationsHelper.createNodeFromDrop(nodeTypeData, position, flowNodes);
344
+
345
+ if (newNode && currentWorkflow) {
346
+ currentWorkflow = WorkflowOperationsHelper.addNode(currentWorkflow, newNode);
347
+
348
+ // Update the global store
349
+ updateGlobalStore();
350
+
351
+ // Wait for DOM update to ensure SvelteFlow updates
352
+ await tick();
353
+ } else if (!currentWorkflow) {
354
+ console.warn('No currentWorkflow available for new node');
355
+ }
356
+ }
332
357
  </script>
333
358
 
334
359
  <SvelteFlowProvider>
@@ -336,80 +361,41 @@
336
361
  <!-- Main Editor Area -->
337
362
  <div class="flowdrop-workflow-editor__main">
338
363
  <!-- Flow Canvas -->
339
- <div
340
- class="flowdrop-canvas"
341
- role="application"
342
- aria-label="Workflow canvas"
343
- ondragover={(e: DragEvent) => {
344
- e.preventDefault();
345
- e.dataTransfer!.dropEffect = 'copy';
346
- }}
347
- ondrop={async (e: DragEvent) => {
348
- e.preventDefault();
349
-
350
- // Get the data from the drag event
351
- const nodeTypeData = e.dataTransfer?.getData('application/json');
352
- if (nodeTypeData) {
353
- // Get the position relative to the canvas
354
- const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
355
- const position = {
356
- x: e.clientX - rect.left,
357
- y: e.clientY - rect.top
358
- };
359
-
360
- // Create the node using the helper, passing existing nodes for ID generation
361
- const newNode = NodeOperationsHelper.createNodeFromDrop(
362
- nodeTypeData,
363
- position,
364
- flowNodes
365
- );
366
-
367
- if (newNode && currentWorkflow) {
368
- currentWorkflow = WorkflowOperationsHelper.addNode(currentWorkflow, newNode);
369
-
370
- // Update the global store
371
- updateGlobalStore();
372
-
373
- // Wait for DOM update to ensure SvelteFlow updates
374
- await tick();
375
- } else if (!currentWorkflow) {
376
- console.warn('No currentWorkflow available for new node');
377
- }
378
- }
379
- }}
380
- >
381
- <SvelteFlow
382
- bind:nodes={flowNodes}
383
- bind:edges={flowEdges}
384
- {nodeTypes}
385
- {defaultEdgeOptions}
386
- onconnect={handleConnect}
387
- onnodesdelete={handleNodesDelete}
388
- minZoom={0.2}
389
- maxZoom={3}
390
- clickConnect={true}
391
- elevateEdgesOnSelect={true}
392
- connectionLineType={ConnectionLineType.Bezier}
393
- connectionLineComponent={ConnectionLine}
394
- snapGrid={[10, 10]}
395
- fitView
396
- >
397
- <Controls />
398
- <Background
399
- gap={10}
400
- bgColor="var(--flowdrop-background-color)"
401
- variant={BackgroundVariant.Dots}
402
- />
403
- <MiniMap />
404
- </SvelteFlow>
405
- <!-- Drop Zone Indicator -->
406
- {#if flowNodes.length === 0}
407
- <CanvasBanner
408
- title="Drag components here to start building"
409
- description="Use the sidebar to add components to your workflow"
410
- iconName="mdi:graph"
411
- />
412
- {/if}
364
+ <div class="flowdrop-canvas">
365
+ <FlowDropZone ondrop={handleNodeDrop}>
366
+ <SvelteFlow
367
+ bind:nodes={flowNodes}
368
+ bind:edges={flowEdges}
369
+ {nodeTypes}
370
+ {defaultEdgeOptions}
371
+ onconnect={handleConnect}
372
+ ondelete={handleNodesDelete}
373
+ minZoom={0.2}
374
+ maxZoom={3}
375
+ clickConnect={true}
376
+ elevateEdgesOnSelect={true}
377
+ connectionLineType={ConnectionLineType.Bezier}
378
+ connectionLineComponent={ConnectionLine}
379
+ snapGrid={[10, 10]}
380
+ fitView
381
+ >
382
+ <Controls />
383
+ <Background
384
+ gap={10}
385
+ bgColor="var(--flowdrop-background-color)"
386
+ variant={BackgroundVariant.Dots}
387
+ />
388
+ <MiniMap />
389
+ </SvelteFlow>
390
+ <!-- Drop Zone Indicator -->
391
+ {#if flowNodes.length === 0}
392
+ <CanvasBanner
393
+ title="Drag components here to start building"
394
+ description="Use the sidebar to add components to your workflow"
395
+ iconName="mdi:graph"
396
+ />
397
+ {/if}
398
+ </FlowDropZone>
413
399
  </div>
414
400
 
415
401
  <!-- Status Bar -->
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Runtime Configuration Service
3
+ *
4
+ * Provides runtime configuration fetched from the server.
5
+ * This allows the application to use environment variables set at deployment
6
+ * time rather than build time.
7
+ */
8
+ export interface RuntimeConfig {
9
+ /** Base URL for the FlowDrop API */
10
+ apiBaseUrl: string;
11
+ /** Theme preference */
12
+ theme: "light" | "dark" | "auto";
13
+ /** Request timeout in milliseconds */
14
+ timeout: number;
15
+ /** Authentication type */
16
+ authType: "none" | "bearer" | "api_key" | "custom";
17
+ /** Authentication token */
18
+ authToken?: string;
19
+ /** Application version */
20
+ version: string;
21
+ /** Environment name */
22
+ environment: string;
23
+ }
24
+ /**
25
+ * Fetch runtime configuration from the server
26
+ *
27
+ * @param force - Force fetch even if cached
28
+ * @returns Promise resolving to runtime configuration
29
+ */
30
+ export declare function fetchRuntimeConfig(force?: boolean): Promise<RuntimeConfig>;
31
+ /**
32
+ * Get runtime configuration synchronously from cache
33
+ *
34
+ * @returns Cached runtime configuration or null if not loaded
35
+ */
36
+ export declare function getRuntimeConfig(): RuntimeConfig | null;
37
+ /**
38
+ * Clear the runtime configuration cache
39
+ */
40
+ export declare function clearRuntimeConfigCache(): void;
41
+ /**
42
+ * Initialize runtime configuration
43
+ * Should be called once when the application starts
44
+ *
45
+ * @returns Promise resolving to runtime configuration
46
+ */
47
+ export declare function initRuntimeConfig(): Promise<RuntimeConfig>;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Runtime Configuration Service
3
+ *
4
+ * Provides runtime configuration fetched from the server.
5
+ * This allows the application to use environment variables set at deployment
6
+ * time rather than build time.
7
+ */
8
+ /** Cached runtime configuration */
9
+ let cachedConfig = null;
10
+ /** Cache timestamp */
11
+ let cacheTimestamp = 0;
12
+ /** Cache duration in milliseconds (5 minutes) */
13
+ const CACHE_DURATION = 5 * 60 * 1000;
14
+ /**
15
+ * Fetch runtime configuration from the server
16
+ *
17
+ * @param force - Force fetch even if cached
18
+ * @returns Promise resolving to runtime configuration
19
+ */
20
+ export async function fetchRuntimeConfig(force = false) {
21
+ const now = Date.now();
22
+ // Return cached config if available and not expired
23
+ if (!force && cachedConfig && now - cacheTimestamp < CACHE_DURATION) {
24
+ return cachedConfig;
25
+ }
26
+ try {
27
+ const response = await fetch("/api/config");
28
+ if (!response.ok) {
29
+ throw new Error(`Failed to fetch runtime config: ${response.statusText}`);
30
+ }
31
+ const config = (await response.json());
32
+ // Update cache
33
+ cachedConfig = config;
34
+ cacheTimestamp = now;
35
+ return config;
36
+ }
37
+ catch (error) {
38
+ console.error("Failed to fetch runtime configuration:", error);
39
+ // Return default configuration if fetch fails
40
+ const defaultConfig = {
41
+ apiBaseUrl: "/api/flowdrop",
42
+ theme: "auto",
43
+ timeout: 30000,
44
+ authType: "none",
45
+ version: "1.0.0",
46
+ environment: "production",
47
+ };
48
+ // Cache the default config to avoid repeated failed requests
49
+ if (!cachedConfig) {
50
+ cachedConfig = defaultConfig;
51
+ cacheTimestamp = now;
52
+ }
53
+ return cachedConfig || defaultConfig;
54
+ }
55
+ }
56
+ /**
57
+ * Get runtime configuration synchronously from cache
58
+ *
59
+ * @returns Cached runtime configuration or null if not loaded
60
+ */
61
+ export function getRuntimeConfig() {
62
+ return cachedConfig;
63
+ }
64
+ /**
65
+ * Clear the runtime configuration cache
66
+ */
67
+ export function clearRuntimeConfigCache() {
68
+ cachedConfig = null;
69
+ cacheTimestamp = 0;
70
+ }
71
+ /**
72
+ * Initialize runtime configuration
73
+ * Should be called once when the application starts
74
+ *
75
+ * @returns Promise resolving to runtime configuration
76
+ */
77
+ export async function initRuntimeConfig() {
78
+ return fetchRuntimeConfig(true);
79
+ }
package/dist/index.d.ts CHANGED
@@ -52,6 +52,7 @@ export * from './config/endpoints.js';
52
52
  export { defaultApiConfig, getEndpointUrl } from './config/apiConfig.js';
53
53
  export type { ApiConfig } from './config/apiConfig.js';
54
54
  export { DEFAULT_PORT_CONFIG } from './config/defaultPortConfig.js';
55
+ export * from './config/runtimeConfig.js';
55
56
  export * from './adapters/WorkflowAdapter.js';
56
57
  export * from './clients/ApiClient.js';
57
58
  export { mountWorkflowEditor, unmountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from './svelte-app.js';
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes
56
56
  export * from './config/endpoints.js';
57
57
  export { defaultApiConfig, getEndpointUrl } from './config/apiConfig.js';
58
58
  export { DEFAULT_PORT_CONFIG } from './config/defaultPortConfig.js';
59
+ export * from './config/runtimeConfig.js';
59
60
  // Export adapters
60
61
  export * from './adapters/WorkflowAdapter.js';
61
62
  // Export API client
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@d34dman/flowdrop",
3
3
  "license": "MIT",
4
4
  "private": false,
5
- "version": "0.0.13",
5
+ "version": "0.0.15",
6
6
  "scripts": {
7
7
  "dev": "vite dev",
8
8
  "build": "vite build && npm run prepack",
@@ -99,6 +99,7 @@
99
99
  "@storybook/addon-vitest": "^9.0.15",
100
100
  "@storybook/sveltekit": "^9.0.15",
101
101
  "@sveltejs/adapter-auto": "^6.0.0",
102
+ "@sveltejs/adapter-node": "^5.4.0",
102
103
  "@sveltejs/kit": "^2.16.0",
103
104
  "@sveltejs/package": "^2.0.0",
104
105
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
@@ -136,4 +137,4 @@
136
137
  "svelte-5-french-toast": "^2.0.6",
137
138
  "uuid": "^11.1.0"
138
139
  }
139
- }
140
+ }