Ramesh-vani commited on
Commit
80a8361
·
verified ·
1 Parent(s): 42076a5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +666 -52
index.html CHANGED
@@ -23,12 +23,25 @@
23
  cursor: move;
24
  user-select: none;
25
  box-sizing: border-box;
26
- /* Smooth transition for slight style changes */
27
  transition: box-shadow 0.1s ease;
28
  }
29
  .node.executing { border: 3px solid orange; }
30
  .node.error { border-color: red; }
31
- .node-header { font-weight: bold; margin-bottom: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  .port {
33
  width: 10px;
34
  height: 10px;
@@ -69,6 +82,7 @@
69
  </head>
70
  <body>
71
  <div id="toolbar">
 
72
  <button onclick="addNode('Variable')">Variable</button>
73
  <button onclick="addNode('Assign')">Assign</button>
74
  <button onclick="addNode('Print')">Print</button>
@@ -85,26 +99,32 @@
85
  <button onclick="addNode('Multiply')">Multiply</button>
86
  <button onclick="addNode('Divide')">Divide</button>
87
  <button onclick="addNode('Array')">Array</button>
 
 
 
 
88
  <br>
89
  <button onclick="runWorkflow()">Run Workflow</button>
90
  <button onclick="startStepExecution()">Start Step Execution</button>
91
  <button onclick="stepExecution()">Step Execution</button>
92
  <button onclick="saveWorkflow()">Save Workflow</button>
93
  <button onclick="loadWorkflow()">Load Workflow</button>
 
 
94
  </div>
95
  <div id="canvas">
96
  <svg id="connections"></svg>
97
  </div>
98
 
99
  <script>
100
- // Global variables and a variable to track the highest z-index
101
  const canvas = document.getElementById('canvas');
102
  const connectionsSvg = document.getElementById('connections');
103
  let nodes = [];
104
  let connections = [];
105
  let draggingNode = null;
106
  let draggingPort = null;
107
- let tempLine = null; // temporary connection line
108
  let offsetX, offsetY;
109
  let variables = {};
110
  let functionReturns = {};
@@ -114,7 +134,7 @@
114
  let workflowOutput = "";
115
  let executionQueue = [];
116
  const controlNodes = ["If", "WhileLoop", "Switch", "Break", "Continue", "Function", "Return"];
117
- let highestZ = 100; // initial highest z-index
118
 
119
  // ----------------------------
120
  // Node creation and UI helpers
@@ -124,16 +144,16 @@
124
  node.className = 'node';
125
  node.dataset.type = type;
126
  node.dataset.id = nodeIdCounter++;
127
- // Set node position (snapped to grid)
128
  const left = options.left || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
129
  const top = options.top || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
130
  node.style.left = `${left}px`;
131
  node.style.top = `${top}px`;
132
- // Ensure node is on top when created
133
  highestZ++;
134
  node.style.zIndex = highestZ;
135
-
136
- let html = `<div class="node-header">${type}</div>`;
 
 
137
  if (type === 'Variable') {
138
  html += `
139
  <input type="text" class="var-name" placeholder="Variable Name">
@@ -181,8 +201,12 @@
181
  html += `
182
  <input type="text" class="condition" placeholder="Condition (e.g., x > 5)">
183
  <div class="port port-input" data-type="input"></div>
184
- <div class="port port-output-true" data-type="output" data-branch="true"><span class="label">True</span></div>
185
- <div class="port port-output-false" data-type="output" data-branch="false"><span class="label">False</span></div>
 
 
 
 
186
  `;
187
  } else if (type === 'WhileLoop') {
188
  html += `
@@ -219,6 +243,30 @@
219
  <div class="port port-output" data-type="output"></div>
220
  `;
221
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  node.innerHTML = html;
223
  canvas.appendChild(node);
224
  nodes.push(node);
@@ -228,9 +276,11 @@
228
  if (input) input.value = options.inputs[className];
229
  });
230
  }
231
- // When a node is clicked (mousedown), bring it to the top
232
  node.addEventListener('mousedown', (e) => {
233
- if (!e.target.classList.contains('port') && e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') {
 
 
234
  highestZ++;
235
  node.style.zIndex = highestZ;
236
  draggingNode = node;
@@ -248,6 +298,21 @@
248
  });
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  function addArg(funcNode) {
252
  const argCount = funcNode.querySelectorAll('.arg-container').length;
253
  const argDiv = document.createElement('div');
@@ -255,7 +320,6 @@
255
  argDiv.innerHTML = `<input type="text" class="arg-name" placeholder="Arg Name (e.g., arg${argCount + 1})">`;
256
  funcNode.insertBefore(argDiv, funcNode.querySelector('button'));
257
  }
258
-
259
  function addCallArg(callNode) {
260
  const argCount = callNode.querySelectorAll('.arg-container').length;
261
  const argDiv = document.createElement('div');
@@ -263,7 +327,6 @@
263
  argDiv.innerHTML = `<input type="text" class="call-arg" placeholder="Arg Value (e.g., ${argCount + 1})">`;
264
  callNode.insertBefore(argDiv, callNode.querySelector('button'));
265
  }
266
-
267
  function addCase(switchNode) {
268
  const caseCount = switchNode.querySelectorAll('.case-container').length;
269
  const caseDiv = document.createElement('div');
@@ -280,7 +343,6 @@
280
  draggingPort = newPort;
281
  });
282
  }
283
-
284
  function addArrayElement(arrayNode) {
285
  const container = arrayNode.querySelector('.elements-container');
286
  const input = document.createElement('input');
@@ -291,7 +353,7 @@
291
  }
292
 
293
  // ----------------------------
294
- // Dragging and connection handling with requestAnimationFrame
295
  // ----------------------------
296
  document.addEventListener('mousemove', (e) => {
297
  if (draggingNode) {
@@ -337,12 +399,10 @@
337
  if (draggingNode) draggingNode = null;
338
  if (draggingPort) {
339
  const targetPort = e.target.closest('.port');
340
- if (
341
- targetPort &&
342
- targetPort !== draggingPort &&
343
- draggingPort.dataset.type === "output" &&
344
- targetPort.dataset.type === "input"
345
- ) {
346
  connections.push({ from: draggingPort, to: targetPort });
347
  updateConnections();
348
  }
@@ -373,7 +433,7 @@
373
  }
374
 
375
  // ----------------------------
376
- // Expression evaluation and error marking
377
  // ----------------------------
378
  function evaluateExpression(expr) {
379
  if (!expr) return null;
@@ -399,14 +459,272 @@
399
  }
400
 
401
  // ----------------------------
402
- // Global workflow execution functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  // ----------------------------
404
  function executeNext(node, branch, loopControl, funcContext) {
405
  let portClass;
406
  if (node.dataset.type === 'Switch') {
407
  portClass = `.port-output[data-case="${branch}"]`;
408
  } else {
409
- portClass = branch === 'true' ? '.port-output-true' : branch === 'false' ? '.port-output-false' : '.port-output';
 
410
  }
411
  const nextConn = connections.find(c => c.from === node.querySelector(portClass));
412
  if (nextConn && (!funcContext || !funcContext.returned)) {
@@ -473,7 +791,62 @@
473
  workflowOutput += `Function ${callName} not found!\n`;
474
  }
475
  executeNext(node, 'output', loopControl, funcContext);
476
- } else if (type === 'If') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  const condition = node.querySelector('.condition').value;
478
  let result = false;
479
  try {
@@ -571,12 +944,9 @@
571
  variables = {};
572
  functionReturns = {};
573
  nodes.forEach(n => delete n.dataset.executed);
574
- const startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
575
- if (startNodes.length > 0) {
576
- startNodes.forEach(node => executeNode(node));
577
- } else {
578
- nodes.forEach(node => executeNode(node));
579
- }
580
  alert(workflowOutput || 'No output generated! Check your connections and node settings.');
581
  }
582
 
@@ -586,11 +956,11 @@
586
  function startStepExecution() {
587
  executionQueue = [];
588
  nodes.forEach(n => delete n.dataset.executedStep);
589
- const startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
 
590
  executionQueue.push(...startNodes);
591
  alert("Step execution started. Click 'Step Execution' to execute the next node. Check the console for output.");
592
  }
593
-
594
  function stepExecution() {
595
  if (executionQueue.length === 0) {
596
  alert("Execution finished.");
@@ -602,7 +972,6 @@
602
  executionQueue.push(...nextNodes.filter(n => n));
603
  setTimeout(() => { node.classList.remove('executing'); }, 500);
604
  }
605
-
606
  function executeNodeStep(node) {
607
  let nextNodes = [];
608
  const type = node.dataset.type;
@@ -658,16 +1027,14 @@
658
  }
659
  return nextNodes.filter(n => n);
660
  }
661
-
662
  function getNextNode(node) {
663
  const port = node.querySelector('.port.port-output');
664
  const conn = connections.find(c => c.from === port);
665
- if (conn) return conn.to.parentElement;
666
- return null;
667
  }
668
 
669
  // ----------------------------
670
- // Workflow persistence
671
  // ----------------------------
672
  function getPortIdentifier(port) {
673
  return Array.from(port.classList).find(cls => cls.startsWith("port-"));
@@ -726,18 +1093,265 @@
726
  }
727
  }
728
 
729
- // // ----------------------------
730
- // // Zooming support
731
- // // ----------------------------
732
- // canvas.addEventListener('wheel', (e) => {
733
- // e.preventDefault();
734
- // if (e.deltaY < 0) {
735
- // zoomFactor *= 1.1;
736
- // } else {
737
- // zoomFactor /= 1.1;
738
- // }
739
- // canvas.style.transform = `scale(${zoomFactor})`;
740
- // });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
  </script>
742
  </body>
743
  </html>
 
23
  cursor: move;
24
  user-select: none;
25
  box-sizing: border-box;
 
26
  transition: box-shadow 0.1s ease;
27
  }
28
  .node.executing { border: 3px solid orange; }
29
  .node.error { border-color: red; }
30
+ .node-header {
31
+ font-weight: bold;
32
+ margin-bottom: 5px;
33
+ position: relative;
34
+ }
35
+ .remove-node {
36
+ position: absolute;
37
+ right: 0;
38
+ top: 0;
39
+ cursor: pointer;
40
+ color: red;
41
+ font-weight: bold;
42
+ padding: 0 4px;
43
+ user-select: none;
44
+ }
45
  .port {
46
  width: 10px;
47
  height: 10px;
 
82
  </head>
83
  <body>
84
  <div id="toolbar">
85
+ <!-- Existing Nodes -->
86
  <button onclick="addNode('Variable')">Variable</button>
87
  <button onclick="addNode('Assign')">Assign</button>
88
  <button onclick="addNode('Print')">Print</button>
 
99
  <button onclick="addNode('Multiply')">Multiply</button>
100
  <button onclick="addNode('Divide')">Divide</button>
101
  <button onclick="addNode('Array')">Array</button>
102
+ <button onclick="addNode('Library')">Import Library</button>
103
+ <!-- New Nodes -->
104
+ <button onclick="addNode('Comment')">Comment</button>
105
+ <button onclick="addNode('ForLoop')">For Loop</button>
106
  <br>
107
  <button onclick="runWorkflow()">Run Workflow</button>
108
  <button onclick="startStepExecution()">Start Step Execution</button>
109
  <button onclick="stepExecution()">Step Execution</button>
110
  <button onclick="saveWorkflow()">Save Workflow</button>
111
  <button onclick="loadWorkflow()">Load Workflow</button>
112
+ <button onclick="groupSimilarNodes()">Group Similar Nodes</button>
113
+ <button onclick="exportCode()">Export Code</button>
114
  </div>
115
  <div id="canvas">
116
  <svg id="connections"></svg>
117
  </div>
118
 
119
  <script>
120
+ // Global variables
121
  const canvas = document.getElementById('canvas');
122
  const connectionsSvg = document.getElementById('connections');
123
  let nodes = [];
124
  let connections = [];
125
  let draggingNode = null;
126
  let draggingPort = null;
127
+ let tempLine = null;
128
  let offsetX, offsetY;
129
  let variables = {};
130
  let functionReturns = {};
 
134
  let workflowOutput = "";
135
  let executionQueue = [];
136
  const controlNodes = ["If", "WhileLoop", "Switch", "Break", "Continue", "Function", "Return"];
137
+ let highestZ = 100;
138
 
139
  // ----------------------------
140
  // Node creation and UI helpers
 
144
  node.className = 'node';
145
  node.dataset.type = type;
146
  node.dataset.id = nodeIdCounter++;
 
147
  const left = options.left || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
148
  const top = options.top || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
149
  node.style.left = `${left}px`;
150
  node.style.top = `${top}px`;
 
151
  highestZ++;
152
  node.style.zIndex = highestZ;
153
+
154
+ // Add a remove button inside the header
155
+ let html = `<div class="node-header">${type}<span class="remove-node" onclick="removeNode(this)">×</span></div>`;
156
+ // Node types and their inner HTML
157
  if (type === 'Variable') {
158
  html += `
159
  <input type="text" class="var-name" placeholder="Variable Name">
 
201
  html += `
202
  <input type="text" class="condition" placeholder="Condition (e.g., x > 5)">
203
  <div class="port port-input" data-type="input"></div>
204
+ <div class="port port-output-true" data-type="output" data-branch="true">
205
+ <span class="label">True</span>
206
+ </div>
207
+ <div class="port port-output-false" data-type="output" data-branch="false">
208
+ <span class="label">False</span>
209
+ </div>
210
  `;
211
  } else if (type === 'WhileLoop') {
212
  html += `
 
243
  <div class="port port-output" data-type="output"></div>
244
  `;
245
  }
246
+ // New: Library Node
247
+ else if (type === 'Library') {
248
+ html += `
249
+ <input type="text" class="lib-url" placeholder="Library (e.g., os)">
250
+ <div class="port port-output" data-type="output"></div>
251
+ `;
252
+ }
253
+ // New: Comment Node
254
+ else if (type === 'Comment') {
255
+ html += `
256
+ <input type="text" class="comment-text" placeholder="Enter comment">
257
+ <div class="port port-output" data-type="output"></div>
258
+ `;
259
+ }
260
+ // New: For Loop Node
261
+ else if (type === 'ForLoop') {
262
+ html += `
263
+ <input type="text" class="init" placeholder="Initialization (e.g., let i = 0)">
264
+ <input type="text" class="condition" placeholder="Condition (e.g., i < 10)">
265
+ <input type="text" class="update" placeholder="Update (e.g., i++)">
266
+ <div class="port port-input" data-type="input"></div>
267
+ <div class="port port-output" data-type="output"></div>
268
+ `;
269
+ }
270
  node.innerHTML = html;
271
  canvas.appendChild(node);
272
  nodes.push(node);
 
276
  if (input) input.value = options.inputs[className];
277
  });
