Spaces:
Paused
Paused
Update static/canvas.js
Browse files- static/canvas.js +75 -36
static/canvas.js
CHANGED
|
@@ -16,6 +16,7 @@ let parsedConnections = [];
|
|
| 16 |
let selectedPort = null;
|
| 17 |
let disconnectMode = false;
|
| 18 |
let codeWindow = null; // Track open code window
|
|
|
|
| 19 |
|
| 20 |
// Zoom functionality
|
| 21 |
let scale = 1;
|
|
@@ -87,15 +88,29 @@ function clearCanvas() {
|
|
| 87 |
codeWindow.destroy();
|
| 88 |
codeWindow = null;
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
layer.draw();
|
| 91 |
}
|
| 92 |
|
| 93 |
// Create nodes and connections from parsed data
|
| 94 |
function createNodesFromParsedData(parsedNodes, parsedConnections) {
|
|
|
|
| 95 |
parsedNodes.forEach(nodeData => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
const node = createNode(
|
| 97 |
-
|
| 98 |
-
|
| 99 |
nodeData.label,
|
| 100 |
nodeData.type,
|
| 101 |
nodeData.inputs,
|
|
@@ -109,7 +124,7 @@ function createNodesFromParsedData(parsedNodes, parsedConnections) {
|
|
| 109 |
layer.add(node);
|
| 110 |
});
|
| 111 |
layer.draw();
|
| 112 |
-
autoConnect();
|
| 113 |
saveNodes();
|
| 114 |
}
|
| 115 |
|
|
@@ -260,11 +275,15 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, source = '
|
|
| 260 |
codeWindow.destroy();
|
| 261 |
codeWindow = null;
|
| 262 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
const nodePos = node.getAbsolutePosition();
|
| 265 |
codeWindow = new Konva.Group({
|
| 266 |
-
x:
|
| 267 |
-
y:
|
| 268 |
});
|
| 269 |
|
| 270 |
const codeBox = new Konva.Rect({
|
|
@@ -276,20 +295,46 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, source = '
|
|
| 276 |
cornerRadius: 5
|
| 277 |
});
|
| 278 |
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
// Update code on change
|
| 290 |
-
|
| 291 |
-
const newSource =
|
| 292 |
node.data.source = newSource;
|
|
|
|
| 293 |
fetch('/update_node', {
|
| 294 |
method: 'POST',
|
| 295 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -314,14 +359,15 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, source = '
|
|
| 314 |
stage.on('click', (e) => {
|
| 315 |
if (e.target !== box && codeWindow) {
|
| 316 |
codeWindow.destroy();
|
| 317 |
-
document.body.removeChild(textarea);
|
| 318 |
codeWindow = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
stage.off('click');
|
| 320 |
}
|
| 321 |
});
|
| 322 |
|
| 323 |
-
codeWindow.add(codeBox);
|
| 324 |
-
layer.add(codeWindow);
|
| 325 |
layer.draw();
|
| 326 |
});
|
| 327 |
|
|
@@ -329,14 +375,14 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, source = '
|
|
| 329 |
node.on('dragmove', () => {
|
| 330 |
node.data.x = node.x();
|
| 331 |
node.data.y = node.y();
|
| 332 |
-
if (codeWindow) {
|
| 333 |
const nodePos = node.getAbsolutePosition();
|
| 334 |
-
codeWindow.position({ x:
|
| 335 |
-
const
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
}
|
| 340 |
}
|
| 341 |
updateConnections();
|
| 342 |
saveNodes();
|
|
@@ -384,7 +430,6 @@ function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
|
|
| 384 |
|
| 385 |
// Enhanced auto-connect based on hierarchy, position, and role
|
| 386 |
function autoConnect() {
|
| 387 |
-
// Clear existing connections
|
| 388 |
layer.find('Shape').forEach(shape => {
|
| 389 |
if (shape.data && shape.data.fromNodeId !== undefined) {
|
| 390 |
shape.destroy();
|
|
@@ -392,13 +437,11 @@ function autoConnect() {
|
|
| 392 |
});
|
| 393 |
connections = [];
|
| 394 |
|
| 395 |
-
// Sort nodes by level and y-position to approximate program order
|
| 396 |
const sortedNodes = [...nodes].sort((a, b) => {
|
| 397 |
if (a.data.level !== b.data.level) return a.data.level - b.data.level;
|
| 398 |
-
return a.data.
|
| 399 |
});
|
| 400 |
|
| 401 |
-
// Build hierarchy map
|
| 402 |
const hierarchy = {};
|
| 403 |
sortedNodes.forEach(node => {
|
| 404 |
const parent = node.data.parent_path.split(' -> ')[0] || 'global';
|
|
@@ -406,7 +449,6 @@ function autoConnect() {
|
|
| 406 |
hierarchy[parent].push(node);
|
| 407 |
});
|
| 408 |
|
| 409 |
-
// Create connections based on parsed connections and hierarchy
|
| 410 |
parsedConnections.forEach(conn => {
|
| 411 |
const fromNode = nodes.find(n => n.data.id === conn.from);
|
| 412 |
const toNode = nodes.find(n => n.data.id === conn.to);
|
|
@@ -425,13 +467,11 @@ function autoConnect() {
|
|
| 425 |
}
|
| 426 |
});
|
| 427 |
|
| 428 |
-
// Additional connections based on hierarchy and role
|
| 429 |
sortedNodes.forEach(node => {
|
| 430 |
const nodeId = node.data.id;
|
| 431 |
const parent = node.data.parent_path.split(' -> ')[0] || 'global';
|
| 432 |
const role = node.data.type;
|
| 433 |
|
| 434 |
-
// Connect to parent if applicable
|
| 435 |
if (parent !== 'global') {
|
| 436 |
const parentNode = nodes.find(n => n.data.id === parent || n.data.label === parent.split('[')[0]);
|
| 437 |
if (parentNode && parentNode.data.outputs.length > 0 && node.data.inputs.length > 0) {
|
|
@@ -449,7 +489,6 @@ function autoConnect() {
|
|
| 449 |
}
|
| 450 |
}
|
| 451 |
|
| 452 |
-
// Connect variables to their uses
|
| 453 |
if (role.includes('variable')) {
|
| 454 |
const varName = node.data.label;
|
| 455 |
sortedNodes.forEach(otherNode => {
|
|
@@ -498,7 +537,7 @@ function updateProgram() {
|
|
| 498 |
function reconstructProgram() {
|
| 499 |
const sortedNodes = [...nodes].sort((a, b) => {
|
| 500 |
if (a.data.level !== b.data.level) return a.data.level - b.data.level;
|
| 501 |
-
return a.data.
|
| 502 |
});
|
| 503 |
|
| 504 |
let program = '';
|
|
@@ -587,7 +626,7 @@ function saveNodes() {
|
|
| 587 |
})),
|
| 588 |
connections: connections
|
| 589 |
})
|
| 590 |
-
}).then(response => response.json
|
| 591 |
.then(data => console.log('Saved:', data))
|
| 592 |
.catch(error => console.error('Error:', error));
|
| 593 |
}
|
|
|
|
| 16 |
let selectedPort = null;
|
| 17 |
let disconnectMode = false;
|
| 18 |
let codeWindow = null; // Track open code window
|
| 19 |
+
let codeTextarea = null; // Track textarea element
|
| 20 |
|
| 21 |
// Zoom functionality
|
| 22 |
let scale = 1;
|
|
|
|
| 88 |
codeWindow.destroy();
|
| 89 |
codeWindow = null;
|
| 90 |
}
|
| 91 |
+
if (codeTextarea) {
|
| 92 |
+
codeTextarea.remove();
|
| 93 |
+
codeTextarea = null;
|
| 94 |
+
}
|
| 95 |
layer.draw();
|
| 96 |
}
|
| 97 |
|
| 98 |
// Create nodes and connections from parsed data
|
| 99 |
function createNodesFromParsedData(parsedNodes, parsedConnections) {
|
| 100 |
+
const scopePositions = {}; // Track x-position per scope
|
| 101 |
parsedNodes.forEach(nodeData => {
|
| 102 |
+
const scope = nodeData.parent_path.split(' -> ')[0] || 'global';
|
| 103 |
+
if (!scopePositions[scope]) {
|
| 104 |
+
scopePositions[scope] = { x: 50, count: 0 };
|
| 105 |
+
}
|
| 106 |
+
// Stack left to right: increment x based on level and scope
|
| 107 |
+
const x = 50 + nodeData.level * 150 + scopePositions[scope].count * 200;
|
| 108 |
+
const y = scopePositions[scope].count * 80 + 50; // Slight y offset for readability
|
| 109 |
+
scopePositions[scope].count += 1;
|
| 110 |
+
|
| 111 |
const node = createNode(
|
| 112 |
+
x,
|
| 113 |
+
y,
|
| 114 |
nodeData.label,
|
| 115 |
nodeData.type,
|
| 116 |
nodeData.inputs,
|
|
|
|
| 124 |
layer.add(node);
|
| 125 |
});
|
| 126 |
layer.draw();
|
| 127 |
+
autoConnect();
|
| 128 |
saveNodes();
|
| 129 |
}
|
| 130 |
|
|
|
|
| 275 |
codeWindow.destroy();
|
| 276 |
codeWindow = null;
|
| 277 |
}
|
| 278 |
+
if (codeTextarea) {
|
| 279 |
+
codeTextarea.remove();
|
| 280 |
+
codeTextarea = null;
|
| 281 |
+
}
|
| 282 |
|
| 283 |
const nodePos = node.getAbsolutePosition();
|
| 284 |
codeWindow = new Konva.Group({
|
| 285 |
+
x: node.x(),
|
| 286 |
+
y: node.y() + height + 10
|
| 287 |
});
|
| 288 |
|
| 289 |
const codeBox = new Konva.Rect({
|
|
|
|
| 295 |
cornerRadius: 5
|
| 296 |
});
|
| 297 |
|
| 298 |
+
// Display source in Konva Text for visual containment
|
| 299 |
+
const codeText = new Konva.Text({
|
| 300 |
+
x: 5,
|
| 301 |
+
y: 5,
|
| 302 |
+
text: source || '',
|
| 303 |
+
fontSize: 12,
|
| 304 |
+
fontFamily: 'monospace',
|
| 305 |
+
fill: 'black',
|
| 306 |
+
width: 290,
|
| 307 |
+
padding: 5
|
| 308 |
+
});
|
| 309 |
+
|
| 310 |
+
codeWindow.add(codeBox);
|
| 311 |
+
codeWindow.add(codeText);
|
| 312 |
+
layer.add(codeWindow);
|
| 313 |
+
|
| 314 |
+
// Create textarea for editing
|
| 315 |
+
codeTextarea = document.createElement('textarea');
|
| 316 |
+
codeTextarea.style.position = 'absolute';
|
| 317 |
+
// Calculate position relative to canvas
|
| 318 |
+
const canvasRect = stage.container().getBoundingClientRect();
|
| 319 |
+
const textareaX = (nodePos.x + stage.x()) / scale + canvasRect.left;
|
| 320 |
+
const textareaY = (nodePos.y + height + 10 + stage.y()) / scale + canvasRect.top;
|
| 321 |
+
codeTextarea.style.left = `${textareaX}px`;
|
| 322 |
+
codeTextarea.style.top = `${textareaY}px`;
|
| 323 |
+
codeTextarea.style.width = `${300 / scale}px`;
|
| 324 |
+
codeTextarea.style.height = `${100 / scale}px`;
|
| 325 |
+
codeTextarea.style.fontFamily = 'monospace';
|
| 326 |
+
codeTextarea.style.fontSize = `${12 / scale}px`;
|
| 327 |
+
codeTextarea.style.background = 'transparent';
|
| 328 |
+
codeTextarea.style.border = 'none';
|
| 329 |
+
codeTextarea.style.resize = 'none';
|
| 330 |
+
codeTextarea.value = source || '';
|
| 331 |
+
document.body.appendChild(codeTextarea);
|
| 332 |
|
| 333 |
// Update code on change
|
| 334 |
+
codeTextarea.addEventListener('change', () => {
|
| 335 |
+
const newSource = codeTextarea.value;
|
| 336 |
node.data.source = newSource;
|
| 337 |
+
codeText.text(newSource);
|
| 338 |
fetch('/update_node', {
|
| 339 |
method: 'POST',
|
| 340 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 359 |
stage.on('click', (e) => {
|
| 360 |
if (e.target !== box && codeWindow) {
|
| 361 |
codeWindow.destroy();
|
|
|
|
| 362 |
codeWindow = null;
|
| 363 |
+
if (codeTextarea) {
|
| 364 |
+
codeTextarea.remove();
|
| 365 |
+
codeTextarea = null;
|
| 366 |
+
}
|
| 367 |
stage.off('click');
|
| 368 |
}
|
| 369 |
});
|
| 370 |
|
|
|
|
|
|
|
| 371 |
layer.draw();
|
| 372 |
});
|
| 373 |
|
|
|
|
| 375 |
node.on('dragmove', () => {
|
| 376 |
node.data.x = node.x();
|
| 377 |
node.data.y = node.y();
|
| 378 |
+
if (codeWindow && codeTextarea) {
|
| 379 |
const nodePos = node.getAbsolutePosition();
|
| 380 |
+
codeWindow.position({ x: node.x(), y: node.y() + height + 10 });
|
| 381 |
+
const canvasRect = stage.container().getBoundingClientRect();
|
| 382 |
+
const textareaX = (nodePos.x + stage.x()) / scale + canvasRect.left;
|
| 383 |
+
const textareaY = (nodePos.y + height + 10 + stage.y()) / scale + canvasRect.top;
|
| 384 |
+
codeTextarea.style.left = `${textareaX}px`;
|
| 385 |
+
codeTextarea.style.top = `${textareaY}px`;
|
| 386 |
}
|
| 387 |
updateConnections();
|
| 388 |
saveNodes();
|
|
|
|
| 430 |
|
| 431 |
// Enhanced auto-connect based on hierarchy, position, and role
|
| 432 |
function autoConnect() {
|
|
|
|
| 433 |
layer.find('Shape').forEach(shape => {
|
| 434 |
if (shape.data && shape.data.fromNodeId !== undefined) {
|
| 435 |
shape.destroy();
|
|
|
|
| 437 |
});
|
| 438 |
connections = [];
|
| 439 |
|
|
|
|
| 440 |
const sortedNodes = [...nodes].sort((a, b) => {
|
| 441 |
if (a.data.level !== b.data.level) return a.data.level - b.data.level;
|
| 442 |
+
return a.data.x - b.data.x; // Sort by x for left-to-right order
|
| 443 |
});
|
| 444 |
|
|
|
|
| 445 |
const hierarchy = {};
|
| 446 |
sortedNodes.forEach(node => {
|
| 447 |
const parent = node.data.parent_path.split(' -> ')[0] || 'global';
|
|
|
|
| 449 |
hierarchy[parent].push(node);
|
| 450 |
});
|
| 451 |
|
|
|
|
| 452 |
parsedConnections.forEach(conn => {
|
| 453 |
const fromNode = nodes.find(n => n.data.id === conn.from);
|
| 454 |
const toNode = nodes.find(n => n.data.id === conn.to);
|
|
|
|
| 467 |
}
|
| 468 |
});
|
| 469 |
|
|
|
|
| 470 |
sortedNodes.forEach(node => {
|
| 471 |
const nodeId = node.data.id;
|
| 472 |
const parent = node.data.parent_path.split(' -> ')[0] || 'global';
|
| 473 |
const role = node.data.type;
|
| 474 |
|
|
|
|
| 475 |
if (parent !== 'global') {
|
| 476 |
const parentNode = nodes.find(n => n.data.id === parent || n.data.label === parent.split('[')[0]);
|
| 477 |
if (parentNode && parentNode.data.outputs.length > 0 && node.data.inputs.length > 0) {
|
|
|
|
| 489 |
}
|
| 490 |
}
|
| 491 |
|
|
|
|
| 492 |
if (role.includes('variable')) {
|
| 493 |
const varName = node.data.label;
|
| 494 |
sortedNodes.forEach(otherNode => {
|
|
|
|
| 537 |
function reconstructProgram() {
|
| 538 |
const sortedNodes = [...nodes].sort((a, b) => {
|
| 539 |
if (a.data.level !== b.data.level) return a.data.level - b.data.level;
|
| 540 |
+
return a.data.x - b.data.x; // Sort by x for left-to-right order
|
| 541 |
});
|
| 542 |
|
| 543 |
let program = '';
|
|
|
|
| 626 |
})),
|
| 627 |
connections: connections
|
| 628 |
})
|
| 629 |
+
}).then(response => response.json золото
|
| 630 |
.then(data => console.log('Saved:', data))
|
| 631 |
.catch(error => console.error('Error:', error));
|
| 632 |
}
|