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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +713 -420
index.html CHANGED
@@ -1,450 +1,743 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Visual Programming Language</title>
7
- <style>
8
- body { margin: 0; font-family: Arial, sans-serif; }
9
- #canvas {
10
- width: 100vw;
11
- height: 100vh;
12
- background: #f0f0f0;
13
- position: relative;
14
- overflow: auto;
15
- }
16
- .node {
17
- position: absolute;
18
- width: 250px;
19
- background: #fff;
20
- border: 2px solid #333;
21
- border-radius: 5px;
22
- padding: 10px;
23
- cursor: move;
24
- user-select: none;
25
- z-index: 1;
26
- }
27
- .node-header { font-weight: bold; margin-bottom: 5px; }
28
- .port {
29
- width: 10px;
30
- height: 10px;
31
- background: #666;
32
- border-radius: 50%;
33
- position: absolute;
34
- cursor: pointer;
35
- }
36
- .port-input { left: -15px; top: 50%; transform: translateY(-50%); }
37
- .port-output { right: -15px; }
38
- .port-output-true { right: -15px; top: 30%; }
39
- .port-output-false { right: -15px; top: 70%; }
40
- #toolbar {
41
- position: fixed;
42
- top: 10px;
43
- left: 10px;
44
- background: #ddd;
45
- padding: 10px;
46
- border-radius: 5px;
47
- z-index: 10;
48
- }
49
- button { margin: 2px; }
50
- #connections {
51
- position: absolute;
52
- top: 0;
53
- left: 0;
54
- width: 100%;
55
- height: 100%;
56
- pointer-events: none;
57
- z-index: 0;
58
- }
59
- input { width: 80%; margin: 5px 0; }
60
- .case-container { margin: 5px 0; display: flex; align-items: center; }
61
- .label { font-size: 12px; color: #555; position: absolute; right: -50px; }
62
- .arg-container { margin: 5px 0; }
63
- </style>
 
 
 
 
 
64
  </head>
65
  <body>
66
- <div id="toolbar">
67
- <button onclick="addNode('Variable')">Variable</button>
68
- <button onclick="addNode('Assign')">Assign</button>
69
- <button onclick="addNode('Print')">Print</button>
70
- <button onclick="addNode('Function')">Function</button>
71
- <button onclick="addNode('Return')">Return</button>
72
- <button onclick="addNode('Call')">Call</button>
73
- <button onclick="addNode('If')">If</button>
74
- <button onclick="addNode('WhileLoop')">While Loop</button>
75
- <button onclick="addNode('Switch')">Switch</button>
76
- <button onclick="addNode('Break')">Break</button>
77
- <button onclick="addNode('Continue')">Continue</button>
78
- <button onclick="runWorkflow()">Run Workflow</button>
79
- </div>
80
- <div id="canvas">
81
- <svg id="connections"></svg>
82
- </div>
 
 
 
 
 
 
 
 
 
 
83
 
84
- <script>
85
- const canvas = document.getElementById('canvas');
86
- const connectionsSvg = document.getElementById('connections');
87
- let nodes = [];
88
- let connections = [];
89
- let draggingNode = null;
90
- let draggingPort = null;
91
- let offsetX, offsetY;
92
- let variables = {};
93
- let functionReturns = {};
 
 
 
 
 
 
 
 
 
94
 
95
- // Add a new node
96
- function addNode(type) {
97
- const node = document.createElement('div');
98
- node.className = 'node';
99
- node.style.left = `${Math.random() * 300 + 50}px`;
100
- node.style.top = `${Math.random() * 300 + 50}px`;
101
- node.dataset.type = type;
 
 
 
 
 
 
 
 
 
102
 
103
- let html = `<div class="node-header">${type}</div>`;
104
- if (type === 'Variable') {
105
- html += `
106
- <input type="text" class="var-name" placeholder="Variable Name">
107
- <input type="text" class="var-value" placeholder="Value">
108
- <div class="port port-output" data-type="output"></div>
109
- `;
110
- } else if (type === 'Assign') {
111
- html += `
112
- <input type="text" class="assign-expr" placeholder="Assignment (e.g., i = i + 1)">
113
- <div class="port port-input" data-type="input"></div>
114
- <div class="port port-output" data-type="output"></div>
115
- `;
116
- } else if (type === 'Print') {
117
- html += `
118
- <input type="text" class="print-value" placeholder="Variable or Text">
119
- <div class="port port-input" data-type="input"></div>
120
- `;
121
- } else if (type === 'Function') {
122
- html += `
123
- <input type="text" class="func-name" placeholder="Function Name">
124
- <div class="arg-container">
125
- <input type="text" class="arg-name" placeholder="Arg Name (e.g., a)">
126
- </div>
127
- <button onclick="addArg(this.parentElement)">Add Arg</button>
128
- <div class="port port-input" data-type="input"></div>
129
- <div class="port port-output" data-type="output"></div>
130
- `;
131
- } else if (type === 'Return') {
132
- html += `
133
- <input type="text" class="return-value" placeholder="Return Value (e.g., x + 1)">
134
- <div class="port port-input" data-type="input"></div>
135
- `;
136
- } else if (type === 'Call') {
137
- html += `
138
- <input type="text" class="call-name" placeholder="Function Name (e.g., add)">
139
- <div class="arg-container">
140
- <input type="text" class="call-arg" placeholder="Arg Value (e.g., 3)">
141
- </div>
142
- <button onclick="addCallArg(this.parentElement)">Add Arg</button>
143
- <input type="text" class="call-result" placeholder="Result Var (e.g., z)">
144
- <div class="port port-input" data-type="input"></div>
145
- <div class="port port-output" data-type="output"></div>
146
- `;
147
- } else if (type === 'If') {
148
- html += `
149
- <input type="text" class="condition" placeholder="Condition (e.g., x > 5)">
150
- <div class="port port-input" data-type="input"></div>
151
- <div class="port port-output-true" data-type="output" data-branch="true"><span class="label">True</span></div>
152
- <div class="port port-output-false" data-type="output" data-branch="false"><span class="label">False</span></div>
153
- `;
154
- } else if (type === 'WhileLoop') {
155
- html += `
156
- <input type="text" class="condition" placeholder="Condition (e.g., i < 5)">
157
- <input type="text" class="update" placeholder="Update (e.g., i = i + 1)">
158
- <div class="port port-input" data-type="input"></div>
159
- <div class="port port-output" data-type="output"></div>
160
- `;
161
- } else if (type === 'Switch') {
162
- html += `
163
- <input type="text" class="switch-var" placeholder="Variable to Switch">
164
- <div class="case-container">
165
- <input type="text" class="case-value" placeholder="Case Value (e.g., 1)">
166
- <input type="checkbox" class="case-break" title="Break after this case">
167
- <div class="port port-output" data-type="output" data-case="0"></div>
168
- </div>
169
- <button onclick="addCase(this.parentElement)">Add Case</button>
170
- <div class="port port-input" data-type="input"></div>
171
- `;
172
- } else if (type === 'Break' || type === 'Continue') {
173
- html += `
174
- <div class="port port-input" data-type="input"></div>
175
- `;
176
- }
177
- node.innerHTML = html;
178
- canvas.appendChild(node);
179
- nodes.push(node);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- // Dragging node
182
- node.addEventListener('mousedown', (e) => {
183
- if (!e.target.classList.contains('port') && e.target.tagName !== 'INPUT' && e.target.tagName !== 'BUTTON') {
184
- draggingNode = node;
185
- const rect = node.getBoundingClientRect();
186
- offsetX = e.clientX - rect.left;
187
- offsetY = e.clientY - rect.top;
188
- }
189
- });
190
 
191
- // Connecting ports
192
- const ports = node.querySelectorAll('.port');
193
- ports.forEach(port => {
194
- port.addEventListener('mousedown', (e) => {
195
- e.stopPropagation();
196
- draggingPort = port;
197
- });
198
- });
199
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- // Add a new argument to Function node
202
- function addArg(funcNode) {
203
- const argCount = funcNode.querySelectorAll('.arg-container').length;
204
- const argDiv = document.createElement('div');
205
- argDiv.className = 'arg-container';
206
- argDiv.innerHTML = `<input type="text" class="arg-name" placeholder="Arg Name (e.g., arg${argCount + 1})">`;
207
- funcNode.insertBefore(argDiv, funcNode.querySelector('button'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }
 
 
209
 
210
- // Add a new argument to Call node
211
- function addCallArg(callNode) {
212
- const argCount = callNode.querySelectorAll('.arg-container').length;
213
- const argDiv = document.createElement('div');
214
- argDiv.className = 'arg-container';
215
- argDiv.innerHTML = `<input type="text" class="call-arg" placeholder="Arg Value (e.g., ${argCount + 1})">`;
216
- callNode.insertBefore(argDiv, callNode.querySelector('button'));
 
 
 
 
 
 
 
 
 
 
217
  }
 
 
218
 
219
- // Add a new case to Switch node
220
- function addCase(switchNode) {
221
- const caseCount = switchNode.querySelectorAll('.case-container').length;
222
- const caseDiv = document.createElement('div');
223
- caseDiv.className = 'case-container';
224
- caseDiv.innerHTML = `
225
- <input type="text" class="case-value" placeholder="Case Value (e.g., ${caseCount + 1})">
226
- <input type="checkbox" class="case-break" title="Break after this case">
227
- <div class="port port-output" data-type="output" data-case="${caseCount}"></div>
228
- `;
229
- switchNode.insertBefore(caseDiv, switchNode.querySelector('button'));
230
- const newPort = caseDiv.querySelector('.port');
231
- newPort.addEventListener('mousedown', (e) => {
232
- e.stopPropagation();
233
- draggingPort = newPort;
234
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
 
 
 
 
 
236
 
237
- // Handle dragging
238
- document.addEventListener('mousemove', (e) => {
239
- if (draggingNode) {
240
- const canvasRect = canvas.getBoundingClientRect();
241
- const x = e.clientX - offsetX - canvasRect.left;
242
- const y = e.clientY - offsetY - canvasRect.top;
243
- draggingNode.style.left = `${x}px`;
244
- draggingNode.style.top = `${y}px`;
245
- updateConnections();
246
- }
247
- });
248
 
249
- // Handle mouse up
250
- document.addEventListener('mouseup', (e) => {
251
- if (draggingNode) draggingNode = null;
252
- if (draggingPort) {
253
- const targetPort = e.target.closest('.port');
254
- if (targetPort && targetPort !== draggingPort &&
255
- draggingPort.dataset.type !== targetPort.dataset.type) {
256
- connections.push({ from: draggingPort, to: targetPort });
257
- updateConnections();
258
- }
259
- draggingPort = null;
260
- }
261
- });
 
 
 
262
 
263
- // Update connection lines
264
- function updateConnections() {
265
- connectionsSvg.innerHTML = '';
266
- const canvasRect = canvas.getBoundingClientRect();
267
- connections.forEach(conn => {
268
- const fromRect = conn.from.getBoundingClientRect();
269
- const toRect = conn.to.getBoundingClientRect();
270
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
271
- line.setAttribute('x1', fromRect.left + fromRect.width / 2 - canvasRect.left);
272
- line.setAttribute('y1', fromRect.top + fromRect.height / 2 - canvasRect.top);
273
- line.setAttribute('x2', toRect.left + toRect.width / 2 - canvasRect.left);
274
- line.setAttribute('y2', toRect.top + toRect.height / 2 - canvasRect.top);
275
- line.setAttribute('stroke', '#333');
276
- line.setAttribute('stroke-width', '2');
277
- connectionsSvg.appendChild(line);
278
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  }
280
-
281
- // Evaluate an expression
282
- function evaluateExpression(expr) {
283
- if (!expr) return null;
284
- try {
285
- let evalExpr = expr;
286
- for (const [varName, varValue] of Object.entries(variables)) {
287
- evalExpr = evalExpr.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
288
- }
289
- if (evalExpr.includes('=')) {
290
- const [varName, value] = evalExpr.split('=').map(s => s.trim());
291
- variables[varName] = eval(value);
292
- return null;
293
- }
294
- return eval(evalExpr);
295
- } catch (e) {
296
- return `Error in expression: ${e.message}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- // Run the workflow
301
- function runWorkflow() {
302
- let output = '';
303
- variables = {};
304
- functionReturns = {};
305
- nodes.forEach(n => delete n.dataset.executed);
 
 
 
 
 
 
 
306
 
307
- function executeNode(node, loopControl = { break: false, continue: false }, funcContext = null) {
308
- if (node.dataset.executed || loopControl.break) return;
309
- node.dataset.executed = true;
 
 
 
 
 
 
 
310
 
311
- const type = node.dataset.type;
312
- if (type === 'Variable') {
313
- const name = node.querySelector('.var-name').value;
314
- const value = node.querySelector('.var-value').value;
315
- variables[name] = isNaN(value) ? value : Number(value);
316
- executeNext(node, 'output', loopControl, funcContext);
317
- } else if (type === 'Assign') {
318
- const expr = node.querySelector('.assign-expr').value;
319
- const error = evaluateExpression(expr);
320
- if (error) output += `${error}\n`;
321
- executeNext(node, 'output', loopControl, funcContext);
322
- } else if (type === 'Print') {
323
- const value = node.querySelector('.print-value').value;
324
- const printVal = variables[value] !== undefined ? variables[value] : value;
325
- output += `${printVal}\n`;
326
- } else if (type === 'Function') {
327
- const funcName = node.querySelector('.func-name').value;
328
- const args = Array.from(node.querySelectorAll('.arg-name')).map(input => input.value);
329
- const prevVars = { ...variables };
330
- funcContext = { name: funcName, args: {}, returned: false };
331
- executeNext(node, 'output', loopControl, funcContext);
332
- variables = prevVars;
333
- if (!funcContext.returned) functionReturns[funcName] = undefined;
334
- } else if (type === 'Return') {
335
- if (funcContext) {
336
- const returnValue = node.querySelector('.return-value').value;
337
- const result = evaluateExpression(returnValue);
338
- if (result && !result.startsWith('Error')) {
339
- functionReturns[funcContext.name] = result;
340
- } else if (result) {
341
- output += `${result}\n`;
342
- }
343
- funcContext.returned = true;
344
- }
345
- } else if (type === 'Call') {
346
- const callName = node.querySelector('.call-name').value;
347
- const callArgs = Array.from(node.querySelectorAll('.call-arg')).map(input => evaluateExpression(input.value) || input.value);
348
- const resultVar = node.querySelector('.call-result').value;
349
- const targetFunc = nodes.find(n => n.dataset.type === 'Function' && n.querySelector('.func-name').value === callName);
350
- if (targetFunc) {
351
- const funcArgs = Array.from(targetFunc.querySelectorAll('.arg-name')).map(input => input.value);
352
- const prevVars = { ...variables };
353
- funcArgs.forEach((argName, index) => {
354
- variables[argName] = callArgs[index] !== undefined ? callArgs[index] : undefined;
355
- });
356
- const funcContext = { name: callName, args: {}, returned: false };
357
- executeNode(targetFunc, { break: false, continue: false }, funcContext);
358
- variables = prevVars;
359
- if (resultVar && functionReturns[callName] !== undefined) {
360
- variables[resultVar] = functionReturns[callName];
361
- }
362
- } else {
363
- output += `Function ${callName} not found!\n`;
364
- }
365
- executeNext(node, 'output', loopControl, funcContext);
366
- } else if (type === 'If') {
367
- const condition = node.querySelector('.condition').value;
368
- let result = false;
369
- try {
370
- let evalCondition = condition;
371
- for (const [varName, varValue] of Object.entries(variables)) {
372
- evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
373
- }
374
- result = eval(evalCondition);
375
- } catch (e) {
376
- output += `Error in If condition: ${e.message}\n`;
377
- }
378
- executeNext(node, result ? 'true' : 'false', loopControl, funcContext);
379
- } else if (type === 'WhileLoop') {
380
- const condition = node.querySelector('.condition').value;
381
- const update = node.querySelector('.update').value;
382
- let result = true;
383
- while (result && !loopControl.break) {
384
- try {
385
- let evalCondition = condition;
386
- for (const [varName, varValue] of Object.entries(variables)) {
387
- evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
388
- }
389
- result = eval(evalCondition);
390
- if (result) {
391
- let innerControl = { break: false, continue: false };
392
- executeNext(node, 'output', innerControl, funcContext);
393
- if (innerControl.continue) continue;
394
- if (innerControl.break) break;
395
- const error = evaluateExpression(update);
396
- if (error) output += `${error}\n`;
397
- }
398
- } catch (e) {
399
- output += `Error in WhileLoop condition: ${e.message}\n`;
400
- break;
401
- }
402
- }
403
- } else if (type === 'Switch') {
404
- const switchVar = node.querySelector('.switch-var').value;
405
- const caseInputs = node.querySelectorAll('.case-container');
406
- const value = variables[switchVar];
407
- let matched = false;
408
- for (let [index, container] of caseInputs.entries()) {
409
- const caseValue = container.querySelector('.case-value').value;
410
- const shouldBreak = container.querySelector('.case-break').checked;
411
- if (caseValue == value || matched) {
412
- matched = true;
413
- executeNext(node, index.toString(), loopControl, funcContext);
414
- if (shouldBreak) break;
415
- }
416
- }
417
- if (!matched) output += `No matching case for ${switchVar}=${value}\n`;
418
- } else if (type === 'Break') {
419
- loopControl.break = true;
420
- } else if (type === 'Continue') {
421
- loopControl.continue = true;
422
- }
423
- }
424
 
425
- function executeNext(node, branch, loopControl, funcContext) {
426
- let portClass;
427
- if (node.dataset.type === 'Switch') {
428
- portClass = `.port-output[data-case="${branch}"]`;
429
- } else {
430
- portClass = branch === 'true' ? '.port-output-true' : branch === 'false' ? '.port-output-false' : '.port-output';
431
- }
432
- const nextConn = connections.find(c => c.from === node.querySelector(portClass));
433
- if (nextConn && (!funcContext || !funcContext.returned)) {
434
- const nextNode = nextConn.to.parentElement;
435
- executeNode(nextNode, loopControl, funcContext);
436
- }
437
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
- const startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
440
- if (startNodes.length > 0) {
441
- startNodes.forEach(node => executeNode(node));
442
- } else {
443
- nodes.forEach(node => executeNode(node));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
 
 
 
 
 
 
 
445
 
446
- alert(output || 'No output generated! Check your connections and node settings.');
447
- }
448
- </script>
 
 
 
 
 
 
 
 
 
 
449
  </body>
450
- </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Visual Programming Language</title>
7
+ <style>
8
+ body { margin: 0; font-family: Arial, sans-serif; }
9
+ #canvas {
10
+ width: 100vw;
11
+ height: 100vh;
12
+ background: #f0f0f0;
13
+ position: relative;
14
+ overflow: auto;
15
+ }
16
+ .node {
17
+ position: absolute;
18
+ width: 250px;
19
+ background: #fff;
20
+ border: 2px solid #333;
21
+ border-radius: 5px;
22
+ padding: 10px;
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;
35
+ background: #666;
36
+ border-radius: 50%;
37
+ position: absolute;
38
+ cursor: pointer;
39
+ }
40
+ .port-input { left: -15px; top: 50%; transform: translateY(-50%); }
41
+ .port-output { right: -15px; top: 50%; transform: translateY(-50%); }
42
+ .port-output-true { right: -15px; top: 30%; }
43
+ .port-output-false { right: -15px; top: 70%; }
44
+ #toolbar {
45
+ position: fixed;
46
+ top: 10px;
47
+ left: 10px;
48
+ background: #ddd;
49
+ padding: 10px;
50
+ border-radius: 5px;
51
+ z-index: 1000;
52
+ }
53
+ button { margin: 2px; }
54
+ #connections {
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ width: 100%;
59
+ height: 100%;
60
+ pointer-events: none;
61
+ z-index: 0;
62
+ }
63
+ input { width: 80%; margin: 5px 0; }
64
+ .case-container { margin: 5px 0; display: flex; align-items: center; }
65
+ .label { font-size: 12px; color: #555; position: absolute; right: -50px; }
66
+ .arg-container { margin: 5px 0; }
67
+ .elements-container input { width: 70%; margin: 3px 0; }
68
+ </style>
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>
75
+ <button onclick="addNode('Function')">Function</button>
76
+ <button onclick="addNode('Return')">Return</button>
77
+ <button onclick="addNode('Call')">Call</button>
78
+ <button onclick="addNode('If')">If</button>
79
+ <button onclick="addNode('WhileLoop')">While Loop</button>
80
+ <button onclick="addNode('Switch')">Switch</button>
81
+ <button onclick="addNode('Break')">Break</button>
82
+ <button onclick="addNode('Continue')">Continue</button>
83
+ <button onclick="addNode('Add')">Add</button>
84
+ <button onclick="addNode('Subtract')">Subtract</button>
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 = {};
111
+ let nodeIdCounter = 0;
112
+ const gridSize = 20;
113
+ let zoomFactor = 1.0;
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
121
+ // ----------------------------
122
+ function addNode(type, options = {}) {
123
+ const node = document.createElement('div');
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">
140
+ <input type="text" class="var-value" placeholder="Value">
141
+ <div class="port port-output" data-type="output"></div>
142
+ `;
143
+ } else if (type === 'Assign') {
144
+ html += `
145
+ <input type="text" class="assign-expr" placeholder="Assignment (e.g., i = i + 1)">
146
+ <div class="port port-input" data-type="input"></div>
147
+ <div class="port port-output" data-type="output"></div>
148
+ `;
149
+ } else if (type === 'Print') {
150
+ html += `
151
+ <input type="text" class="print-value" placeholder="Variable or Text">
152
+ <div class="port port-input" data-type="input"></div>
153
+ `;
154
+ } else if (type === 'Function') {
155
+ html += `
156
+ <input type="text" class="func-name" placeholder="Function Name">
157
+ <div class="arg-container">
158
+ <input type="text" class="arg-name" placeholder="Arg Name (e.g., a)">
159
+ </div>
160
+ <button onclick="addArg(this.parentElement)">Add Arg</button>
161
+ <div class="port port-input" data-type="input"></div>
162
+ <div class="port port-output" data-type="output"></div>
163
+ `;
164
+ } else if (type === 'Return') {
165
+ html += `
166
+ <input type="text" class="return-value" placeholder="Return Value (e.g., x + 1)">
167
+ <div class="port port-input" data-type="input"></div>
168
+ `;
169
+ } else if (type === 'Call') {
170
+ html += `
171
+ <input type="text" class="call-name" placeholder="Function Name (e.g., add)">
172
+ <div class="arg-container">
173
+ <input type="text" class="call-arg" placeholder="Arg Value (e.g., 3)">
174
+ </div>
175
+ <button onclick="addCallArg(this.parentElement)">Add Arg</button>
176
+ <input type="text" class="call-result" placeholder="Result Var (e.g., z)">
177
+ <div class="port port-input" data-type="input"></div>
178
+ <div class="port port-output" data-type="output"></div>
179
+ `;
180
+ } else if (type === 'If') {
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 += `
189
+ <input type="text" class="condition" placeholder="Condition (e.g., i < 5)">
190
+ <input type="text" class="update" placeholder="Update (e.g., i = i + 1)">
191
+ <div class="port port-input" data-type="input"></div>
192
+ <div class="port port-output" data-type="output"></div>
193
+ `;
194
+ } else if (type === 'Switch') {
195
+ html += `
196
+ <input type="text" class="switch-var" placeholder="Variable to Switch">
197
+ <div class="case-container">
198
+ <input type="text" class="case-value" placeholder="Case Value (e.g., 1)">
199
+ <input type="checkbox" class="case-break" title="Break after this case">
200
+ <div class="port port-output" data-type="output" data-case="0"></div>
201
+ </div>
202
+ <button onclick="addCase(this.parentElement)">Add Case</button>
203
+ <div class="port port-input" data-type="input"></div>
204
+ `;
205
+ } else if (type === 'Break' || type === 'Continue') {
206
+ html += `<div class="port port-input" data-type="input"></div>`;
207
+ } else if (type === 'Add' || type === 'Subtract' || type === 'Multiply' || type === 'Divide') {
208
+ html += `
209
+ <input type="text" class="operand1" placeholder="Operand 1">
210
+ <input type="text" class="operand2" placeholder="Operand 2">
211
+ <input type="text" class="result-var" placeholder="Result Var">
212
+ <div class="port port-output" data-type="output"></div>
213
+ `;
214
+ } else if (type === 'Array') {
215
+ html += `
216
+ <input type="text" class="array-name" placeholder="Array Name">
217
+ <div class="elements-container"></div>
218
+ <button onclick="addArrayElement(this.parentElement)">Add Element</button>
219
+ <div class="port port-output" data-type="output"></div>
220
+ `;
221
+ }
222
+ node.innerHTML = html;
223
+ canvas.appendChild(node);
224
+ nodes.push(node);
225
+ if (options.inputs) {
226
+ Object.keys(options.inputs).forEach(className => {
227
+ const input = node.querySelector(`.${className}`);
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;
237
+ const rect = node.getBoundingClientRect();
238
+ offsetX = e.clientX - rect.left;
239
+ offsetY = e.clientY - rect.top;
240
+ }
241
+ });
242
+ const ports = node.querySelectorAll('.port');
243
+ ports.forEach(port => {
244
+ port.addEventListener('mousedown', (e) => {
245
+ e.stopPropagation();
246
+ draggingPort = port;
247
+ });
248
+ });
249
+ }
250
 
251
+ function addArg(funcNode) {
252
+ const argCount = funcNode.querySelectorAll('.arg-container').length;
253
+ const argDiv = document.createElement('div');
254
+ argDiv.className = 'arg-container';
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');
262
+ argDiv.className = 'arg-container';
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');
270
+ caseDiv.className = 'case-container';
271
+ caseDiv.innerHTML = `
272
+ <input type="text" class="case-value" placeholder="Case Value (e.g., ${caseCount + 1})">
273
+ <input type="checkbox" class="case-break" title="Break after this case">
274
+ <div class="port port-output" data-type="output" data-case="${caseCount}"></div>
275
+ `;
276
+ switchNode.insertBefore(caseDiv, switchNode.querySelector('button'));
277
+ const newPort = caseDiv.querySelector('.port');
278
+ newPort.addEventListener('mousedown', (e) => {
279
+ e.stopPropagation();
280
+ draggingPort = newPort;
281
+ });
282
+ }
283
 
284
+ function addArrayElement(arrayNode) {
285
+ const container = arrayNode.querySelector('.elements-container');
286
+ const input = document.createElement('input');
287
+ input.type = 'text';
288
+ input.className = 'element-value';
289
+ input.placeholder = 'Element value';
290
+ container.appendChild(input);
291
+ }
292
+
293
+ // ----------------------------
294
+ // Dragging and connection handling with requestAnimationFrame
295
+ // ----------------------------
296
+ document.addEventListener('mousemove', (e) => {
297
+ if (draggingNode) {
298
+ requestAnimationFrame(() => {
299
+ const canvasRect = canvas.getBoundingClientRect();
300
+ let x = e.clientX - offsetX - canvasRect.left;
301
+ let y = e.clientY - offsetY - canvasRect.top;
302
+ x = Math.round(x / gridSize) * gridSize;
303
+ y = Math.round(y / gridSize) * gridSize;
304
+ draggingNode.style.left = `${x}px`;
305
+ draggingNode.style.top = `${y}px`;
306
+ updateConnections();
307
+ });
308
+ }
309
+ if (draggingPort) {
310
+ requestAnimationFrame(() => {
311
+ if (!tempLine) {
312
+ tempLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
313
+ tempLine.setAttribute('stroke', 'green');
314
+ tempLine.setAttribute('stroke-width', '2');
315
+ connectionsSvg.appendChild(tempLine);
316
+ }
317
+ const canvasRect = canvas.getBoundingClientRect();
318
+ const fromRect = draggingPort.getBoundingClientRect();
319
+ const x1 = fromRect.left + fromRect.width / 2 - canvasRect.left;
320
+ const y1 = fromRect.top + fromRect.height / 2 - canvasRect.top;
321
+ const x2 = e.clientX - canvasRect.left;
322
+ const y2 = e.clientY - canvasRect.top;
323
+ tempLine.setAttribute('x1', x1);
324
+ tempLine.setAttribute('y1', y1);
325
+ tempLine.setAttribute('x2', x2);
326
+ tempLine.setAttribute('y2', y2);
327
+ });
328
+ } else {
329
+ if (tempLine) {
330
+ tempLine.remove();
331
+ tempLine = null;
332
  }
333
+ }
334
+ });
335
 
336
+ document.addEventListener('mouseup', (e) => {
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
+ }
349
+ draggingPort = null;
350
+ if (tempLine) {
351
+ tempLine.remove();
352
+ tempLine = null;
353
  }
354
+ }
355
+ });
356
 
357
+ function updateConnections() {
358
+ connectionsSvg.innerHTML = '';
359
+ const canvasRect = canvas.getBoundingClientRect();
360
+ connections.forEach(conn => {
361
+ const fromRect = conn.from.getBoundingClientRect();
362
+ const toRect = conn.to.getBoundingClientRect();
363
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
364
+ line.setAttribute('x1', fromRect.left + fromRect.width / 2 - canvasRect.left);
365
+ line.setAttribute('y1', fromRect.top + fromRect.height / 2 - canvasRect.top);
366
+ line.setAttribute('x2', toRect.left + toRect.width / 2 - canvasRect.left);
367
+ line.setAttribute('y2', toRect.top + toRect.height / 2 - canvasRect.top);
368
+ const fromNodeType = conn.from.parentElement.dataset.type;
369
+ line.setAttribute('stroke', controlNodes.includes(fromNodeType) ? 'red' : 'blue');
370
+ line.setAttribute('stroke-width', '2');
371
+ connectionsSvg.appendChild(line);
372
+ });
373
+ }
374
+
375
+ // ----------------------------
376
+ // Expression evaluation and error marking
377
+ // ----------------------------
378
+ function evaluateExpression(expr) {
379
+ if (!expr) return null;
380
+ try {
381
+ let evalExpr = expr;
382
+ for (const [varName, varValue] of Object.entries(variables)) {
383
+ evalExpr = evalExpr.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
384
+ }
385
+ if (evalExpr.includes('=')) {
386
+ const [varName, value] = evalExpr.split('=').map(s => s.trim());
387
+ variables[varName] = eval(value);
388
+ return null;
389
  }
390
+ return eval(evalExpr);
391
+ } catch (e) {
392
+ return `Error in expression: ${e.message}`;
393
+ }
394
+ }
395
 
396
+ function markError(node, message) {
397
+ node.classList.add('error');
398
+ node.title = message;
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)) {
413
+ const nextNode = nextConn.to.parentElement;
414
+ executeNode(nextNode, loopControl, funcContext);
415
+ }
416
+ }
417
 
418
+ function executeNode(node, loopControl = { break: false, continue: false }, funcContext = null) {
419
+ if (node.dataset.executed || loopControl.break) return;
420
+ node.dataset.executed = true;
421
+ const type = node.dataset.type;
422
+ if (type === 'Variable') {
423
+ const name = node.querySelector('.var-name').value;
424
+ const value = node.querySelector('.var-value').value;
425
+ variables[name] = isNaN(value) ? value : Number(value);
426
+ executeNext(node, 'output', loopControl, funcContext);
427
+ } else if (type === 'Assign') {
428
+ const expr = node.querySelector('.assign-expr').value;
429
+ const error = evaluateExpression(expr);
430
+ if (typeof error === 'string' && error.startsWith('Error')) markError(node, error);
431
+ executeNext(node, 'output', loopControl, funcContext);
432
+ } else if (type === 'Print') {
433
+ const value = node.querySelector('.print-value').value;
434
+ const printVal = variables[value] !== undefined ? variables[value] : value;
435
+ workflowOutput += `${printVal}\n`;
436
+ } else if (type === 'Function') {
437
+ const funcName = node.querySelector('.func-name').value;
438
+ const args = Array.from(node.querySelectorAll('.arg-name')).map(input => input.value);
439
+ const prevVars = { ...variables };
440
+ funcContext = { name: funcName, args: {}, returned: false };
441
+ executeNext(node, 'output', loopControl, funcContext);
442
+ variables = prevVars;
443
+ if (!funcContext.returned) functionReturns[funcName] = undefined;
444
+ } else if (type === 'Return') {
445
+ if (funcContext) {
446
+ const returnValue = node.querySelector('.return-value').value;
447
+ const result = evaluateExpression(returnValue);
448
+ if (result && !result.startsWith('Error')) {
449
+ functionReturns[funcContext.name] = result;
450
+ } else if (result) {
451
+ workflowOutput += `${result}\n`;
452
+ }
453
+ funcContext.returned = true;
454
  }
455
+ } else if (type === 'Call') {
456
+ const callName = node.querySelector('.call-name').value;
457
+ const callArgs = Array.from(node.querySelectorAll('.call-arg')).map(input => evaluateExpression(input.value) || input.value);
458
+ const resultVar = node.querySelector('.call-result').value;
459
+ const targetFunc = nodes.find(n => n.dataset.type === 'Function' && n.querySelector('.func-name').value === callName);
460
+ if (targetFunc) {
461
+ const funcArgs = Array.from(targetFunc.querySelectorAll('.arg-name')).map(input => input.value);
462
+ const prevVars = { ...variables };
463
+ funcArgs.forEach((argName, index) => {
464
+ variables[argName] = callArgs[index] !== undefined ? callArgs[index] : undefined;
465
+ });
466
+ const funcContext = { name: callName, args: {}, returned: false };
467
+ executeNode(targetFunc, { break: false, continue: false }, funcContext);
468
+ variables = prevVars;
469
+ if (resultVar && functionReturns[callName] !== undefined) {
470
+ variables[resultVar] = functionReturns[callName];
471
+ }
472
+ } else {
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 {
480
+ let evalCondition = condition;
481
+ for (const [varName, varValue] of Object.entries(variables)) {
482
+ evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
483
+ }
484
+ result = eval(evalCondition);
485
+ } catch (e) {
486
+ workflowOutput += `Error in If condition: ${e.message}\n`;
487
+ }
488
+ executeNext(node, result ? 'true' : 'false', loopControl, funcContext);
489
+ } else if (type === 'WhileLoop') {
490
+ const condition = node.querySelector('.condition').value;
491
+ const update = node.querySelector('.update').value;
492
+ let result = true;
493
+ while (result && !loopControl.break) {
494
+ try {
495
+ let evalCondition = condition;
496
+ for (const [varName, varValue] of Object.entries(variables)) {
497
+ evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
498
  }
499
+ result = eval(evalCondition);
500
+ if (result) {
501
+ let innerControl = { break: false, continue: false };
502
+ executeNext(node, 'output', innerControl, funcContext);
503
+ if (innerControl.continue) continue;
504
+ if (innerControl.break) break;
505
+ const error = evaluateExpression(update);
506
+ if (error) workflowOutput += `${error}\n`;
507
+ }
508
+ } catch (e) {
509
+ workflowOutput += `Error in WhileLoop condition: ${e.message}\n`;
510
+ break;
511
+ }
512
+ }
513
+ } else if (type === 'Switch') {
514
+ const switchVar = node.querySelector('.switch-var').value;
515
+ const caseInputs = node.querySelectorAll('.case-container');
516
+ const value = variables[switchVar];
517
+ let matched = false;
518
+ for (let [index, container] of caseInputs.entries()) {
519
+ const caseValue = container.querySelector('.case-value').value;
520
+ const shouldBreak = container.querySelector('.case-break').checked;
521
+ if (caseValue == value || matched) {
522
+ matched = true;
523
+ executeNext(node, index.toString(), loopControl, funcContext);
524
+ if (shouldBreak) break;
525
+ }
526
  }
527
+ if (!matched) workflowOutput += `No matching case for ${switchVar}=${value}\n`;
528
+ } else if (type === 'Break') {
529
+ loopControl.break = true;
530
+ } else if (type === 'Continue') {
531
+ loopControl.continue = true;
532
+ } else if (type === 'Add') {
533
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
534
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
535
+ const resultVar = node.querySelector('.result-var').value;
536
+ variables[resultVar] = Number(op1) + Number(op2);
537
+ executeNext(node, 'output', loopControl, funcContext);
538
+ } else if (type === 'Subtract') {
539
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
540
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
541
+ const resultVar = node.querySelector('.result-var').value;
542
+ variables[resultVar] = Number(op1) - Number(op2);
543
+ executeNext(node, 'output', loopControl, funcContext);
544
+ } else if (type === 'Multiply') {
545
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
546
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
547
+ const resultVar = node.querySelector('.result-var').value;
548
+ variables[resultVar] = Number(op1) * Number(op2);
549
+ executeNext(node, 'output', loopControl, funcContext);
550
+ } else if (type === 'Divide') {
551
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
552
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
553
+ const resultVar = node.querySelector('.result-var').value;
554
+ if (Number(op2) === 0) {
555
+ workflowOutput += "Division by zero error\n";
556
+ markError(node, "Division by zero");
557
+ } else {
558
+ variables[resultVar] = Number(op1) / Number(op2);
559
+ }
560
+ executeNext(node, 'output', loopControl, funcContext);
561
+ } else if (type === 'Array') {
562
+ const arrayName = node.querySelector('.array-name').value;
563
+ const elements = Array.from(node.querySelectorAll('.element-value')).map(input => evaluateExpression(input.value));
564
+ variables[arrayName] = elements;
565
+ executeNext(node, 'output', loopControl, funcContext);
566
+ }
567
+ }
568
 
569
+ function runWorkflow() {
570
+ workflowOutput = "";
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
 
583
+ // ----------------------------
584
+ // Step-by-step execution
585
+ // ----------------------------
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.");
597
+ return;
598
+ }
599
+ const node = executionQueue.shift();
600
+ node.classList.add('executing');
601
+ const nextNodes = executeNodeStep(node);
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;
609
+ if (type === 'Variable') {
610
+ const name = node.querySelector('.var-name').value;
611
+ const value = node.querySelector('.var-value').value;
612
+ variables[name] = isNaN(value) ? value : Number(value);
613
+ nextNodes.push(getNextNode(node));
614
+ } else if (type === 'Assign') {
615
+ const expr = node.querySelector('.assign-expr').value;
616
+ const error = evaluateExpression(expr);
617
+ if (typeof error === 'string' && error.startsWith('Error')) markError(node, error);
618
+ nextNodes.push(getNextNode(node));
619
+ } else if (type === 'Print') {
620
+ const value = node.querySelector('.print-value').value;
621
+ const printVal = variables[value] !== undefined ? variables[value] : value;
622
+ console.log(printVal);
623
+ } else if (type === 'Add') {
624
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
625
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
626
+ const resultVar = node.querySelector('.result-var').value;
627
+ variables[resultVar] = Number(op1) + Number(op2);
628
+ nextNodes.push(getNextNode(node));
629
+ } else if (type === 'Subtract') {
630
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
631
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
632
+ const resultVar = node.querySelector('.result-var').value;
633
+ variables[resultVar] = Number(op1) - Number(op2);
634
+ nextNodes.push(getNextNode(node));
635
+ } else if (type === 'Multiply') {
636
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
637
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
638
+ const resultVar = node.querySelector('.result-var').value;
639
+ variables[resultVar] = Number(op1) * Number(op2);
640
+ nextNodes.push(getNextNode(node));
641
+ } else if (type === 'Divide') {
642
+ const op1 = evaluateExpression(node.querySelector('.operand1').value);
643
+ const op2 = evaluateExpression(node.querySelector('.operand2').value);
644
+ const resultVar = node.querySelector('.result-var').value;
645
+ if (Number(op2) === 0) {
646
+ markError(node, "Division by zero");
647
+ } else {
648
+ variables[resultVar] = Number(op1) / Number(op2);
649
+ }
650
+ nextNodes.push(getNextNode(node));
651
+ } else if (type === 'Array') {
652
+ const arrayName = node.querySelector('.array-name').value;
653
+ const elements = Array.from(node.querySelectorAll('.element-value')).map(input => evaluateExpression(input.value));
654
+ variables[arrayName] = elements;
655
+ nextNodes.push(getNextNode(node));
656
+ } else {
657
+ nextNodes.push(getNextNode(node));
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-"));
674
+ }
675
+ function saveWorkflow() {
676
+ const workflow = {
677
+ nodes: nodes.map(n => {
678
+ const inputs = {};
679
+ n.querySelectorAll('input').forEach(input => {
680
+ inputs[input.className.split(' ')[0]] = input.value;
681
+ });
682
+ return {
683
+ id: n.dataset.id,
684
+ type: n.dataset.type,
685
+ left: n.style.left,
686
+ top: n.style.top,
687
+ inputs: inputs
688
+ };
689
+ }),
690
+ connections: connections.map(c => {
691
+ return {
692
+ fromNodeId: c.from.parentElement.dataset.id,
693
+ fromPortClass: getPortIdentifier(c.from),
694
+ toNodeId: c.to.parentElement.dataset.id,
695
+ toPortClass: getPortIdentifier(c.to)
696
+ };
697
+ })
698
+ };
699
+ prompt("Copy your workflow JSON:", JSON.stringify(workflow));
700
+ }
701
+ function loadWorkflow() {
702
+ const data = prompt("Paste your workflow JSON:");
703
+ if (!data) return;
704
+ try {
705
+ const workflow = JSON.parse(data);
706
+ nodes = [];
707
+ connections = [];
708
+ canvas.querySelectorAll('.node').forEach(n => n.remove());
709
+ workflow.nodes.forEach(nData => {
710
+ addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
711
+ });
712
+ workflow.connections.forEach(conn => {
713
+ const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
714
+ const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
715
+ if (fromNode && toNode) {
716
+ const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
717
+ const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
718
+ if (fromPort && toPort) {
719
+ connections.push({ from: fromPort, to: toPort });
720
  }
721
+ }
722
+ });
723
+ updateConnections();
724
+ } catch (e) {
725
+ alert("Invalid JSON data.");
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>