278
  }
279
+ // Bring node to front on mousedown
280
  node.addEventListener('mousedown', (e) => {
281
+ if (!e.target.classList.contains('port') &&
282
+ e.target.tagName !== 'INPUT' &&
283
+ e.target.tagName !== 'BUTTON') {
284
  highestZ++;
285
  node.style.zIndex = highestZ;
286
  draggingNode = node;
 
298
  });
299
  }
300
 
301
+ // ----------------------------
302
+ // Remove Node
303
+ // ----------------------------
304
+ function removeNode(removeButton) {
305
+ const node = removeButton.parentElement.parentElement;
306
+ // Remove connections for this node
307
+ connections = connections.filter(conn => conn.from.parentElement !== node && conn.to.parentElement !== node);
308
+ node.remove();
309
+ nodes = nodes.filter(n => n !== node);
310
+ updateConnections();
311
+ }
312
+
313
+ // ----------------------------
314
+ // Additional helper functions for node inputs
315
+ // ----------------------------
316
  function addArg(funcNode) {
317
  const argCount = funcNode.querySelectorAll('.arg-container').length;
318
  const argDiv = document.createElement('div');
 
320
  argDiv.innerHTML = `<input type="text" class="arg-name" placeholder="Arg Name (e.g., arg${argCount + 1})">`;
321
  funcNode.insertBefore(argDiv, funcNode.querySelector('button'));
322
  }
 
