nottri-lang / index.html
Ramesh-vani's picture
Update index.html
d867130 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Visual Programming Language</title>
<style>
body { margin: 0; font-family: Arial, sans-serif; }
#canvas {
width: 100vw;
height: 100vh;
background: #f0f0f0;
position: relative;
overflow: auto;
}
.node {
position: absolute;
width: 250px;
background: #fff;
border: 2px solid #333;
border-radius: 5px;
padding: 10px;
cursor: move;
user-select: none;
box-sizing: border-box;
transition: box-shadow 0.1s ease;
}
.node.executing { border: 3px solid orange; }
.node.error { border-color: red; }
.node-header {
font-weight: bold;
margin-bottom: 5px;
position: relative;
}
.remove-node {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
color: red;
font-weight: bold;
padding: 0 4px;
user-select: none;
}
.port {
width: 10px;
height: 10px;
background: #666;
border-radius: 50%;
position: absolute;
cursor: pointer;
}
.port-input { left: -15px; top: 50%; transform: translateY(-50%); }
.port-output { right: -15px; top: 50%; transform: translateY(-50%); }
.port-output-true { right: -15px; top: 30%; }
.port-output-false { right: -15px; top: 70%; }
#toolbar {
position: fixed;
top: 10px;
left: 10px;
background: #ddd;
padding: 10px;
border-radius: 5px;
z-index: 1000;
}
button { margin: 2px; }
#connections {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
input { width: 80%; margin: 5px 0; }
.case-container { margin: 5px 0; display: flex; align-items: center; }
.label { font-size: 12px; color: #555; position: absolute; right: -50px; }
.arg-container { margin: 5px 0; }
.elements-container input { width: 70%; margin: 3px 0; }
</style>
</head>
<body>
<div id="toolbar">
<!-- Existing Nodes -->
<button onclick="addNode('Variable')">Variable</button>
<button onclick="addNode('Assign')">Assign</button>
<button onclick="addNode('Print')">Print</button>
<button onclick="addNode('Function')">Function</button>
<button onclick="addNode('Return')">Return</button>
<button onclick="addNode('Call')">Call</button>
<button onclick="addNode('If')">If</button>
<button onclick="addNode('WhileLoop')">While Loop</button>
<button onclick="addNode('Switch')">Switch</button>
<button onclick="addNode('Break')">Break</button>
<button onclick="addNode('Continue')">Continue</button>
<button onclick="addNode('Add')">Add</button>
<button onclick="addNode('Subtract')">Subtract</button>
<button onclick="addNode('Multiply')">Multiply</button>
<button onclick="addNode('Divide')">Divide</button>
<button onclick="addNode('Array')">Array</button>
<button onclick="addNode('Library')">Import Library</button>
<!-- New Nodes -->
<button onclick="addNode('Comment')">Comment</button>
<button onclick="addNode('ForLoop')">For Loop</button>
<br>
<button onclick="runWorkflow()">Run Workflow</button>
<button onclick="startStepExecution()">Start Step Execution</button>
<button onclick="stepExecution()">Step Execution</button>
<button onclick="saveWorkflow()">Save Workflow</button>
<button onclick="loadWorkflow()">Load Workflow</button>
<button onclick="groupSimilarNodes()">Group Similar Nodes</button>
<button onclick="exportCode()">Export Code</button>
</div>
<div id="canvas">
<svg id="connections"></svg>
</div>
<script>
// Global variables
const canvas = document.getElementById('canvas');
const connectionsSvg = document.getElementById('connections');
let nodes = [];
let connections = [];
let draggingNode = null;
let draggingPort = null;
let tempLine = null;
let offsetX, offsetY;
let variables = {};
let functionReturns = {};
let nodeIdCounter = 0;
const gridSize = 20;
let zoomFactor = 1.0;
let workflowOutput = "";
let executionQueue = [];
const controlNodes = ["If", "WhileLoop", "Switch", "Break", "Continue", "Function", "Return"];
let highestZ = 100;
// ----------------------------
// Node creation and UI helpers
// ----------------------------
function addNode(type, options = {}) {
const node = document.createElement('div');
node.className = 'node';
node.dataset.type = type;
node.dataset.id = nodeIdCounter++;
const left = options.left || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
const top = options.top || Math.round((Math.random() * 300 + 50) / gridSize) * gridSize;
node.style.left = `${left}px`;
node.style.top = `${top}px`;
highestZ++;
node.style.zIndex = highestZ;
let html = `<div class="node-header">${type}<span class="remove-node" onclick="removeNode(this)">×</span></div>`;
if (type === 'Variable') {
html += `
<input type="text" class="var-name" placeholder="Variable Name">
<input type="text" class="var-value" placeholder="Value">
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'Assign') {
html += `
<input type="text" class="assign-expr" placeholder="Assignment (e.g., i = i + 1)">
<div class="port port-input" data-type="input"></div>
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'Print') {
html += `
<input type="text" class="print-value" placeholder='Expression to print (e.g., "Hello " + x)'>
<div class="port port-input" data-type="input"></div>
`;
} else if (type === 'Function') {
html += `
<input type="text" class="func-name" placeholder="Function Name">
<div class="arg-container">
<input type="text" class="arg-name" placeholder="Arg Name (e.g., a)">
</div>
<button onclick="addArg(this.parentElement)">Add Arg</button>
<div class="port port-input" data-type="input"></div>
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'Return') {
html += `
<input type="text" class="return-value" placeholder="Return Value (e.g., x + 1)">
<div class="port port-input" data-type="input"></div>
`;
} else if (type === 'Call') {
html += `
<input type="text" class="call-name" placeholder="Function Name (e.g., add)">
<div class="arg-container">
<input type="text" class="call-arg" placeholder="Arg Value (e.g., 3)">
</div>
<button onclick="addCallArg(this.parentElement)">Add Arg</button>
<input type="text" class="call-result" placeholder="Result Var (e.g., z)">
<div class="port port-input" data-type="input"></div>
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'If') {
html += `
<input type="text" class="condition" placeholder="Condition (e.g., x > 5)">
<div class="port port-input" data-type="input"></div>
<div class="port port-output-true" data-type="output" data-branch="true">
<span class="label">True</span>
</div>
<div class="port port-output-false" data-type="output" data-branch="false">
<span class="label">False</span>
</div>
`;
} else if (type === 'WhileLoop') {
html += `
<input type="text" class="condition" placeholder="Condition (e.g., i < 5)">
<input type="text" class="update" placeholder="Update (e.g., i = i + 1)">
<div class="port port-input" data-type="input"></div>
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'Switch') {
html += `
<input type="text" class="switch-var" placeholder="Variable to Switch">
<div class="case-container">
<input type="text" class="case-value" placeholder="Case Value (e.g., 1)">
<input type="checkbox" class="case-break" title="Break after this case">
<div class="port port-output" data-type="output" data-case="0"></div>
</div>
<button onclick="addCase(this.parentElement)">Add Case</button>
<div class="port port-input" data-type="input"></div>
`;
} else if (type === 'Break' || type === 'Continue') {
html += `<div class="port port-input" data-type="input"></div>`;
} else if (type === 'Add' || type === 'Subtract' ||
type === 'Multiply' || type === 'Divide') {
html += `
<input type="text" class="operand1" placeholder="Operand 1">
<input type="text" class="operand2" placeholder="Operand 2">
<input type="text" class="result-var" placeholder="Result Var">
<div class="port port-output" data-type="output"></div>
`;
} else if (type === 'Array') {
html += `
<input type="text" class="array-name" placeholder="Array Name">
<div class="elements-container"></div>
<button onclick="addArrayElement(this.parentElement)">Add Element</button>
<div class="port port-output" data-type="output"></div>
`;
}
// New: Library Node
else if (type === 'Library') {
html += `
<input type="text" class="lib-url" placeholder="Library (e.g., os)">
<div class="port port-output" data-type="output"></div>
`;
}
// New: Comment Node
else if (type === 'Comment') {
html += `
<input type="text" class="comment-text" placeholder="Enter comment">
<div class="port port-output" data-type="output"></div>
`;
}
// New: For Loop Node
else if (type === 'ForLoop') {
html += `
<input type="text" class="init" placeholder="Initialization (e.g., let i = 0)">
<input type="text" class="condition" placeholder="Condition (e.g., i < 10)">
<input type="text" class="update" placeholder="Update (e.g., i++)">
<div class="port port-input" data-type="input"></div>
<div class="port port-output" data-type="output"></div>
`;
}
node.innerHTML = html;
canvas.appendChild(node);
nodes.push(node);
if (options.inputs) {
Object.keys(options.inputs).forEach(className => {
const input = node.querySelector(`.${className}`);
if (input) input.value = options.inputs[className];
});
}
// Bring node to front on mousedown
node.addEventListener('mousedown', (e) => {
if (!e.target.classList.contains('port') &&
e.target.tagName !== 'INPUT' &&
e.target.tagName !== 'BUTTON') {
highestZ++;
node.style.zIndex = highestZ;
draggingNode = node;
const rect = node.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
}
});
const ports = node.querySelectorAll('.port');
ports.forEach(port => {
port.addEventListener('mousedown', (e) => {
e.stopPropagation();
draggingPort = port;
});
});
}
// ----------------------------
// Remove Node
// ----------------------------
function removeNode(removeButton) {
const node = removeButton.parentElement.parentElement;
connections = connections.filter(conn => conn.from.parentElement !== node && conn.to.parentElement !== node);
node.remove();
nodes = nodes.filter(n => n !== node);
updateConnections();
}
// ----------------------------
// Additional helper functions for node inputs
// ----------------------------
function addArg(funcNode) {
const argCount = funcNode.querySelectorAll('.arg-container').length;
const argDiv = document.createElement('div');
argDiv.className = 'arg-container';
argDiv.innerHTML = `<input type="text" class="arg-name" placeholder="Arg Name (e.g., arg${argCount + 1})">`;
funcNode.insertBefore(argDiv, funcNode.querySelector('button'));
}
function addCallArg(callNode) {
const argCount = callNode.querySelectorAll('.arg-container').length;
const argDiv = document.createElement('div');
argDiv.className = 'arg-container';
argDiv.innerHTML = `<input type="text" class="call-arg" placeholder="Arg Value (e.g., ${argCount + 1})">`;
callNode.insertBefore(argDiv, callNode.querySelector('button'));
}
function addCase(switchNode) {
const caseCount = switchNode.querySelectorAll('.case-container').length;
const caseDiv = document.createElement('div');
caseDiv.className = 'case-container';
caseDiv.innerHTML = `
<input type="text" class="case-value" placeholder="Case Value (e.g., ${caseCount + 1})">
<input type="checkbox" class="case-break" title="Break after this case">
<div class="port port-output" data-type="output" data-case="${caseCount}"></div>
`;
switchNode.insertBefore(caseDiv, switchNode.querySelector('button'));
const newPort = caseDiv.querySelector('.port');
newPort.addEventListener('mousedown', (e) => {
e.stopPropagation();
draggingPort = newPort;
});
}
function addArrayElement(arrayNode) {
const container = arrayNode.querySelector('.elements-container');
const input = document.createElement('input');
input.type = 'text';
input.className = 'element-value';
input.placeholder = 'Element value';
container.appendChild(input);
}
// ----------------------------
// Dragging and Connection Handling
// ----------------------------
document.addEventListener('mousemove', (e) => {
if (draggingNode) {
requestAnimationFrame(() => {
const canvasRect = canvas.getBoundingClientRect();
let x = e.clientX - offsetX - canvasRect.left;
let y = e.clientY - offsetY - canvasRect.top;
x = Math.round(x / gridSize) * gridSize;
y = Math.round(y / gridSize) * gridSize;
draggingNode.style.left = `${x}px`;
draggingNode.style.top = `${y}px`;
updateConnections();
});
}
if (draggingPort) {
requestAnimationFrame(() => {
if (!tempLine) {
tempLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
tempLine.setAttribute('stroke', 'green');
tempLine.setAttribute('stroke-width', '2');
connectionsSvg.appendChild(tempLine);
}
const canvasRect = canvas.getBoundingClientRect();
const fromRect = draggingPort.getBoundingClientRect();
const x1 = fromRect.left + fromRect.width / 2 - canvasRect.left;
const y1 = fromRect.top + fromRect.height / 2 - canvasRect.top;
const x2 = e.clientX - canvasRect.left;
const y2 = e.clientY - canvasRect.top;
tempLine.setAttribute('x1', x1);
tempLine.setAttribute('y1', y1);
tempLine.setAttribute('x2', x2);
tempLine.setAttribute('y2', y2);
});
} else {
if (tempLine) {
tempLine.remove();
tempLine = null;
}
}
});
document.addEventListener('mouseup', (e) => {
if (draggingNode) draggingNode = null;
if (draggingPort) {
const targetPort = e.target.closest('.port');
if (targetPort &&
targetPort !== draggingPort &&
draggingPort.dataset.type === "output" &&
targetPort.dataset.type === "input") {
connections.push({ from: draggingPort, to: targetPort });
updateConnections();
}
draggingPort = null;
if (tempLine) {
tempLine.remove();
tempLine = null;
}
}
});
function updateConnections() {
connectionsSvg.innerHTML = '';
const canvasRect = canvas.getBoundingClientRect();
connections.forEach(conn => {
const fromRect = conn.from.getBoundingClientRect();
const toRect = conn.to.getBoundingClientRect();
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', fromRect.left + fromRect.width / 2 - canvasRect.left);
line.setAttribute('y1', fromRect.top + fromRect.height / 2 - canvasRect.top);
line.setAttribute('x2', toRect.left + toRect.width / 2 - canvasRect.left);
line.setAttribute('y2', toRect.top + toRect.height / 2 - canvasRect.top);
const fromNodeType = conn.from.parentElement.dataset.type;
line.setAttribute('stroke', controlNodes.includes(fromNodeType) ? 'red' : 'blue');
line.setAttribute('stroke-width', '2');
connectionsSvg.appendChild(line);
});
}
// ----------------------------
// Expression Evaluation (with string support)
// ----------------------------
function evaluateExpression(expr) {
if (!expr) return null;
try {
let evalExpr = expr;
// Replace variable names with their values. If a variable is a non-numeric string, wrap in quotes.
for (const [varName, varValue] of Object.entries(variables)) {
let replacement = varValue;
if (typeof varValue === "string" || isNaN(varValue)) {
replacement = `"${varValue}"`;
}
evalExpr = evalExpr.replace(new RegExp(`\\b${varName}\\b`, 'g'), replacement);
}
if (evalExpr.includes('=')) {
const [varName, value] = evalExpr.split('=').map(s => s.trim());
variables[varName] = eval(value);
return null;
}
return eval(evalExpr);
} catch (e) {
return `Error in expression: ${e.message}`;
}
}
function markError(node, message) {
node.classList.add('error');
node.title = message;
}
// ----------------------------
// Export Workflow to Code (DFS-based, with proper indentation)
// ----------------------------
function getChild(node, portSelector) {
const port = node.querySelector(portSelector);
if (!port) return null;
const conn = connections.find(c => c.from === port);
return conn ? conn.to.parentElement : null;
}
function exportWorkflowToLanguage(language) {
let visited = new Set();
let code = "";
function exportNode(node, indentLevel) {
let indent = " ".repeat(indentLevel);
if (visited.has(node.dataset.id)) return "";
visited.add(node.dataset.id);
let nodeCode = "";
let inputs = {};
node.querySelectorAll('input').forEach(input => {
inputs[input.className.split(' ')[0]] = input.value;
});
switch(node.dataset.type) {
case "Variable":
if (language === "JavaScript") {
nodeCode += indent + `let ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
} else if (language === "Python") {
nodeCode += indent + `${inputs["var-name"]} = ${inputs["var-value"]}\n`;
} else if (language === "C") {
nodeCode += indent + `int ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
}
break;
case "Assign":
nodeCode += indent + inputs["assign-expr"] + (language === "Python" ? "\n" : ";\n");
break;
case "Print":
if (language === "JavaScript") {
nodeCode += indent + `console.log(${inputs["print-value"]});\n`;
} else if (language === "Python") {
nodeCode += indent + `print(${inputs["print-value"]})\n`;
} else if (language === "C") {
nodeCode += indent + `printf("%d", ${inputs["print-value"]});\n`;
}
break;
case "Function":
if (language === "JavaScript") {
nodeCode += indent + `function ${inputs["func-name"]}(${inputs["arg-name"] || ""}) {\n // function body\n}\n`;
} else if (language === "Python") {
nodeCode += indent + `def ${inputs["func-name"]}(${inputs["arg-name"] || ""}):\n # function body\n\n`;
} else if (language === "C") {
nodeCode += indent + `void ${inputs["func-name"]}(${inputs["arg-name"] || "void"}) {\n // function body\n}\n`;
}
break;
case "Return":
nodeCode += indent + (language === "Python" ? "return " : "return ") + inputs["return-value"] + (language === "Python" ? "\n" : ";\n");
break;
case "Call":
if (inputs["call-result"]) {
if (language === "JavaScript") {
nodeCode += indent + `let ${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
} else if (language === "Python") {
nodeCode += indent + `${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""})\n`;
} else if (language === "C") {
nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""}); // assign result to ${inputs["call-result"]}\n`;
}
} else {
nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
}
break;
case "If":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `if (${inputs["condition"]}) {\n`;
} else if (language === "Python") {
nodeCode += indent + `if ${inputs["condition"]}:\n`;
}
let trueChild = getChild(node, '.port-output-true');
if (trueChild) {
nodeCode += exportNode(trueChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `} else {\n`;
} else if (language === "Python") {
nodeCode += indent + `else:\n`;
}
let falseChild = getChild(node, '.port-output-false');
if (falseChild) {
nodeCode += exportNode(falseChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "WhileLoop":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `while (${inputs["condition"]}) {\n ${inputs["update"]};\n`;
} else if (language === "Python") {
nodeCode += indent + `while ${inputs["condition"]}:\n ${inputs["update"]}\n`;
}
let whileChild = getChild(node, '.port-output');
if (whileChild) {
nodeCode += exportNode(whileChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "ForLoop":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `for (${inputs["init"]}; ${inputs["condition"]}; ${inputs["update"]}) {\n`;
} else if (language === "Python") {
nodeCode += indent + `# For loop converted to while:\n${inputs["init"]}\nwhile ${inputs["condition"]}:\n`;
}
let forChild = getChild(node, '.port-output');
if (forChild) {
nodeCode += exportNode(forChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "Comment":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `// ${inputs["comment-text"]}\n`;
} else if (language === "Python") {
nodeCode += indent + `# ${inputs["comment-text"]}\n`;
}
break;
case "Library":
if (language === "JavaScript") {
nodeCode += indent + `import * as lib from '${inputs["lib-url"]}';\n`;
} else if (language === "Python") {
nodeCode += indent + `import ${inputs["lib-url"]}\n`;
} else if (language === "C") {
nodeCode += indent + `#include <${inputs["lib-url"]}>\n`;
}
break;
default:
nodeCode += indent + `// Unhandled node type: ${node.dataset.type}\n`;
}
// For non-control nodes, append the next connected node (if any)
if (node.dataset.type !== "If" && node.dataset.type !== "WhileLoop" && node.dataset.type !== "ForLoop") {
let child = getChild(node, '.port-output');
if (child) {
nodeCode += exportNode(child, indentLevel);
}
}
return nodeCode;
}
let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
startNodes.forEach(node => {
code += exportNode(node, 0);
});
return code;
}
function exportCode() {
const language = prompt("Enter target language (JavaScript, Python, or C):", "JavaScript");
if (!language) return;
const code = exportWorkflowToLanguage(language);
const codeWindow = window.open("", "Exported Code", "width=600,height=400");
if (codeWindow) {
codeWindow.document.write("<pre>" + code + "</pre>");
} else {
alert("Exported Code:\n" + code);
}
}
// ----------------------------
// Group Similar Nodes
// ----------------------------
function groupSimilarNodes() {
const groups = {};
nodes.forEach(node => {
const type = node.dataset.type;
if (!groups[type]) {
groups[type] = [];
}
groups[type].push(node);
});
let xOffset = 20;
const yStart = 20;
const xGap = 280;
for (const type in groups) {
let yOffset = yStart;
groups[type].forEach(node => {
node.style.left = `${xOffset}px`;
node.style.top = `${yOffset}px`;
yOffset += node.offsetHeight + 20;
});
xOffset += xGap;
}
updateConnections();
}
// ----------------------------
// Workflow Persistence (Save/Load)
// ----------------------------
function getPortIdentifier(port) {
return Array.from(port.classList).find(cls => cls.startsWith("port-"));
}
function saveWorkflow() {
const workflow = {
nodes: nodes.map(n => {
const inputs = {};
n.querySelectorAll('input').forEach(input => {
inputs[input.className.split(' ')[0]] = input.value;
});
return {
id: n.dataset.id,
type: n.dataset.type,
left: n.style.left,
top: n.style.top,
inputs: inputs
};
}),
connections: connections.map(c => {
return {
fromNodeId: c.from.parentElement.dataset.id,
fromPortClass: getPortIdentifier(c.from),
toNodeId: c.to.parentElement.dataset.id,
toPortClass: getPortIdentifier(c.to)
};
})
};
prompt("Copy your workflow JSON:", JSON.stringify(workflow));
}
function loadWorkflow() {
const data = prompt("Paste your workflow JSON:");
if (!data) return;
try {
const workflow = JSON.parse(data);
nodes = [];
connections = [];
canvas.querySelectorAll('.node').forEach(n => n.remove());
workflow.nodes.forEach(nData => {
addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
});
workflow.connections.forEach(conn => {
const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
if (fromNode && toNode) {
const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
if (fromPort && toPort) {
connections.push({ from: fromPort, to: toPort });
}
}
});
updateConnections();
} catch (e) {
alert("Invalid JSON data.");
}
}
// ----------------------------
// Workflow Execution Functions
// ----------------------------
function executeNext(node, branch, loopControl, funcContext) {
let portClass;
if (node.dataset.type === 'Switch') {
portClass = `.port-output[data-case="${branch}"]`;
} else {
portClass = branch === 'true' ? '.port-output-true' :
branch === 'false' ? '.port-output-false' : '.port-output';
}
const nextConn = connections.find(c => c.from === node.querySelector(portClass));
if (nextConn && (!funcContext || !funcContext.returned)) {
const nextNode = nextConn.to.parentElement;
executeNode(nextNode, loopControl, funcContext);
}
}
function executeNode(node, loopControl = { break: false, continue: false }, funcContext = null) {
if (node.dataset.executed || loopControl.break) return;
node.dataset.executed = true;
const type = node.dataset.type;
if (type === 'Variable') {
const name = node.querySelector('.var-name').value;
const value = node.querySelector('.var-value').value;
variables[name] = isNaN(value) ? value : Number(value);
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Assign') {
const expr = node.querySelector('.assign-expr').value;
const error = evaluateExpression(expr);
if (typeof error === 'string' && error.startsWith('Error')) markError(node, error);
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Print') {
const expr = node.querySelector('.print-value').value;
const result = evaluateExpression(expr);
workflowOutput += (result !== null ? result : "") + "\n";
} else if (type === 'Function') {
const funcName = node.querySelector('.func-name').value;
const args = Array.from(node.querySelectorAll('.arg-name')).map(input => input.value);
const prevVars = { ...variables };
funcContext = { name: funcName, args: {}, returned: false };
executeNext(node, 'output', loopControl, funcContext);
variables = prevVars;
if (!funcContext.returned) functionReturns[funcName] = undefined;
} else if (type === 'Return') {
if (funcContext) {
const returnValue = node.querySelector('.return-value').value;
const result = evaluateExpression(returnValue);
if (result && !result.startsWith('Error')) {
functionReturns[funcContext.name] = result;
} else if (result) {
workflowOutput += `${result}\n`;
}
funcContext.returned = true;
}
} else if (type === 'Call') {
const callName = node.querySelector('.call-name').value;
const callArgs = Array.from(node.querySelectorAll('.call-arg')).map(input => evaluateExpression(input.value) || input.value);
const resultVar = node.querySelector('.call-result').value;
const targetFunc = nodes.find(n => n.dataset.type === 'Function' && n.querySelector('.func-name').value === callName);
if (targetFunc) {
const funcArgs = Array.from(targetFunc.querySelectorAll('.arg-name')).map(input => input.value);
const prevVars = { ...variables };
funcArgs.forEach((argName, index) => {
variables[argName] = callArgs[index] !== undefined ? callArgs[index] : undefined;
});
const funcContext = { name: callName, args: {}, returned: false };
executeNode(targetFunc, { break: false, continue: false }, funcContext);
variables = prevVars;
if (resultVar && functionReturns[callName] !== undefined) {
variables[resultVar] = functionReturns[callName];
}
} else {
workflowOutput += `Function ${callName} not found!\n`;
}
executeNext(node, 'output', loopControl, funcContext);
}
else if (type === 'Library') {
const libUrl = node.querySelector('.lib-url').value;
if (libUrl) {
let script = document.createElement('script');
script.src = libUrl;
script.onload = () => {
workflowOutput += `Library loaded: ${libUrl}\n`;
executeNext(node, 'output', loopControl, funcContext);
};
script.onerror = () => {
markError(node, `Failed to load library: ${libUrl}`);
workflowOutput += `Failed to load library: ${libUrl}\n`;
executeNext(node, 'output', loopControl, funcContext);
};
document.head.appendChild(script);
return;
} else {
executeNext(node, 'output', loopControl, funcContext);
}
}
else if (type === 'Comment') {
executeNext(node, 'output', loopControl, funcContext);
}
else if (type === 'ForLoop') {
const init = node.querySelector('.init').value;
const condition = node.querySelector('.condition').value;
const update = node.querySelector('.update').value;
evaluateExpression(init);
let result = true;
while (result && !loopControl.break) {
try {
let evalCondition = condition;
for (const [varName, varValue] of Object.entries(variables)) {
evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
}
result = eval(evalCondition);
if (result) {
let innerControl = { break: false, continue: false };
executeNext(node, 'output', innerControl, funcContext);
if (innerControl.continue) { evaluateExpression(update); continue; }
if (innerControl.break) break;
const error = evaluateExpression(update);
if (error) workflowOutput += `${error}\n`;
}
} catch (e) {
workflowOutput += `Error in ForLoop condition: ${e.message}\n`;
break;
}
}
executeNext(node, 'output', loopControl, funcContext);
}
else if (type === 'If') {
const condition = node.querySelector('.condition').value;
let result = false;
try {
let evalCondition = condition;
for (const [varName, varValue] of Object.entries(variables)) {
evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
}
result = eval(evalCondition);
} catch (e) {
workflowOutput += `Error in If condition: ${e.message}\n`;
}
executeNext(node, result ? 'true' : 'false', loopControl, funcContext);
} else if (type === 'WhileLoop') {
const condition = node.querySelector('.condition').value;
const update = node.querySelector('.update').value;
let result = true;
while (result && !loopControl.break) {
try {
let evalCondition = condition;
for (const [varName, varValue] of Object.entries(variables)) {
evalCondition = evalCondition.replace(new RegExp(`\\b${varName}\\b`, 'g'), varValue);
}
result = eval(evalCondition);
if (result) {
let innerControl = { break: false, continue: false };
executeNext(node, 'output', innerControl, funcContext);
if (innerControl.continue) continue;
if (innerControl.break) break;
const error = evaluateExpression(update);
if (error) workflowOutput += `${error}\n`;
}
} catch (e) {
workflowOutput += `Error in WhileLoop condition: ${e.message}\n`;
break;
}
}
} else if (type === 'Switch') {
const switchVar = node.querySelector('.switch-var').value;
const caseInputs = node.querySelectorAll('.case-container');
const value = variables[switchVar];
let matched = false;
for (let [index, container] of caseInputs.entries()) {
const caseValue = container.querySelector('.case-value').value;
const shouldBreak = container.querySelector('.case-break').checked;
if (caseValue == value || matched) {
matched = true;
executeNext(node, index.toString(), loopControl, funcContext);
if (shouldBreak) break;
}
}
if (!matched) workflowOutput += `No matching case for ${switchVar}=${value}\n`;
} else if (type === 'Break') {
loopControl.break = true;
} else if (type === 'Continue') {
loopControl.continue = true;
} else if (type === 'Add') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) + Number(op2);
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Subtract') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) - Number(op2);
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Multiply') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) * Number(op2);
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Divide') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
if (Number(op2) === 0) {
workflowOutput += "Division by zero error\n";
markError(node, "Division by zero");
} else {
variables[resultVar] = Number(op1) / Number(op2);
}
executeNext(node, 'output', loopControl, funcContext);
} else if (type === 'Array') {
const arrayName = node.querySelector('.array-name').value;
const elements = Array.from(node.querySelectorAll('.element-value')).map(input => evaluateExpression(input.value));
variables[arrayName] = elements;
executeNext(node, 'output', loopControl, funcContext);
}
}
function runWorkflow() {
workflowOutput = "";
variables = {};
functionReturns = {};
nodes.forEach(n => delete n.dataset.executed);
let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
if (startNodes.length === 0) startNodes = nodes;
startNodes.forEach(node => executeNode(node));
alert(workflowOutput || 'No output generated! Check your connections and node settings.');
}
// ----------------------------
// Step-by-step execution
// ----------------------------
function startStepExecution() {
executionQueue = [];
nodes.forEach(n => delete n.dataset.executedStep);
let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
if (startNodes.length === 0) startNodes = nodes;
executionQueue.push(...startNodes);
alert("Step execution started. Click 'Step Execution' to execute the next node. Check the console for output.");
}
function stepExecution() {
if (executionQueue.length === 0) {
alert("Execution finished.");
return;
}
const node = executionQueue.shift();
node.classList.add('executing');
const nextNodes = executeNodeStep(node);
executionQueue.push(...nextNodes.filter(n => n));
setTimeout(() => { node.classList.remove('executing'); }, 500);
}
function executeNodeStep(node) {
let nextNodes = [];
const type = node.dataset.type;
if (type === 'Variable') {
const name = node.querySelector('.var-name').value;
const value = node.querySelector('.var-value').value;
variables[name] = isNaN(value) ? value : Number(value);
nextNodes.push(getNextNode(node));
} else if (type === 'Assign') {
const expr = node.querySelector('.assign-expr').value;
const error = evaluateExpression(expr);
if (typeof error === 'string' && error.startsWith('Error')) markError(node, error);
nextNodes.push(getNextNode(node));
} else if (type === 'Print') {
const expr = node.querySelector('.print-value').value;
const result = evaluateExpression(expr);
console.log(result);
} else if (type === 'Add') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) + Number(op2);
nextNodes.push(getNextNode(node));
} else if (type === 'Subtract') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) - Number(op2);
nextNodes.push(getNextNode(node));
} else if (type === 'Multiply') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
variables[resultVar] = Number(op1) * Number(op2);
nextNodes.push(getNextNode(node));
} else if (type === 'Divide') {
const op1 = evaluateExpression(node.querySelector('.operand1').value);
const op2 = evaluateExpression(node.querySelector('.operand2').value);
const resultVar = node.querySelector('.result-var').value;
if (Number(op2) === 0) {
markError(node, "Division by zero");
} else {
variables[resultVar] = Number(op1) / Number(op2);
}
nextNodes.push(getNextNode(node));
} else if (type === 'Array') {
const arrayName = node.querySelector('.array-name').value;
const elements = Array.from(node.querySelectorAll('.element-value')).map(input => evaluateExpression(input.value));
variables[arrayName] = elements;
nextNodes.push(getNextNode(node));
} else {
nextNodes.push(getNextNode(node));
}
return nextNodes.filter(n => n);
}
function getNextNode(node) {
const port = node.querySelector('.port.port-output');
const conn = connections.find(c => c.from === port);
return conn ? conn.to.parentElement : null;
}
// ----------------------------
// Workflow Persistence (Save/Load)
// ----------------------------
function getPortIdentifier(port) {
return Array.from(port.classList).find(cls => cls.startsWith("port-"));
}
function saveWorkflow() {
const workflow = {
nodes: nodes.map(n => {
const inputs = {};
n.querySelectorAll('input').forEach(input => {
inputs[input.className.split(' ')[0]] = input.value;
});
return {
id: n.dataset.id,
type: n.dataset.type,
left: n.style.left,
top: n.style.top,
inputs: inputs
};
}),
connections: connections.map(c => {
return {
fromNodeId: c.from.parentElement.dataset.id,
fromPortClass: getPortIdentifier(c.from),
toNodeId: c.to.parentElement.dataset.id,
toPortClass: getPortIdentifier(c.to)
};
})
};
prompt("Copy your workflow JSON:", JSON.stringify(workflow));
}
function loadWorkflow() {
const data = prompt("Paste your workflow JSON:");
if (!data) return;
try {
const workflow = JSON.parse(data);
nodes = [];
connections = [];
canvas.querySelectorAll('.node').forEach(n => n.remove());
workflow.nodes.forEach(nData => {
addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
});
workflow.connections.forEach(conn => {
const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
if (fromNode && toNode) {
const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
if (fromPort && toPort) {
connections.push({ from: fromPort, to: toPort });
}
}
});
updateConnections();
} catch (e) {
alert("Invalid JSON data.");
}
}
// ----------------------------
// Export Workflow to Code
// ----------------------------
function exportWorkflowToLanguage(language) {
let visited = new Set();
let code = "";
function exportNode(node, indentLevel) {
let indent = " ".repeat(indentLevel);
if (visited.has(node.dataset.id)) return "";
visited.add(node.dataset.id);
let nodeCode = "";
let inputs = {};
node.querySelectorAll('input').forEach(input => {
inputs[input.className.split(' ')[0]] = input.value;
});
switch(node.dataset.type) {
case "Variable":
if (language === "JavaScript") {
nodeCode += indent + `let ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
} else if (language === "Python") {
nodeCode += indent + `${inputs["var-name"]} = ${inputs["var-value"]}\n`;
} else if (language === "C") {
nodeCode += indent + `int ${inputs["var-name"]} = ${inputs["var-value"]};\n`;
}
break;
case "Assign":
nodeCode += indent + inputs["assign-expr"] + (language === "Python" ? "\n" : ";\n");
break;
case "Print":
if (language === "JavaScript") {
nodeCode += indent + `console.log(${inputs["print-value"]});\n`;
} else if (language === "Python") {
nodeCode += indent + `print(${inputs["print-value"]})\n`;
} else if (language === "C") {
nodeCode += indent + `printf("%d", ${inputs["print-value"]});\n`;
}
break;
case "Function":
if (language === "JavaScript") {
nodeCode += indent + `function ${inputs["func-name"]}(${inputs["arg-name"] || ""}) {\n // function body\n}\n`;
} else if (language === "Python") {
nodeCode += indent + `def ${inputs["func-name"]}(${inputs["arg-name"] || ""}):\n # function body\n\n`;
} else if (language === "C") {
nodeCode += indent + `void ${inputs["func-name"]}(${inputs["arg-name"] || "void"}) {\n // function body\n}\n`;
}
break;
case "Return":
nodeCode += indent + (language === "Python" ? "return " : "return ") + inputs["return-value"] + (language === "Python" ? "\n" : ";\n");
break;
case "Call":
if (inputs["call-result"]) {
if (language === "JavaScript") {
nodeCode += indent + `let ${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
} else if (language === "Python") {
nodeCode += indent + `${inputs["call-result"]} = ${inputs["call-name"]}(${inputs["call-arg"] || ""})\n`;
} else if (language === "C") {
nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""}); // assign result to ${inputs["call-result"]}\n`;
}
} else {
nodeCode += indent + `${inputs["call-name"]}(${inputs["call-arg"] || ""});\n`;
}
break;
case "If":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `if (${inputs["condition"]}) {\n`;
} else if (language === "Python") {
nodeCode += indent + `if ${inputs["condition"]}:\n`;
}
let trueChild = getChild(node, '.port-output-true');
if (trueChild) {
nodeCode += exportNode(trueChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `} else {\n`;
} else if (language === "Python") {
nodeCode += indent + `else:\n`;
}
let falseChild = getChild(node, '.port-output-false');
if (falseChild) {
nodeCode += exportNode(falseChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "WhileLoop":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `while (${inputs["condition"]}) {\n ${inputs["update"]};\n`;
} else if (language === "Python") {
nodeCode += indent + `while ${inputs["condition"]}:\n ${inputs["update"]}\n`;
}
let whileChild = getChild(node, '.port-output');
if (whileChild) {
nodeCode += exportNode(whileChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "ForLoop":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `for (${inputs["init"]}; ${inputs["condition"]}; ${inputs["update"]}) {\n`;
} else if (language === "Python") {
nodeCode += indent + `# For loop converted to while:\n${inputs["init"]}\nwhile ${inputs["condition"]}:\n`;
}
let forChild = getChild(node, '.port-output');
if (forChild) {
nodeCode += exportNode(forChild, indentLevel + 1);
}
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `}\n`;
}
break;
case "Comment":
if (language === "JavaScript" || language === "C") {
nodeCode += indent + `// ${inputs["comment-text"]}\n`;
} else if (language === "Python") {
nodeCode += indent + `# ${inputs["comment-text"]}\n`;
}
break;
case "Library":
if (language === "JavaScript") {
nodeCode += indent + `import * as lib from '${inputs["lib-url"]}';\n`;
} else if (language === "Python") {
nodeCode += indent + `import ${inputs["lib-url"]}\n`;
} else if (language === "C") {
nodeCode += indent + `#include <${inputs["lib-url"]}>\n`;
}
break;
default:
nodeCode += indent + `// Unhandled node type: ${node.dataset.type}\n`;
}
// For non-control nodes, add the next connected node
if (node.dataset.type !== "If" && node.dataset.type !== "WhileLoop" && node.dataset.type !== "ForLoop") {
let child = getChild(node, '.port-output');
if (child) {
nodeCode += exportNode(child, indentLevel);
}
}
return nodeCode;
}
let startNodes = nodes.filter(n => !connections.some(c => c.to.parentElement === n));
startNodes.forEach(node => {
code += exportNode(node, 0);
});
return code;
}
function exportCode() {
const language = prompt("Enter target language (JavaScript, Python, or C):", "JavaScript");
if (!language) return;
const code = exportWorkflowToLanguage(language);
const codeWindow = window.open("", "Exported Code", "width=600,height=400");
if (codeWindow) {
codeWindow.document.write("<pre>" + code + "</pre>");
} else {
alert("Exported Code:\n" + code);
}
}
// ----------------------------
// Group Similar Nodes
// ----------------------------
function groupSimilarNodes() {
const groups = {};
nodes.forEach(node => {
const type = node.dataset.type;
if (!groups[type]) {
groups[type] = [];
}
groups[type].push(node);
});
let xOffset = 20;
const yStart = 20;
const xGap = 280;
for (const type in groups) {
let yOffset = yStart;
groups[type].forEach(node => {
node.style.left = `${xOffset}px`;
node.style.top = `${yOffset}px`;
yOffset += node.offsetHeight + 20;
});
xOffset += xGap;
}
updateConnections();
}
// ----------------------------
// Workflow Persistence (Save/Load)
// ----------------------------
function getPortIdentifier(port) {
return Array.from(port.classList).find(cls => cls.startsWith("port-"));
}
function saveWorkflow() {
const workflow = {
nodes: nodes.map(n => {
const inputs = {};
n.querySelectorAll('input').forEach(input => {
inputs[input.className.split(' ')[0]] = input.value;
});
return {
id: n.dataset.id,
type: n.dataset.type,
left: n.style.left,
top: n.style.top,
inputs: inputs
};
}),
connections: connections.map(c => {
return {
fromNodeId: c.from.parentElement.dataset.id,
fromPortClass: getPortIdentifier(c.from),
toNodeId: c.to.parentElement.dataset.id,
toPortClass: getPortIdentifier(c.to)
};
})
};
prompt("Copy your workflow JSON:", JSON.stringify(workflow));
}
function loadWorkflow() {
const data = prompt("Paste your workflow JSON:");
if (!data) return;
try {
const workflow = JSON.parse(data);
nodes = [];
connections = [];
canvas.querySelectorAll('.node').forEach(n => n.remove());
workflow.nodes.forEach(nData => {
addNode(nData.type, { left: parseInt(nData.left), top: parseInt(nData.top), inputs: nData.inputs });
});
workflow.connections.forEach(conn => {
const fromNode = nodes.find(n => n.dataset.id === conn.fromNodeId);
const toNode = nodes.find(n => n.dataset.id === conn.toNodeId);
if (fromNode && toNode) {
const fromPort = Array.from(fromNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.fromPortClass));
const toPort = Array.from(toNode.querySelectorAll('.port')).find(p => p.classList.contains(conn.toPortClass));
if (fromPort && toPort) {
connections.push({ from: fromPort, to: toPort });
}
}
});
updateConnections();
} catch (e) {
alert("Invalid JSON data.");
}
}
// ----------------------------
// Zooming Support
// ----------------------------
// canvas.addEventListener('wheel', (e) => {
// e.preventDefault();
// if (e.deltaY < 0) {
// zoomFactor *= 1.1;
// } else {
// zoomFactor /= 1.1;
// }
// canvas.style.transform = `scale(${zoomFactor})`;
// });
</script>
</body>
</html>