broadfield-dev commited on
Commit
837c995
·
verified ·
1 Parent(s): 4ef30c5

Update static/canvas.js

Browse files
Files changed (1) hide show
  1. static/canvas.js +79 -28
static/canvas.js CHANGED
@@ -2,18 +2,58 @@
2
  const stage = new Konva.Stage({
3
  container: 'container',
4
  width: 1000,
5
- height: 600
 
6
  });
7
 
8
  const layer = new Konva.Layer();
9
  stage.add(layer);
10
 
 
11
  let nodes = [];
12
- let connections = [];
13
  let parsedConnections = [];
14
- let selectedPort = null;
15
  let disconnectMode = false;
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  function submitCode() {
18
  const fileInput = document.getElementById('codeFile');
19
  const codeInput = document.getElementById('codeInput').value;
@@ -39,12 +79,13 @@ function submitCode() {
39
  return;
40
  }
41
  clearCanvas();
42
- parsedConnections = data.connections;
43
  createNodesFromParsedData(data.nodes, data.connections);
44
  })
45
  .catch(error => console.error('Error:', error));
46
  }
47
 
 
48
  function clearCanvas() {
49
  nodes.forEach(node => node.destroy());
50
  layer.find('Shape').forEach(shape => shape.destroy());
@@ -54,6 +95,7 @@ function clearCanvas() {
54
  layer.draw();
55
  }
56
 
 
57
  function createNodesFromParsedData(parsedNodes, parsedConnections) {
58
  parsedNodes.forEach(nodeData => {
59
  const node = createNode(
@@ -63,8 +105,7 @@ function createNodesFromParsedData(parsedNodes, parsedConnections) {
63
  nodeData.type,
64
  nodeData.inputs,
65
  nodeData.outputs,
66
- nodeData.id,
67
- nodeData.value
68
  );
69
  nodes.push(node);
70
  layer.add(node);
@@ -73,44 +114,40 @@ function createNodesFromParsedData(parsedNodes, parsedConnections) {
73
  saveNodes();
74
  }
75
 
76
- function createNode(x, y, label, type, inputs = [], outputs = [], id, value = null) {
 
77
  const node = new Konva.Group({
78
  x: x,
79
  y: y,
80
  draggable: true
81
  });
82
 
83
- // Node appearance based on type
84
- const isNumberBox = type === 'number_box';
85
- const color = isNumberBox ? '#ffcccb' : type === 'function' ? '#ffeb3b' : type.includes('variable') ? '#90caf9' : type === 'import' ? '#a5d6a7' : '#ccc';
86
- const width = isNumberBox ? 80 : 100;
87
- const height = isNumberBox ? 40 : 50;
88
-
89
  const box = new Konva.Rect({
90
- width: width,
91
- height: height,
92
  fill: color,
93
  stroke: 'black',
94
  strokeWidth: 2,
95
  cornerRadius: 5
96
  });
97
 
98
- // Node label and value
99
- const textContent = isNumberBox && value ? `${label} = ${value}` : label;
100
  const text = new Konva.Text({
101
- text: textContent,
102
  fontSize: 12,
103
  fontFamily: 'Arial',
104
  fill: 'black',
105
- width: width,
106
  align: 'center',
107
- y: isNumberBox ? 14 : 20
108
  });
109
 
110
  node.add(box);
111
  node.add(text);
112
 
113
- // Input/output ports
114
  const inputPorts = inputs.map((input, i) => ({
115
  id: `input-${id}-${i}`,
116
  name: input,
@@ -126,20 +163,22 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
126
  id: `output-${id}-${i}`,
127
  name: output,
128
  circle: new Konva.Circle({
129
- x: width,
130
  y: 10 + i * 20,
131
  radius: 5,
132
  fill: 'green'
133
  })
134
  }));
135
 
 
136
  inputPorts.forEach(port => {
137
  node.add(port.circle);
138
  port.circle.on('click', () => {
139
  if (!selectedPort) {
140
  selectedPort = { node, portId: port.id, type: 'input' };
141
- disconnectMode = true;
142
  } else if (selectedPort.type === 'output' && selectedPort.node !== node) {
 
143
  createSplineConnection(
144
  selectedPort.node,
145
  selectedPort.portId,
@@ -156,7 +195,8 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
156
  disconnectMode = false;
157
  saveNodes();
158
  } else if (disconnectMode && selectedPort.type === 'input' && selectedPort.node === node) {
159
- selectedPort = { node, portId: port.id, type: 'input' };
 
160
  } else {
161
  selectedPort = null;
162
  disconnectMode = false;
@@ -171,6 +211,7 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
171
  selectedPort = { node, portId: port.id, type: 'output' };
172
  disconnectMode = false;
173
  } else if (disconnectMode && selectedPort.type === 'input') {
 
174
  const connIndex = connections.findIndex(
175
  c => c.toNodeId === selectedPort.node.data.id &&
176
  c.toPortId === selectedPort.portId &&
@@ -199,6 +240,7 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
199
  });
200
  });
201
 
 
202
  node.data = {
203
  id: id,
204
  type: type,
@@ -206,10 +248,10 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
206
  inputs: inputPorts,
207
  outputs: outputPorts,
208
  x: x,
209
- y: y,
210
- value: value
211
  };
212
 
 
213
  node.on('dragmove', () => {
214
  node.data.x = node.x();
215
  node.data.y = node.y();
@@ -220,6 +262,7 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id, value = nu
220
  return node;
221
  }
222
 
 
223
  function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
224
  const fromPort = fromNode.data.outputs.find(p => p.id === fromPortId);
225
  const toPort = toNode.data.inputs.find(p => p.id === toPortId);
@@ -230,6 +273,7 @@ function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
230
  const endX = toNode.x() + toPort.circle.x();
231
  const endY = toNode.y() + toPort.circle.y();
232
 
 
233
  const control1X = startX + (endX - startX) / 3;
234
  const control1Y = startY;
235
  const control2X = startX + 2 * (endX - startX) / 3;
@@ -256,7 +300,9 @@ function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
256
  layer.draw();
257
  }
258
 
 
259
  function autoConnect() {
 
260
  layer.find('Shape').forEach(shape => {
261
  if (shape.data && shape.data.fromNodeId !== undefined) {
262
  shape.destroy();
@@ -264,10 +310,12 @@ function autoConnect() {
264
  });
265
  connections = [];
266
 
 
267
  parsedConnections.forEach(conn => {
268
  const fromNode = nodes.find(n => n.data.id === conn.from);
269
  const toNode = nodes.find(n => n.data.id === conn.to);
270
  if (fromNode && toNode) {
 
271
  const fromPort = fromNode.data.outputs[0];
272
  const toPort = toNode.data.inputs[0];
273
  if (fromPort && toPort) {
@@ -286,6 +334,7 @@ function autoConnect() {
286
  saveNodes();
287
  }
288
 
 
289
  function addNode() {
290
  const node = createNode(
291
  Math.random() * (stage.width() - 100),
@@ -302,6 +351,7 @@ function addNode() {
302
  saveNodes();
303
  }
304
 
 
305
  function updateConnections() {
306
  layer.find('Shape').forEach(shape => {
307
  if (shape.data && shape.data.fromNodeId !== undefined) {
@@ -334,6 +384,7 @@ function updateConnections() {
334
  layer.draw();
335
  }
336
 
 
337
  function saveNodes() {
338
  fetch('/save_nodes', {
339
  method: 'POST',
@@ -348,8 +399,7 @@ function saveNodes() {
348
  x: n.data.x,
349
  y: n.data.y,
350
  inputs: n.data.inputs.map(p => p.name),
351
- outputs: n.data.outputs.map(p => p.name),
352
- value: n.data.value
353
  })),
354
  connections: connections
355
  })
@@ -358,4 +408,5 @@ function saveNodes() {
358
  .catch(error => console.error('Error:', error));
359
  }
360
 
 
361
  layer.draw();
 
2
  const stage = new Konva.Stage({
3
  container: 'container',
4
  width: 1000,
5
+ height: 600,
6
+ draggable: true // Enable stage dragging for panning
7
  });
8
 
9
  const layer = new Konva.Layer();
10
  stage.add(layer);
11
 
12
+ // Store nodes, connections, and parsed data
13
  let nodes = [];
14
+ let connections = []; // { fromNodeId, fromPortId, toNodeId, toPortId }
15
  let parsedConnections = [];
16
+ let selectedPort = null; // { node, portId, type: 'input'|'output' }
17
  let disconnectMode = false;
18
 
19
+ // Zoom functionality
20
+ let scale = 1;
21
+ const scaleFactor = 1.1;
22
+ const minScale = 0.5;
23
+ const maxScale = 2.0;
24
+
25
+ stage.on('wheel', (e) => {
26
+ e.evt.preventDefault(); // Prevent default browser scrolling
27
+
28
+ const oldScale = scale;
29
+ const pointer = stage.getPointerPosition();
30
+
31
+ // Determine zoom direction
32
+ const delta = e.evt.deltaY > 0 ? 1 / scaleFactor : scaleFactor;
33
+ const newScale = Math.max(minScale, Math.min(maxScale, oldScale * delta));
34
+
35
+ if (newScale === scale) return; // No change in scale
36
+ scale = newScale;
37
+
38
+ // Calculate mouse position relative to stage
39
+ const mousePointTo = {
40
+ x: (pointer.x - stage.x()) / oldScale,
41
+ y: (pointer.y - stage.y()) / oldScale
42
+ };
43
+
44
+ // Apply new scale
45
+ stage.scale({ x: scale, y: scale });
46
+
47
+ // Adjust stage position to keep the zoom centered on the mouse
48
+ const newPos = {
49
+ x: pointer.x - mousePointTo.x * scale,
50
+ y: pointer.y - mousePointTo.y * scale
51
+ };
52
+ stage.position(newPos);
53
+ stage.batchDraw();
54
+ });
55
+
56
+ // Submit code or file for parsing
57
  function submitCode() {
58
  const fileInput = document.getElementById('codeFile');
59
  const codeInput = document.getElementById('codeInput').value;
 
79
  return;
80
  }
81
  clearCanvas();
82
+ parsedConnections = data.connections; // Store for auto-connect
83
  createNodesFromParsedData(data.nodes, data.connections);
84
  })
85
  .catch(error => console.error('Error:', error));
86
  }
87
 
88
+ // Clear existing nodes and connections
89
  function clearCanvas() {
90
  nodes.forEach(node => node.destroy());
91
  layer.find('Shape').forEach(shape => shape.destroy());
 
95
  layer.draw();
96
  }
97
 
98
+ // Create nodes and connections from parsed data
99
  function createNodesFromParsedData(parsedNodes, parsedConnections) {
100
  parsedNodes.forEach(nodeData => {
101
  const node = createNode(
 
105
  nodeData.type,
106
  nodeData.inputs,
107
  nodeData.outputs,
108
+ nodeData.id
 
109
  );
110
  nodes.push(node);
111
  layer.add(node);
 
114
  saveNodes();
115
  }
116
 
117
+ // Create a node with inputs and outputs
118
+ function createNode(x, y, label, type, inputs = [], outputs = [], id) {
119
  const node = new Konva.Group({
120
  x: x,
121
  y: y,
122
  draggable: true
123
  });
124
 
125
+ // Node rectangle
126
+ const color = type === 'function' ? '#ffeb3b' : type.includes('variable') ? '#90caf9' : type === 'import' ? '#a5d6a7' : '#ccc';
 
 
 
 
127
  const box = new Konva.Rect({
128
+ width: 100,
129
+ height: 50,
130
  fill: color,
131
  stroke: 'black',
132
  strokeWidth: 2,
133
  cornerRadius: 5
134
  });
135
 
136
+ // Node label
 
137
  const text = new Konva.Text({
138
+ text: label,
139
  fontSize: 12,
140
  fontFamily: 'Arial',
141
  fill: 'black',
142
+ width: 100,
143
  align: 'center',
144
+ y: 20
145
  });
146
 
147
  node.add(box);
148
  node.add(text);
149
 
150
+ // Add input/output ports with unique IDs
151
  const inputPorts = inputs.map((input, i) => ({
152
  id: `input-${id}-${i}`,
153
  name: input,
 
163
  id: `output-${id}-${i}`,
164
  name: output,
165
  circle: new Konva.Circle({
166
+ x: 100,
167
  y: 10 + i * 20,
168
  radius: 5,
169
  fill: 'green'
170
  })
171
  }));
172
 
173
+ // Add ports to node and set up click handlers
174
  inputPorts.forEach(port => {
175
  node.add(port.circle);
176
  port.circle.on('click', () => {
177
  if (!selectedPort) {
178
  selectedPort = { node, portId: port.id, type: 'input' };
179
+ disconnectMode = true; // First click on input enables disconnect mode
180
  } else if (selectedPort.type === 'output' && selectedPort.node !== node) {
181
+ // Connect output to input
182
  createSplineConnection(
183
  selectedPort.node,
184
  selectedPort.portId,
 
195
  disconnectMode = false;
196
  saveNodes();
197
  } else if (disconnectMode && selectedPort.type === 'input' && selectedPort.node === node) {
198
+ // Select output to disconnect
199
+ selectedPort = { node, portId: port.id, type: 'input' }; // Keep input selected
200
  } else {
201
  selectedPort = null;
202
  disconnectMode = false;
 
211
  selectedPort = { node, portId: port.id, type: 'output' };
212
  disconnectMode = false;
213
  } else if (disconnectMode && selectedPort.type === 'input') {
214
+ // Disconnect input from output
215
  const connIndex = connections.findIndex(
216
  c => c.toNodeId === selectedPort.node.data.id &&
217
  c.toPortId === selectedPort.portId &&
 
240
  });
241
  });
242
 
243
+ // Node data
244
  node.data = {
245
  id: id,
246
  type: type,
 
248
  inputs: inputPorts,
249
  outputs: outputPorts,
250
  x: x,
251
+ y: y
 
252
  };
253
 
254
+ // Update position and connections on drag
255
  node.on('dragmove', () => {
256
  node.data.x = node.x();
257
  node.data.y = node.y();
 
262
  return node;
263
  }
264
 
265
+ // Create a spline (Bezier curve) connection
266
  function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
267
  const fromPort = fromNode.data.outputs.find(p => p.id === fromPortId);
268
  const toPort = toNode.data.inputs.find(p => p.id === toPortId);
 
273
  const endX = toNode.x() + toPort.circle.x();
274
  const endY = toNode.y() + toPort.circle.y();
275
 
276
+ // Control points for Bezier curve
277
  const control1X = startX + (endX - startX) / 3;
278
  const control1Y = startY;
279
  const control2X = startX + 2 * (endX - startX) / 3;
 
300
  layer.draw();
301
  }
302
 
303
+ // Auto-connect nodes based on parsed connections
304
  function autoConnect() {
305
+ // Clear existing connections
306
  layer.find('Shape').forEach(shape => {
307
  if (shape.data && shape.data.fromNodeId !== undefined) {
308
  shape.destroy();
 
310
  });
311
  connections = [];
312
 
313
+ // Create spline connections for parsed data
314
  parsedConnections.forEach(conn => {
315
  const fromNode = nodes.find(n => n.data.id === conn.from);
316
  const toNode = nodes.find(n => n.data.id === conn.to);
317
  if (fromNode && toNode) {
318
+ // Find first available output and input ports
319
  const fromPort = fromNode.data.outputs[0];
320
  const toPort = toNode.data.inputs[0];
321
  if (fromPort && toPort) {
 
334
  saveNodes();
335
  }
336
 
337
+ // Add a manual node
338
  function addNode() {
339
  const node = createNode(
340
  Math.random() * (stage.width() - 100),
 
351
  saveNodes();
352
  }
353
 
354
+ // Update spline connections when nodes move
355
  function updateConnections() {
356
  layer.find('Shape').forEach(shape => {
357
  if (shape.data && shape.data.fromNodeId !== undefined) {
 
384
  layer.draw();
385
  }
386
 
387
+ // Save nodes and connections to backend
388
  function saveNodes() {
389
  fetch('/save_nodes', {
390
  method: 'POST',
 
399
  x: n.data.x,
400
  y: n.data.y,
401
  inputs: n.data.inputs.map(p => p.name),
402
+ outputs: n.data.outputs.map(p => p.name)
 
403
  })),
404
  connections: connections
405
  })
 
408
  .catch(error => console.error('Error:', error));
409
  }
410
 
411
+ // Initial draw
412
  layer.draw();