323
  function addCallArg(callNode) {
324
  const argCount = callNode.querySelectorAll('.arg-container').length;
325
  const argDiv = document.createElement('div');
 
327
  argDiv.innerHTML = `<input type="text" class="call-arg" placeholder="Arg Value (e.g., ${argCount + 1})">`;
328
  callNode.insertBefore(argDiv, callNode.querySelector('button'));
329
  }
 
330
  function addCase(switchNode) {
331
  const caseCount = switchNode.querySelectorAll('.case-container').length;
332
  const caseDiv = document.createElement('div');
 
343
  draggingPort = newPort;
344
  });
345
  }
 
346
  function addArrayElement(arrayNode) {
347
  const container = arrayNode.querySelector('.elements-container');
348
  const input = document.createElement('input');
 
353
  }
354
 
355
  // ----------------------------
356
+ // Dragging and Connection Handling
357
  // ----------------------------
358
  document.addEventListener('mousemove', (e) => {
359
  if (draggingNode) {
 
399
  if (draggingNode) draggingNode = null;
400
  if (draggingPort) {
401
  const targetPort = e.target.closest('.port');
402
+ if (targetPort &&
403
+ targetPort !== draggingPort &&
404
+ draggingPort.dataset.type === "output" &&
405
+ targetPort.dataset.type === "input") {
 
 
406
  connections.push({ from: draggingPort, to: targetPort });
407
  updateConnections();
408
  }
 
433
  }
434
 
435
  // ----------------------------
436
+ // Expression Evaluation
437
  // ----------------------------
438
  function evaluateExpression(expr) {
439
  if (!expr) return null;
 
459
  }
460
 
461
  // ----------------------------
462
+ // Export Workflow to Code (DFS-based)
463
+ // ----------------------------
464
+ function getChild(node, portSelector) {
465
+ const port = node.querySelector(portSelector);
466
+ if (!port) return null;
467
+ const conn = connections.find(c => c.from === port);
468
+ return conn ? conn.to.parentElement : null;
469
+ }
470
+
471
+ function exportWorkflowToLanguage(language) {
472
+ let visited = new Set();
473
+ let code = "";
474
+
475
+ function exportNode(node, indentLevel) {
476
+ let indent = " ".repeat(indentLevel);
477
+ if (visited.has(node.dataset.id)) return "";
478
+ visited.add(node.dataset.id);
479
+ let nodeCode = "";
480
+ let inputs = {};
481
+ node.querySelectorAll('input').forEach(input => {
482
+ inputs[input.className.split(' ')[0]] = input.value;
483
+ });
484
+ switch(node.dataset.type) {
485
+ case "Variable":
486
+ if (language === "JavaScript") {
487
+ nodeCode += indent + `let ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
488
+ } else if (language === "Python") {
489
+ nodeCode += indent + `${inputs["var-name"]} = ${inputs["var-value"]}\n`;
490
+ } else if (language === "C") {
491
+ nodeCode += indent + `int ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
492
+ }
493
+ break;
494
+ case "Assign":
495
+ nodeCode += indent + inputs["assign-expr"] + (language === "Python" ? "\n" : ";\n");
496
+ break;
497
+ case "Print":
498
+ if (language === "JavaScript") {
499
+ nodeCode += indent + `console.log(${inputs["print-value"]});\n`;
500
+ } else if (language === "Python") {
501
+ nodeCode += indent + `print(${inputs["print-value"]})\n`;
502
+ } else if (language === "C") {
503
+ nodeCode += indent + `printf("%d", ${inputs["print-value"]});\n`;
504
+ }
505
+ break;
506
+ case "Function":
507
+ if (language === "JavaScript") {
508
+ nodeCode += indent + `function ${inputs["func-name"]}(${inputs["arg-name"] || ""}) {\n // function body\n}\n`;
509
+ } else if (language === "Python") {
510
+ nodeCode += indent + `def ${inputs["func-name"]}(${inputs["arg-name"] || ""}):\n # function body\n\n`;
511
+ } else if (language === "C") {
512
+ nodeCode += indent + `void ${inputs["func-name"]}(${inputs["arg-name"] || "void"}) {\n // function body\n}\n`;
513
+ }
514
+ break;
515
+ case "Return":
516
+ nodeCode += indent + (language === "Python" ? "return " : "return ") + inputs["return-value"] + (language === "Python" ? "\n" : ";\n");
517
+ break;
518
+ case "Call":
519
+ if (inputs["call-result"]) {
520
+ if (language === "JavaScript") {
521
+ nodeCode += indent + `let ${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
522
+ } else if (language === "Python") {
523
+ nodeCode += indent + `${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""})\n`;
524
+ } else if (language === "C") {
525
+ nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""}); // assign result to ${inputs["call-result"]}\n`;
526
+ }
527
+ } else {
528
+ nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
529
+ }
530
+ break;
531
+ case "If":
532
+ if (language === "JavaScript" || language === "C") {
533
+ nodeCode += indent + `if (${inputs["condition"]}) {\n`;
534
+ } else if (language === "Python") {
535
+ nodeCode += indent + `if ${inputs["condition"]}:\n`;
536
+ }
537
+ let trueChild = getChild(node, '.port-output-true');
538
+ if (trueChild) {
539
+ nodeCode += exportNode(trueChild, indentLevel + 1);
540
+ }
541
+ if (language === "JavaScript" || language === "C") {
542
+ nodeCode += indent + `} else {\n`;
543
+ } else if (language === "Python") {
544
+ nodeCode += indent + `else:\n`;
545
+ }
546
+ let falseChild = getChild(node, '.port-output-false');
547
+ if (falseChild) {
548
+ nodeCode += exportNode(falseChild, indentLevel + 1);
549
+ }
550
+ if (language === "JavaScript" || language === "C") {
551
+ nodeCode += indent + `}\n`;
552
+ }
553
+ break;
554
+ case "WhileLoop":
555
+ if (language === "JavaScript" || language === "C") {
556
+ nodeCode += indent + `while (${inputs["condition"]}) {\n ${inputs["update"]};\n`;
557
+ } else if (language === "Python") {
558
+ nodeCode += indent + `while ${inputs["condition"]}:\n ${inputs["update"]}\n`;
559
+ }
560
+ let whileChild = getChild(node, '.port-output');
561
+ if (whileChild) {
562
+ nodeCode += exportNode(whileChild, indentLevel + 1);
563
+ }
564
+ if (language === "JavaScript" || language === "C") {
565
+ nodeCode += indent + `}\n`;
566
+ }
567
+ break;
568
+ case "ForLoop":
569
+ if (language === "JavaScript" || language === "C") {
570
+ nodeCode += indent + `for (${inputs["init"]}; ${inputs["condition"]}; ${inputs["update"]}) {\n`;
571
+ } else if (language === "Python") {
572
+ nodeCode += indent + `# For loop converted to while:\n${inputs["init"]}\nwhile ${inputs["condition"]}:\n`;
573
+ }
574
+ let forChild = getChild(node, '.port-output');
575
+ if (forChild) {
576
+ nodeCode += exportNode(forChild, indentLevel + 1);
577
+ }
578
+ if (language === "JavaScript" || language === "C") {
579
+ nodeCode += indent + `}\n`;
580
+ }
581
+ break;
582
+ case "Comment":
583
+ if (language === "JavaScript" || language === "C") {
584
+ nodeCode += indent + `// ${inputs["comment-text"]}\n`;
585
+ } else if (language === "Python") {
586
+ nodeCode += indent + `# ${inputs["comment-text"]}\n`;
587
+ }
588
+ break;
589
+ case "Library":
590
+ if (language === "JavaScript") {
591
+ nodeCode += indent + `import * as lib from '${inputs["lib-url"]}';\n`;
592
+ } else if (language === "Python") {
593
+ nodeCode += indent + `import ${inputs["lib-url"]}\n`;
594
+ } else if (language === "C") {
595
+ nodeCode += indent + `#include <${inputs["lib-url"]}>\n`;
596
+ }
597
+ break;
598
+ default:
599
+ nodeCode += indent + `// Unhandled node type: ${node.dataset.type}\n`;
600
+ }
601
+ // For non-control nodes, append the next node if available
602
+ if (node.dataset.type !== "If" && node.dataset.type !== "WhileLoop" && node.dataset.type !== "ForLoop") {
603
+ let child = getChild(node, '.port-output');
604
+ if (child) {
605
+ nodeCode += exportNode(child, indentLevel);
606
+ }
607
+ }
608
+ return nodeCode;
609
+ }
610
+
611
+ // Find starting nodes (with no incoming connection)
612
+ let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
613
+ startNodes.forEach(node => {
614
+ code += exportNode(node, 0);
615
+ });
616
+ return code;
617
+ }
618
+
619
+ function exportCode() {
620
+ const language = prompt("Enter target language (JavaScript, Python, or C):", "JavaScript");
621
+ if (!language) return;
622
+ const code = exportWorkflowToLanguage(language);
623
+ const codeWindow = window.open("", "Exported Code", "width=600,height=400");
624
+ if (codeWindow) {
625
+ codeWindow.document.write("<pre>" + code + "</pre>");
626
+ } else {
627
+ alert("Exported Code:\n" + code);
628
+ }
629
+ }
630
+
631
+ // ----------------------------
632
+ // Group Similar Nodes
633
+ // ----------------------------
634
+ function groupSimilarNodes() {
635
+ const groups = {};
636
+ nodes.forEach(node => {
637
+ const type = node.dataset.type;
638
+ if (!groups[type]) {
639
+ groups[type] = [];
640
+ }
641
+ groups[type].push(node);
642
+ });
643
+ let xOffset = 20;
644
+ const yStart = 20;
645
+ const xGap = 280;
646
+ for (const type in groups) {
647
+ let yOffset = yStart;
648
+ groups[type].forEach(node => {
649
+ node.style.left = `${xOffset}px`;
650
+ node.style.top = `${yOffset}px`;
651
+ yOffset += node.offsetHeight + 20;
652
+ });
653
+ xOffset += xGap;
654
+ }
655
+ updateConnections();
656
+ }
657
+
658
+ // ----------------------------
659
+ // Workflow Persistence (Save/Load)
660
+ // ----------------------------
661
+ function getPortIdentifier(port) {
662
+ return Array.from(port.classList).find(cls => cls.startsWith("port-"));
663
+ }
664
+ function saveWorkflow() {
665
+ const workflow = {
666
+ nodes: nodes.map(n => {
667
+ const inputs = {};
668
+ n.querySelectorAll('input').forEach(input => {
669
+ inputs[input.className.split(' ')[0]] = input.value;
670
+ });
671
+ return {
672
+ id: n.dataset.id,
673
+ type: n.dataset.type,
674
+ left: n.style.left,
675
+ top: n.style.top,
676
+ inputs: inputs
677
+ };
678
+ }),
679
+ connections: connections.map(c => {
680
+ return {
681
+ fromNodeId: c.from.parentElement.dataset.id,
682
+ fromPortClass: getPortIdentifier(c.from),
683
+ toNodeId: c.to.parentElement.dataset.id,
684
+ toPortClass: getPortIdentifier(c.to)
685
+ };
686
+ })
687
+ };
688
+ prompt("Copy your workflow JSON:", JSON.stringify(workflow));
689
+ }
690
+ function loadWorkflow() {
691
+ const data = prompt("Paste your workflow JSON:");
692
+ if (!data) return;
693
+ try {
694
+ const workflow = JSON.parse(data);
695
+ nodes = [];
696
+ connections = [];
697
+ canvas.querySelectorAll('.node').forEach(n => n.remove());
698
+ workflow.nodes.forEach(nData => {
699
+ addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
700
+ });
701
+ workflow.connections.forEach(conn => {
702
+ const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
703
+ const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
704
+ if (fromNode && toNode) {
705
+ const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
706
+ const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
707
+ if (fromPort && toPort) {
708
+ connections.push({ from: fromPort, to: toPort });
709
+ }
710
+ }
711
+ });
712
+ updateConnections();
713
+ } catch (e) {
714
+ alert("Invalid JSON data.");
715
+ }
716
+ }
717
+
718
+ // ----------------------------
719
+ // Workflow Execution Functions
720
  // ----------------------------
721
  function executeNext(node, branch, loopControl, funcContext) {
722
  let portClass;
723
  if (node.dataset.type === 'Switch') {
724
  portClass = `.port-output[data-case="${branch}"]`;
725
  } else {
726
+ portClass = branch === 'true' ? '.port-output-true' :
727
+ branch === 'false' ? '.port-output-false' : '.port-output';
728
  }
729
  const nextConn = connections.find(c => c.from === node.querySelector(portClass));
730
  if (nextConn && (!funcContext || !funcContext.returned)) {
 
791
  workflowOutput += `Function ${callName} not found!\n`;
792
  }
793
  executeNext(node, 'output', loopControl, funcContext);
794
+ }
795
+ // Library Node
796
+ else if (type === 'Library') {
797
+ const libUrl = node.querySelector('.lib-url').value;
798
+ if (libUrl) {
799
+ let script = document.createElement('script');
800
+ script.src = libUrl;
801
+ script.onload = () => {
802
+ workflowOutput += `Library loaded: ${libUrl}\n`;
803
+ executeNext(node, 'output', loopControl, funcContext);
804
+ };
805
+ script.onerror = () => {
806
+ markError(node, `Failed to load library: ${libUrl}`);
807
+ workflowOutput += `Failed to load library: ${libUrl}\n`;
808
+ executeNext(node, 'output', loopControl, funcContext);
809
+ };
810
+ document.head.appendChild(script);
811
+ return;
812
+ } else {
813
+ executeNext(node, 'output', loopControl, funcContext);
814
+ }
815
+ }
816
+ // Comment Node
817
+ else if (type === 'Comment') {
818
+ executeNext(node, 'output', loopControl, funcContext);
819
+ }
820
+ // For Loop Node
821
+ else if (type === 'ForLoop') {
822
+ const init = node.querySelector('.init').value;
823
+ const condition = node.querySelector('.condition').value;
824
+ const update = node.querySelector('.update').value;
825
+ evaluateExpression(init);
826
+ let result = true;
827
+ while (result && !loopControl.break) {
828
+ try {
829
+ let evalCondition = condition;
830
+ for (const [varName, varValue] of Object.entries(variables)) {
831
+ evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
832
+ }
833
+ result = eval(evalCondition);
834
+ if (result) {
835
+ let innerControl = { break: false, continue: false };
836
+ executeNext(node, 'output', innerControl, funcContext);
837
+ if (innerControl.continue) { evaluateExpression(update); continue; }
838
+ if (innerControl.break) break;
839
+ const error = evaluateExpression(update);
840
+ if (error) workflowOutput += `${error}\n`;
841
+ }
842
+ } catch (e) {
843
+ workflowOutput += `Error in ForLoop condition: ${e.message}\n`;
844
+ break;
845
+ }
846
+ }
847
+ executeNext(node, 'output', loopControl, funcContext);
848
+ }
849
+ else if (type === 'If') {
850
  const condition = node.querySelector('.condition').value;
851
  let result = false;
852
  try {
 
944
  variables = {};
945
  functionReturns = {};
946
  nodes.forEach(n => delete n.dataset.executed);
947
+ let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
948
+ if (startNodes.length === 0) startNodes = nodes;
949
+ startNodes.forEach(node => executeNode(node));
 
 
 
950
  alert(workflowOutput || 'No output generated! Check your connections and node settings.');
951
  }
952
 
 
956
  function startStepExecution() {
957
  executionQueue = [];
958
  nodes.forEach(n => delete n.dataset.executedStep);
959
+ let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
960
+ if (startNodes.length === 0) startNodes = nodes;
961
  executionQueue.push(...startNodes);
962
  alert("Step execution started. Click 'Step Execution' to execute the next node. Check the console for output.");
963
  }
 
964
  function stepExecution() {
965
  if (executionQueue.length === 0) {
966
  alert("Execution finished.");
 
972
  executionQueue.push(...nextNodes.filter(n => n));
973
  setTimeout(() => { node.classList.remove('executing'); }, 500);
974
  }
 
975
  function executeNodeStep(node) {
976
  let nextNodes = [];
977
  const type = node.dataset.type;
 
1027
  }
1028
  return nextNodes.filter(n => n);
1029
  }
 
1030
  function getNextNode(node) {
1031
  const port = node.querySelector('.port.port-output');
1032
  const conn = connections.find(c => c.from === port);
1033
+ return conn ? conn.to.parentElement : null;
 
1034
  }
1035
 
1036
  // ----------------------------
1037
+ // Workflow Persistence (Save/Load)
1038
  // ----------------------------
1039
  function getPortIdentifier(port) {
1040
  return Array.from(port.classList).find(cls => cls.startsWith("port-"));
 
1093
  }
1094
  }
1095
 
1096
+ // ----------------------------
1097
+ // Export Workflow to Code (with indentation)
1098
+ // ----------------------------
1099
+ function exportWorkflowToLanguage(language) {
1100
+ let visited = new Set();
1101
+ let code = "";
1102
+ function exportNode(node, indentLevel) {
1103
+ let indent = " ".repeat(indentLevel);
1104
+ if (visited.has(node.dataset.id)) return "";
1105
+ visited.add(node.dataset.id);
1106
+ let nodeCode = "";
1107
+ let inputs = {};
1108
+ node.querySelectorAll('input').forEach(input => {
1109
+ inputs[input.className.split(' ')[0]] = input.value;
1110
+ });
1111
+ switch(node.dataset.type) {
1112
+ case "Variable":
1113
+ if (language === "JavaScript") {
1114
+ nodeCode += indent + `let ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
1115
+ } else if (language === "Python") {
1116
+ nodeCode += indent + `${inputs["var-name"]} = ${inputs["var-value"]}\n`;
1117
+ } else if (language === "C") {
1118
+ nodeCode += indent + `int ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
1119
+ }
1120
+ break;
1121
+ case "Assign":
1122
+ nodeCode += indent + inputs["assign-expr"] + (language === "Python" ? "\n" : ";\n");
1123
+ break;
1124
+ case "Print":
1125
+ if (language === "JavaScript") {
1126
+ nodeCode += indent + `console.log(${inputs["print-value"]});\n`;
1127
+ } else if (language === "Python") {
1128
+ nodeCode += indent + `print(${inputs["print-value"]})\n`;
1129
+ } else if (language === "C") {
1130
+ nodeCode += indent + `printf("%d", ${inputs["print-value"]});\n`;
1131
+ }
1132
+ break;
1133
+ case "Function":
1134
+ if (language === "JavaScript") {
1135
+ nodeCode += indent + `function ${inputs["func-name"]}(${inputs["arg-name"] || ""}) {\n // function body\n}\n`;
1136
+ } else if (language === "Python") {
1137
+ nodeCode += indent + `def ${inputs["func-name"]}(${inputs["arg-name"] || ""}):\n # function body\n\n`;
1138
+ } else if (language === "C") {
1139
+ nodeCode += indent + `void ${inputs["func-name"]}(${inputs["arg-name"] || "void"}) {\n // function body\n}\n`;
1140
+ }
1141
+ break;
1142
+ case "Return":
1143
+ nodeCode += indent + (language === "Python" ? "return " : "return ") + inputs["return-value"] + (language === "Python" ? "\n" : ";\n");
1144
+ break;
1145
+ case "Call":
1146
+ if (inputs["call-result"]) {
1147
+ if (language === "JavaScript") {
1148
+ nodeCode += indent + `let ${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
1149
+ } else if (language === "Python") {
1150
+ nodeCode += indent + `${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""})\n`;
1151
+ } else if (language === "C") {
1152
+ nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""}); // assign result to ${inputs["call-result"]}\n`;
1153
+ }
1154
+ } else {
1155
+ nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
1156
+ }
1157
+ break;
1158
+ case "If":
1159
+ if (language === "JavaScript" || language === "C") {
1160
+ nodeCode += indent + `if (${inputs["condition"]}) {\n`;
1161
+ } else if (language === "Python") {
1162
+ nodeCode += indent + `if ${inputs["condition"]}:\n`;
1163
+ }
1164
+ let trueChild = getChild(node, '.port-output-true');
1165
+ if (trueChild) {
1166
+ nodeCode += exportNode(trueChild, indentLevel + 1);
1167
+ }
1168
+ if (language === "JavaScript" || language === "C") {
1169
+ nodeCode += indent + `} else {\n`;
1170
+ } else if (language === "Python") {
1171
+ nodeCode += indent + `else:\n`;
1172
+ }
1173
+ let falseChild = getChild(node, '.port-output-false');
1174
+ if (falseChild) {
1175
+ nodeCode += exportNode(falseChild, indentLevel + 1);
1176
+ }
1177
+ if (language === "JavaScript" || language === "C") {
1178
+ nodeCode += indent + `}\n`;
1179
+ }
1180
+ break;
1181
+ case "WhileLoop":
1182
+ if (language === "JavaScript" || language === "C") {
1183
+ nodeCode += indent + `while (${inputs["condition"]}) {\n ${inputs["update"]};\n`;
1184
+ } else if (language === "Python") {
1185
+ nodeCode += indent + `while ${inputs["condition"]}:\n ${inputs["update"]}\n`;
1186
+ }
1187
+ let whileChild = getChild(node, '.port-output');
1188
+ if (whileChild) {
1189
+ nodeCode += exportNode(whileChild, indentLevel + 1);
1190
+ }
1191
+ if (language === "JavaScript" || language === "C") {
1192
+ nodeCode += indent + `}\n`;
1193
+ }
1194
+ break;
1195
+ case "ForLoop":
1196
+ if (language === "JavaScript" || language === "C") {
1197
+ nodeCode += indent + `for (${inputs["init"]}; ${inputs["condition"]}; ${inputs["update"]}) {\n`;
1198
+ } else if (language === "Python") {
1199
+ nodeCode += indent + `# For loop converted to while:\n${inputs["init"]}\nwhile ${inputs["condition"]}:\n`;
1200
+ }
1201
+ let forChild = getChild(node, '.port-output');
1202
+ if (forChild) {
1203
+ nodeCode += exportNode(forChild, indentLevel + 1);
1204
+ }
1205
+ if (language === "JavaScript" || language === "C") {
1206
+ nodeCode += indent + `}\n`;
1207
+ }
1208
+ break;
1209
+ case "Comment":
1210
+ if (language === "JavaScript" || language === "C") {
1211
+ nodeCode += indent + `// ${inputs["comment-text"]}\n`;
1212
+ } else if (language === "Python") {
1213
+ nodeCode += indent + `# ${inputs["comment-text"]}\n`;
1214
+ }
1215
+ break;
1216
+ case "Library":
1217
+ if (language === "JavaScript") {
1218
+ nodeCode += indent + `import * as lib from '${inputs["lib-url"]}';\n`;
1219
+ } else if (language === "Python") {
1220
+ nodeCode += indent + `import ${inputs["lib-url"]}\n`;
1221
+ } else if (language === "C") {
1222
+ nodeCode += indent + `#include <${inputs["lib-url"]}>\n`;
1223
+ }
1224
+ break;
1225
+ default:
1226
+ nodeCode += indent + `// Unhandled node type: ${node.dataset.type}\n`;
1227
+ }
1228
+ // For non-control nodes, recursively add the next connected node
1229
+ if (node.dataset.type !== "If" && node.dataset.type !== "WhileLoop" && node.dataset.type !== "ForLoop") {
1230
+ let child = getChild(node, '.port-output');
1231
+ if (child) {
1232
+ nodeCode += exportNode(child, indentLevel);
1233
+ }
1234
+ }
1235
+ return nodeCode;
1236
+ }
1237
+ let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
1238
+ startNodes.forEach(node => {
1239
+ code += exportNode(node, 0);
1240
+ });
1241
+ return code;
1242
+ }
1243
+
1244
+ function exportCode() {
1245
+ const language = prompt("Enter target language (JavaScript, Python, or C):", "JavaScript");
1246
+ if (!language) return;
1247
+ const code = exportWorkflowToLanguage(language);
1248
+ const codeWindow = window.open("", "Exported Code", "width=600,height=400");
1249
+ if (codeWindow) {
1250
+ codeWindow.document.write("<pre>" + code + "</pre>");
1251
+ } else {
1252
+ alert("Exported Code:\n" + code);
1253
+ }
1254
+ }
1255
+
1256
+ // ----------------------------
1257
+ // Group Similar Nodes
1258
+ // ----------------------------
1259
+ function groupSimilarNodes() {
1260
+ const groups = {};
1261
+ nodes.forEach(node => {
1262
+ const type = node.dataset.type;
1263
+ if (!groups[type]) {
1264
+ groups[type] = [];
1265
+ }
1266
+ groups[type].push(node);
1267
+ });
1268
+ let xOffset = 20;
1269
+ const yStart = 20;
1270
+ const xGap = 280;
1271
+ for (const type in groups) {
1272
+ let yOffset = yStart;
1273
+ groups[type].forEach(node => {
1274
+ node.style.left = `${xOffset}px`;
1275
+ node.style.top = `${yOffset}px`;
1276
+ yOffset += node.offsetHeight + 20;
1277
+ });
1278
+ xOffset += xGap;
1279
+ }
1280
+ updateConnections();
1281
+ }
1282
+
1283
+ // ----------------------------
1284
+ // Workflow Persistence (Save/Load)
1285
+ // ----------------------------
1286
+ function getPortIdentifier(port) {
1287
+ return Array.from(port.classList).find(cls => cls.startsWith("port-"));
1288
+ }
1289
+ function saveWorkflow() {
1290
+ const workflow = {
1291
+ nodes: nodes.map(n => {
1292
+ const inputs = {};
1293
+ n.querySelectorAll('input').forEach(input => {
1294
+ inputs[input.className.split(' ')[0]] = input.value;
1295
+ });
1296
+ return {
1297
+ id: n.dataset.id,
1298
+ type: n.dataset.type,
1299
+ left: n.style.left,
1300
+ top: n.style.top,
1301
+ inputs: inputs
1302
+ };
1303
+ }),
1304
+ connections: connections.map(c => {
1305
+ return {
1306
+ fromNodeId: c.from.parentElement.dataset.id,
1307
+ fromPortClass: getPortIdentifier(c.from),
1308
+ toNodeId: c.to.parentElement.dataset.id,
1309
+ toPortClass: getPortIdentifier(c.to)
1310
+ };
1311
+ })
1312
+ };
1313
+ prompt("Copy your workflow JSON:", JSON.stringify(workflow));
1314
+ }
1315
+ function loadWorkflow() {
1316
+ const data = prompt("Paste your workflow JSON:");
1317
+ if (!data) return;
1318
+ try {
1319
+ const workflow = JSON.parse(data);
1320
+ nodes = [];
1321
+ connections = [];
1322
+ canvas.querySelectorAll('.node').forEach(n => n.remove());
1323
+ workflow.nodes.forEach(nData => {
1324
+ addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
1325
+ });
1326
+ workflow.connections.forEach(conn => {
1327
+ const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
1328
+ const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
1329
+ if (fromNode && toNode) {
1330
+ const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
1331
+ const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
1332
+ if (fromPort && toPort) {
1333
+ connections.push({ from: fromPort, to: toPort });
1334
+ }
1335
+ }
1336
+ });
1337
+ updateConnections();
1338
+ } catch (e) {
1339
+ alert("Invalid JSON data.");
1340
+ }
1341
+ }
1342
+
1343
+ // ----------------------------
1344
+ // Zooming Support
1345
+ // ----------------------------
1346
+ canvas.addEventListener('wheel', (e) => {
1347
+ e.preventDefault();
1348
+ if (e.deltaY < 0) {
1349
+ zoomFactor *= 1.1;
1350
+ } else {
1351
+ zoomFactor /= 1.1;
1352
+ }
1353
+ canvas.style.transform = `scale(${zoomFactor})`;
1354
+ });
1355
  </script>
1356
  </body>
1357
  </html>