मेंपार्ट 2, हमने एक शक्तिशाली काम का निर्माण किया है इंजन.
लेकिन कार्य फूल JSON में परिभाषित नहीं हैं बहुत उपयोगकर्ता अनुकूल नहीं हैं.
पूरा एचएमएक्सीकरण
बाजू पटल में नोड गुण कॉन्फ़िगर करें
तकनीक स्टैकयह स्टैक क्यों?
क्लाएंट- साइड जटिलता कम करता हैसर्वर सच्चाई के स्रोत को बनाए रखता है
x-dataअंतर्क्रियात्मक यूआई अवयव के लिए सहीसरलनिर्देश
पेटी में से गहरा/लाइट मोडप्री- उन्नत अवयव (कार्ड्स, बटन, वीकार्ड्स)
कोई बाहरी लाइब्रेरी नहीं है
┌─────────────────────────────────────────────────────┐
│ Top Toolbar (Name, Save, Run, Cancel) │
├─────────┬───────────────────────────┬───────────────┤
│ Node │ Canvas │ Node │
│ Palette │ (Drag & Drop) │ Inspector │
│ │ │ │
│ [HTTP] │ ┌─────┐ │ Name: ___ │
│ [Xform] │ │Node1│─┐ │ Type: ___ │
│ [Delay] │ └─────┘ │ │ Config: ___ │
│ [Cond] │ ▼ │ Style: ___ │
│ │ ┌─────┐ │ │
│ │ │Node2│ │ │
│ │ └─────┘ │ │
└─────────┴───────────────────────────┴───────────────┘
हमारे संपादक में तीन मुख्य फलक हैं:
[Route("workflow")]
public class WorkflowController : BaseController
{
private readonly WorkflowService _workflowService;
private readonly WorkflowExecutionService _executionService;
[HttpGet]
public async Task<IActionResult> Index()
{
var workflows = await _workflowService.GetAllAsync();
return View("Index", workflows);
}
[HttpGet("create")]
public IActionResult Create()
{
var workflow = new WorkflowDefinition
{
Name = "New Workflow",
Nodes = new List<WorkflowNode>(),
Connections = new List<NodeConnection>()
};
return View("Editor", workflow);
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
var workflow = await _workflowService.GetByIdAsync(id);
return workflow == null ? NotFound() : View("Editor", workflow);
}
[HttpPost("save")]
public async Task<IActionResult> Save([FromBody] WorkflowDefinition workflow)
{
var saved = await _workflowService.CreateOrUpdateAsync(workflow);
return Json(new { success = true, workflowId = saved.Id });
}
[HttpPost("execute/{id}")]
public async Task<IActionResult> Execute(string id,
[FromBody] Dictionary<string, object>? inputData = null)
{
var execution = await _executionService.ExecuteWorkflowAsync(
id, inputData, User.Identity?.Name ?? "Anonymous");
return Json(new
{
success = true,
executionId = execution.Id,
status = execution.Status.ToString()
});
}
}
सबसे पहले, हम हमारे नियंत्रण अंत बिन्दु सेट है:
@model Mostlylucid.Workflow.Shared.Models.WorkflowDefinition
<div class="container-fluid"
x-data="workflowEditor(@Html.Raw(JsonSerializer.Serialize(Model)))"
x-init="init()">
<!-- Top Toolbar -->
<div class="navbar bg-base-200 rounded-box shadow-lg mb-6">
<div class="flex-1">
<input type="text"
x-model="workflow.name"
placeholder="Workflow Name"
class="input input-ghost text-2xl font-bold w-96" />
</div>
<div class="flex-none gap-2">
<button @click="saveWorkflow()" class="btn btn-success">
Save
</button>
<button @click="runWorkflow()" class="btn btn-primary">
Run
</button>
</div>
</div>
<!-- Three-panel layout -->
<div class="flex gap-4 h-[calc(100vh-200px)]">
<!-- Left: Node Palette -->
<!-- Center: Canvas -->
<!-- Right: Node Inspector -->
</div>
</div>
हमारे संपादक दृश्य राज्य प्रबंधन के लिए अप्रयोगात्मक उपयोग करता है:workflowEditorआन्ना राज्य प्रबंधन
function workflowEditor(initialWorkflow) {
return {
workflow: initialWorkflow || {
id: crypto.randomUUID(),
name: 'New Workflow',
nodes: [],
connections: [],
isEnabled: true
},
selectedNode: null,
draggedNodeType: null,
connecting: false,
init() {
// Initialize node inputs as JSON for editing
this.workflow.nodes.forEach(node => {
node.inputsJson = JSON.stringify(node.inputs || {}, null, 2);
});
},
// ... methods below
};
}
अपूर्ण घटक सभी संपादक अवस्था का प्रबंधन करता है:
<div class="w-64 bg-base-200 rounded-box p-4 overflow-y-auto">
<h3 class="text-lg font-bold mb-4">📦 Available Nodes</h3>
<div class="space-y-3">
<div class="card bg-base-100 shadow cursor-pointer"
draggable="true"
@dragstart="dragStart($event, 'HttpRequest', '🌐', '#10B981')">
<div class="card-body p-4">
<h4 class="font-semibold">🌐 HTTP Request</h4>
<p class="text-xs">Make API calls</p>
</div>
</div>
<div class="card bg-base-100 shadow cursor-pointer"
draggable="true"
@dragstart="dragStart($event, 'Transform', '🔄', '#3B82F6')">
<div class="card-body p-4">
<h4 class="font-semibold">🔄 Transform</h4>
<p class="text-xs">Process data</p>
</div>
</div>
<!-- More node types... -->
</div>
</div>
dragStart(event, nodeType, icon, color) {
this.draggedNodeType = { type: nodeType, icon, color };
}
ड्रैग नियंत्रक
<div class="flex-1 bg-base-300 rounded-box relative overflow-hidden"
@drop="dropNode($event)"
@dragover.prevent
id="workflow-canvas">
<div class="absolute inset-0 overflow-auto p-8">
<!-- Grid background -->
<div class="absolute inset-0 opacity-20"
style="background-image: repeating-linear-gradient(...);
background-size: 20px 20px;">
</div>
<!-- Render nodes -->
<template x-for="node in workflow.nodes" :key="node.id">
<div :id="'node-' + node.id"
class="absolute card shadow-2xl cursor-move"
:style="`left: ${node.position.x}px;
top: ${node.position.y}px;
width: ${node.style.width}px;
background-color: ${node.style.backgroundColor};`"
@mousedown="selectNode(node)"
draggable="true"
@dragstart="dragNode($event, node)">
<div class="card-body p-4">
<div class="flex items-center gap-2">
<span x-text="node.style.icon || '📦'"></span>
<h3 x-text="node.name"></h3>
</div>
<!-- Connection points -->
<div class="flex justify-between mt-2">
<div class="badge badge-primary"
@click.stop="startConnection(node, 'input')">
◀
</div>
<div class="badge badge-success"
@click.stop="startConnection(node, 'output')">
▶
</div>
</div>
</div>
</div>
</template>
<!-- SVG connections layer -->
<svg class="absolute inset-0 pointer-events-none"
style="width: 100%; height: 100%;">
<template x-for="conn in workflow.connections" :key="conn.id">
<path :d="getConnectionPath(conn)"
stroke="#94A3B8"
stroke-width="3"
fill="none"
marker-end="url(#arrowhead)"/>
</template>
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="10">
<polygon points="0 0, 10 3, 0 6" fill="#94A3B8" />
</marker>
</defs>
</svg>
</div>
</div>
कैनवास है जहां जादू होता है:
dropNode(event) {
if (!this.draggedNodeType) return;
const canvas = document.getElementById('canvas-container');
const rect = canvas.getBoundingClientRect();
// Calculate drop position
const x = event.clientX - rect.left + canvas.scrollLeft - 100;
const y = event.clientY - rect.top + canvas.scrollTop - 50;
// Create new node
const newNode = {
id: crypto.randomUUID(),
type: this.draggedNodeType.type,
name: this.draggedNodeType.type,
inputs: {},
outputs: {},
position: { x, y },
style: {
backgroundColor: this.draggedNodeType.color,
textColor: '#FFFFFF',
borderColor: this.draggedNodeType.color,
icon: this.draggedNodeType.icon,
width: 200
},
inputsJson: '{}'
};
this.workflow.nodes.push(newNode);
// Set as start node if first node
if (this.workflow.nodes.length === 1) {
this.workflow.startNodeId = newNode.id;
}
this.draggedNodeType = null;
}
जब एक नोड कैनवास पर गिर जाता है:
getConnectionPath(conn) {
const source = this.workflow.nodes.find(n => n.id === conn.sourceNodeId);
const target = this.workflow.nodes.find(n => n.id === conn.targetNodeId);
if (!source || !target) return '';
// Calculate connection points
const x1 = source.position.x + (source.style?.width || 200);
const y1 = source.position.y + 50;
const x2 = target.position.x;
const y2 = target.position.y + 50;
// Create curved Bezier path
const midX = (x1 + x2) / 2;
return `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`;
}
एसवीजी कनेक्शन रेंडरिंग
यह नोड्स के बीच हल्का, वक्रीय कनेक्शन बनाता है!
<div class="w-80 bg-base-200 rounded-box p-4 overflow-y-auto"
x-show="selectedNode">
<h3 class="text-lg font-bold mb-4">⚙️ Node Settings</h3>
<template x-if="selectedNode">
<div class="space-y-4">
<div class="form-control">
<label class="label">Node Name</label>
<input type="text"
x-model="selectedNode.name"
class="input input-bordered" />
</div>
<div class="form-control">
<label class="label">Background Color</label>
<input type="color"
x-model="selectedNode.style.backgroundColor"
class="input w-full h-12" />
</div>
<div class="form-control">
<label class="label">Icon (emoji)</label>
<input type="text"
x-model="selectedNode.style.icon"
class="input input-bordered"
maxlength="2" />
</div>
<div class="form-control">
<label class="label">Inputs (JSON)</label>
<textarea x-model="selectedNode.inputsJson"
@input="updateNodeInputs()"
class="textarea textarea-bordered font-mono"
rows="6"></textarea>
</div>
<button @click="setStartNode(selectedNode)"
class="btn btn-sm"
:class="workflow.startNodeId === selectedNode.id ?
'btn-success' : 'btn-outline'">
Set as Start Node
</button>
</div>
</template>
</div>
दायाँ फलक चुने गए नोड्स को कॉन्फ़िगर करने देता है:
updateNodeInputs() {
if (!this.selectedNode) return;
try {
this.selectedNode.inputs = JSON.parse(this.selectedNode.inputsJson);
} catch (e) {
console.error('Invalid JSON:', e);
// Could show error badge
}
}
उपयोक्ता इनपुट संपादित करें के रूप में, हम उन्हें वास्तव में समय में व्याख्या करते हैं:
startConnection(node, type) {
if (this.connecting) {
// Complete connection
if (this.connectionStart.node.id !== node.id) {
this.workflow.connections.push({
id: crypto.randomUUID(),
sourceNodeId: this.connectionStart.node.id,
targetNodeId: node.id,
sourceOutput: 'default',
targetInput: 'default'
});
}
this.connecting = false;
this.connectionStart = null;
} else {
// Start connection
this.connecting = true;
this.connectionStart = { node, type };
}
}
कनेक्शन बिंदुओं पर क्लिक करके उपयोक्ताओं से कनेक्ट हों:
async saveWorkflow() {
try {
const response = await fetch('/workflow/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.workflow)
});
const result = await response.json();
if (result.success) {
alert('Workflow saved!');
window.location.href = '/workflow';
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error saving: ' + error.message);
}
}
जब उपयोक्ता क्लिक करें "Se", हम एक साधारण कॉल का प्रयोग करते हैं (एमएमएक्स भी हो सकता है):
async runWorkflow() {
try {
// Save first
await this.saveWorkflow();
// Then execute
const response = await fetch(`/workflow/execute/${this.workflow.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const result = await response.json();
if (result.success) {
alert(`Workflow started! Execution ID: ${result.executionId}`);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
संपादक से कार्य प्रवाहित करता है:
<!-- In your layout -->
<select data-choose-theme class="select select-bordered">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="cupcake">Cupcake</option>
<option value="synthwave">Synthwave</option>
</select>
प्रसंग जोरयूआई के साथ स्विच कर रहा हैbg-base-200, text-base-contentगुलबहारआई स्वचालित प्रसंग स्विच प्रदान करता है.
हमारे सभी रंग गुल्बाआई का उपयोग करें (एडिस्किक क्लास)
<div class="flex flex-col lg:flex-row gap-4">
<!-- On mobile: stack vertically -->
<!-- On desktop: side-by-side -->
</div>
हमारे तीन आकारों का खाका भिन्न स्क्रीन आकार के अनुकूल:
आभासी स्क्रॉलिंग
let dragTimeout;
dragNode(event, node) {
clearTimeout(dragTimeout);
dragTimeout = setTimeout(() => {
// Update position
}, 16); // ~60fps
}
लेकिन अधिकांश उपयोग मामलों के लिए, 50-100 नोड्स का अनुवाद आधुनिक ब्राउज़रों के साथ पूरी तरह से ठीक है.
<button @click="deleteNode(node)"
class="btn"
aria-label="Delete node"
@keydown.delete="deleteNode(node)">
🗑️
</button>
जब खींच रहे नोड्स, हम अक्षम स्थिति अद्यतन कर सकते हैं:
✅ पहुँचहम सही ARia लेबल और कुंजीपट नेविगेशन जोड़ा है: ✅ हमने क्या बनाया हैइस पोस्ट में, हमने बनाया: ✅ खींचें और ड्रॉप नोड रंग पैलेट करें- ड्रैग नोड क़िस्म ✅ दृश्य कैनवास- स्थान नोड्स के साथ ग्रिड पृष्ठभूमि ✅ एसवीजी कनेक्शन- नोड्स के बीच वक्रीय पथ ✅ नोड एंटिटी- रीयल- टाइम कॉन्फ़िगरेशन फलक ✅ प्रसंग समर्थन- सभी गुलजार प्रसंगों के साथ काम करता है ✅ उभारे गए ख़ाका- स्क्रीन आकार के अनुकूलक
{
"name": "GitHub Stars Monitor",
"nodes": [
{
"id": "1",
"type": "HttpRequest",
"name": "Fetch Repo Data",
"inputs": {
"url": "https://api.github.com/repos/{{owner}}/{{repo}}",
"method": "GET"
},
"position": { "x": 100, "y": 100 },
"style": { "backgroundColor": "#10B981", "icon": "🌐" }
},
{
"id": "2",
"type": "Condition",
"name": "Check Stars",
"inputs": {
"condition": "{{stargazers_count}} > 100"
},
"position": { "x": 100, "y": 250 },
"style": { "backgroundColor": "#8B5CF6", "icon": "🔀" }
},
{
"id": "3",
"type": "Log",
"name": "Log Success",
"inputs": {
"message": "Repo has {{stargazers_count}} stars!",
"level": "info"
},
"position": { "x": 100, "y": 400 },
"style": { "backgroundColor": "#3B82F6", "icon": "📝" }
}
],
"connections": [
{ "sourceNodeId": "1", "targetNodeId": "2" },
{ "sourceNodeId": "2", "targetNodeId": "3" }
],
"startNodeId": "1"
}
चिकनी, प्रतिक्रिया यूआईउदाहरण कार्य प्रवाहयहाँ एक पूरा कार्य फूल है आप संपादक में निर्माण कर सकते हैं:
अगला क्या है?
में
पार्ट 4
, हम के लिए आग को एकीकृत कर देंगे:
समयसारिणी:
एपीआई पोलिंग ट्रिगर्स
पृष्ठभूमि कार्य प्रक्रिया
डैश बोर्ड मॉनीटर किया जा रहा है
सिर्फ पर्याप्त रीनामेंट प्रदान करता है
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.