Visual Editor
Drag-and-drop nodes from sidebar, connect with edges, zoom and pan canvas.
The <flow-component> is a visual workflow editor built on top of VueFlow. It provides a drag-and-drop interface for creating and editing node-based workflows with custom action nodes.
The component is registered as a Custom Element:
import { defineCustomElement } from 'vue';import FlowCe from '@/flow/view/Flow.ce.vue';import { useCore } from './store/core';
// Expose hooks to browser contextwindow.$useFlowCore = useCore();
// Register custom elementcustomElements.define('flow-component', defineCustomElement(FlowCe));Visual Editor
Drag-and-drop nodes from sidebar, connect with edges, zoom and pan canvas.
Custom Nodes
Configurable action nodes with forms, execution status, and error handling.
Real-time Updates
Visual feedback during execution with animated edges and status indicators.
SubFlows
Nested workflow support for modular flow composition.
<script setup lang="ts">import { onMounted } from 'vue';
// Configure flow with available apps/actionsonMounted(() => { const { configure, setFlow } = window.$useFlowCore;
// Register available action modules configure('ion.action', [ { module: 'ion.action.http', label: 'HTTP Request', icon: 'globe', group: 'core', spec: { type: 'collection', spec: [ { type: 'select', name: 'method', label: 'Method', options: ['GET', 'POST', 'PUT', 'DELETE'] }, { type: 'url', name: 'url', label: 'URL', required: true }, ], }, }, { module: 'ion.action.email', label: 'Send Email', icon: 'mail', group: 'core', spec: { type: 'collection', spec: [ { type: 'email', name: 'to', label: 'To', required: true }, { type: 'text', name: 'subject', label: 'Subject', required: true }, { type: 'text', name: 'body', label: 'Body', multiline: true }, ], }, }, ]);
// Optionally load an existing flow setFlow({ nodes: [], edges: [], variables: {}, });});
function handleChange(change) { console.log('Flow changed:', change); // Auto-save or track changes}
function handleAction(action, value, patch) { console.log('Action triggered:', action.value); // Handle custom actions from node forms}
function handleRunNode(nodeId) { console.log('Execute node:', nodeId); // Trigger node execution on backend}</script>
<template> <flow-component @change="handleChange" @action="handleAction" @run-node="handleRunNode" /></template><!DOCTYPE html><html><head> <script src="./dist/flow.js"></script> <style> flow-component { display: block; width: 100%; height: 100vh; } </style></head><body> <flow-component></flow-component>
<script> const { configure, setFlow, getFlow } = window.$useFlowCore;
// Configure available actions configure('ion.action', [ { module: 'ion.action.log', label: 'Log Message', icon: 'terminal', group: 'core', spec: { type: 'collection', spec: [ { type: 'text', name: 'message', label: 'Message' } ] } } ]);
// Listen for events const flow = document.querySelector('flow-component');
flow.addEventListener('change', (e) => { console.log('Flow modified:', e.detail); });
flow.addEventListener('run-node', (e) => { const nodeId = e.detail; executeNode(nodeId); });
// Save flow function saveFlow() { const flowData = getFlow(); console.log('Saving:', flowData); // POST to backend } </script></body></html>The flow editor uses “apps” or “modules” to define available node types:
interface App { module: string; // Unique identifier (e.g., "ion.action.http") label: string; // Display name icon?: string; // Icon identifier group?: string; // Sidebar group spec?: Field; // Form specification for node configuration color?: string; // Node accent color}
// Register appsconst { configure } = window.$useFlowCore;
configure('ion.action', [ { module: 'ion.action.shopify.getOrders', label: 'Get Orders', icon: 'shopify', group: 'shopify', color: '#96bf48', spec: { type: 'collection', spec: [ { type: 'select', name: 'status', label: 'Status', options: ['open', 'closed', 'any'] }, { type: 'number', name: 'limit', label: 'Limit', default: 50 }, ], }, },]);During workflow execution, the flow provides visual feedback:
const { pushingNode, pushNode, reset } = window.$useFlowCore;
// When a node starts executingpushingNode({ node: 'node_123', queue: { edge_id: 'edge_456', target: 'node_789', data: { /* input data */ }, }, message: null, // or error message});
// When a node completespushNode({ node: { id: 'node_123', data: { /* node data */ } }, status: 'success', // or 'error' message: null, edges: ['edge_456'], outputs: [ { data: { /* output data */ } } ],});
// Reset all execution statereset();| State | Visual Effect |
|---|---|
| Executing | Animated green edge, pointer indicator |
| Success | Green completed edge |
| Error | Red node indicator, error message |
| Reset | All edges return to default gray |
| Shortcut | Action |
|---|---|
⌘/Ctrl + Shift + L | Toggle sidebar (add nodes) |
Escape | Close sidebar |
Delete/Backspace | Remove selected node/edge |
⌘/Ctrl + Z | Undo (if implemented) |
interface WorkFlow { nodes: Array<{ id: string; type: string; // Module identifier position: { x: number; y: number }; data: Record<string, any>; }>; edges: Array<{ id: string; type: 'custom'; source: string; target: string; sourceHandle?: string; targetHandle?: string; label?: string; data?: any; }>; variables?: Record<string, any>;}src/flow/├── flow.ts # Custom element registration├── components/│ ├── Sidebar/ # Node picker sidebar│ ├── ActionNode/ # Custom node component│ ├── ActionNodeViewer/ # Read-only node (for FlowRaw)│ ├── Edge/ # Custom edge component│ └── FlowDrawer/ # Node configuration drawer├── hooks/│ └── useDnD.ts # Drag and drop handling├── services/│ └── ... # Flow services├── store/│ ├── core.ts # Browser hooks (window.$useFlowCore)│ ├── workspace.ts # Flow state management│ └── modules.ts # App/module registry├── types/│ ├── nodes.ts # Node type definitions│ └── types.ts # General types└── view/ └── Flow.ce.vue # Main component