Spaces:
Running
Running
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 |
}
|