broadfield-dev commited on
Commit
d615ba9
·
verified ·
1 Parent(s): 4ee4a07

Update static/canvas.js

Browse files
Files changed (1) hide show
  1. static/canvas.js +149 -62
static/canvas.js CHANGED
@@ -10,9 +10,10 @@ stage.add(layer);
10
 
11
  // Store nodes, connections, and parsed data
12
  let nodes = [];
13
- let connections = [];
14
  let parsedConnections = [];
15
- let selectedNode = null;
 
16
 
17
  // Submit code or file for parsing
18
  function submitCode() {
@@ -49,7 +50,7 @@ function submitCode() {
49
  // Clear existing nodes and connections
50
  function clearCanvas() {
51
  nodes.forEach(node => node.destroy());
52
- layer.find('Shape').forEach(shape => shape.destroy()); // Includes Bezier curves
53
  nodes = [];
54
  connections = [];
55
  parsedConnections = [];
@@ -71,8 +72,6 @@ function createNodesFromParsedData(parsedNodes, parsedConnections) {
71
  nodes.push(node);
72
  layer.add(node);
73
  });
74
-
75
- // Initially, don't draw connections; wait for auto-connect or manual
76
  layer.draw();
77
  saveNodes();
78
  }
@@ -110,25 +109,97 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id) {
110
  node.add(box);
111
  node.add(text);
112
 
113
- // Add input/output ports
114
- inputs.forEach((input, i) => {
115
- const circle = new Konva.Circle({
 
 
116
  x: 0,
117
  y: 10 + i * 20,
118
  radius: 5,
119
  fill: 'red'
120
- });
121
- node.add(circle);
122
- });
123
 
124
- outputs.forEach((output, i) => {
125
- const circle = new Konva.Circle({
 
 
126
  x: 100,
127
  y: 10 + i * 20,
128
  radius: 5,
129
  fill: 'green'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  });
131
- node.add(circle);
132
  });
133
 
134
  // Node data
@@ -136,27 +207,12 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id) {
136
  id: id,
137
  type: type,
138
  label: label,
139
- inputs: inputs,
140
- outputs: outputs,
141
  x: x,
142
  y: y
143
  };
144
 
145
- // Handle node click for manual connections
146
- node.on('click', () => {
147
- if (!selectedNode) {
148
- selectedNode = node;
149
- } else {
150
- createSplineConnection(selectedNode, node);
151
- connections.push({
152
- from: selectedNode.data.id,
153
- to: node.data.id
154
- });
155
- selectedNode = null;
156
- saveNodes();
157
- }
158
- });
159
-
160
  // Update position and connections on drag
161
  node.on('dragmove', () => {
162
  node.data.x = node.x();
@@ -169,11 +225,15 @@ function createNode(x, y, label, type, inputs = [], outputs = [], id) {
169
  }
170
 
171
  // Create a spline (Bezier curve) connection
172
- function createSplineConnection(fromNode, toNode) {
173
- const startX = fromNode.x() + 100;
174
- const startY = fromNode.y() + 25;
175
- const endX = toNode.x();
176
- const endY = toNode.y() + 25;
 
 
 
 
177
 
178
  // Control points for Bezier curve
179
  const control1X = startX + (endX - startX) / 3;
@@ -192,7 +252,12 @@ function createSplineConnection(fromNode, toNode) {
192
  strokeWidth: 2
193
  });
194
 
195
- spline.data = { from: fromNode.data.id, to: toNode.data.id };
 
 
 
 
 
196
  layer.add(spline);
197
  layer.draw();
198
  }
@@ -201,7 +266,7 @@ function createSplineConnection(fromNode, toNode) {
201
  function autoConnect() {
202
  // Clear existing connections
203
  layer.find('Shape').forEach(shape => {
204
- if (shape.data && shape.data.from !== undefined) {
205
  shape.destroy();
206
  }
207
  });
@@ -212,8 +277,18 @@ function autoConnect() {
212
  const fromNode = nodes.find(n => n.data.id === conn.from);
213
  const toNode = nodes.find(n => n.data.id === conn.to);
214
  if (fromNode && toNode) {
215
- createSplineConnection(fromNode, toNode);
216
- connections.push({ from: conn.from, to: conn.to });
 
 
 
 
 
 
 
 
 
 
217
  }
218
  });
219
 
@@ -228,8 +303,8 @@ function addNode() {
228
  Math.random() * (stage.height() - 100),
229
  'Function',
230
  'function',
231
- [],
232
- [],
233
  nodes.length
234
  );
235
  nodes.push(node);
@@ -241,26 +316,30 @@ function addNode() {
241
  // Update spline connections when nodes move
242
  function updateConnections() {
243
  layer.find('Shape').forEach(shape => {
244
- if (shape.data && shape.data.from !== undefined) {
245
- const fromNode = nodes.find(n => n.data.id === shape.data.from);
246
- const toNode = nodes.find(n => n.data.id === shape.data.to);
247
  if (fromNode && toNode) {
248
- const startX = fromNode.x() + 100;
249
- const startY = fromNode.y() + 25;
250
- const endX = toNode.x();
251
- const endY = toNode.y() + 25;
252
-
253
- const control1X = startX + (endX - startX) / 3;
254
- const control1Y = startY;
255
- const control2X = startX + 2 * (endX - startX) / 3;
256
- const control2Y = endY;
257
-
258
- shape.sceneFunc(function(context, shape) {
259
- context.beginPath();
260
- context.moveTo(startX, startY);
261
- context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY);
262
- context.fillStrokeShape(shape);
263
- });
 
 
 
 
264
  }
265
  }
266
  });
@@ -275,7 +354,15 @@ function saveNodes() {
275
  'Content-Type': 'application/json'
276
  },
277
  body: JSON.stringify({
278
- nodes: nodes.map(n => n.data),
 
 
 
 
 
 
 
 
279
  connections: connections
280
  })
281
  }).then(response => response.json())
 
10
 
11
  // Store nodes, connections, and parsed data
12
  let nodes = [];
13
+ let connections = []; // { fromNodeId, fromPortId, toNodeId, toPortId }
14
  let parsedConnections = [];
15
+ let selectedPort = null; // { node, portId, type: 'input'|'output' }
16
+ let disconnectMode = false;
17
 
18
  // Submit code or file for parsing
19
  function submitCode() {
 
50
  // Clear existing nodes and connections
51
  function clearCanvas() {
52
  nodes.forEach(node => node.destroy());
53
+ layer.find('Shape').forEach(shape => shape.destroy());
54
  nodes = [];
55
  connections = [];
56
  parsedConnections = [];
 
72
  nodes.push(node);
73
  layer.add(node);
74
  });
 
 
75
  layer.draw();
76
  saveNodes();
77
  }
 
109
  node.add(box);
110
  node.add(text);
111
 
112
+ // Add input/output ports with unique IDs
113
+ const inputPorts = inputs.map((input, i) => ({
114
+ id: `input-${id}-${i}`,
115
+ name: input,
116
+ circle: new Konva.Circle({
117
  x: 0,
118
  y: 10 + i * 20,
119
  radius: 5,
120
  fill: 'red'
121
+ })
122
+ }));
 
123
 
124
+ const outputPorts = outputs.map((output, i) => ({
125
+ id: `output-${id}-${i}`,
126
+ name: output,
127
+ circle: new Konva.Circle({
128
  x: 100,
129
  y: 10 + i * 20,
130
  radius: 5,
131
  fill: 'green'
132
+ })
133
+ }));
134
+
135
+ // Add ports to node and set up click handlers
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; // First click on input enables disconnect mode
142
+ } else if (selectedPort.type === 'output' && selectedPort.node !== node) {
143
+ // Connect output to input
144
+ createSplineConnection(
145
+ selectedPort.node,
146
+ selectedPort.portId,
147
+ node,
148
+ port.id
149
+ );
150
+ connections.push({
151
+ fromNodeId: selectedPort.node.data.id,
152
+ fromPortId: selectedPort.portId,
153
+ toNodeId: node.data.id,
154
+ toPortId: port.id
155
+ });
156
+ selectedPort = null;
157
+ disconnectMode = false;
158
+ saveNodes();
159
+ } else if (disconnectMode && selectedPort.type === 'input' && selectedPort.node === node) {
160
+ // Select output to disconnect
161
+ selectedPort = { node, portId: port.id, type: 'input' }; // Keep input selected
162
+ } else {
163
+ selectedPort = null;
164
+ disconnectMode = false;
165
+ }
166
+ });
167
+ });
168
+
169
+ outputPorts.forEach(port => {
170
+ node.add(port.circle);
171
+ port.circle.on('click', () => {
172
+ if (!selectedPort) {
173
+ selectedPort = { node, portId: port.id, type: 'output' };
174
+ disconnectMode = false;
175
+ } else if (disconnectMode && selectedPort.type === 'input') {
176
+ // Disconnect input from output
177
+ const connIndex = connections.findIndex(
178
+ c => c.toNodeId === selectedPort.node.data.id &&
179
+ c.toPortId === selectedPort.portId &&
180
+ c.fromNodeId === node.data.id &&
181
+ c.fromPortId === port.id
182
+ );
183
+ if (connIndex !== -1) {
184
+ const conn = connections[connIndex];
185
+ const spline = layer.find('Shape').find(s =>
186
+ s.data.fromNodeId === conn.fromNodeId &&
187
+ s.data.fromPortId === conn.fromPortId &&
188
+ s.data.toNodeId === conn.toNodeId &&
189
+ s.data.toPortId === conn.toPortId
190
+ );
191
+ if (spline) spline.destroy();
192
+ connections.splice(connIndex, 1);
193
+ layer.draw();
194
+ saveNodes();
195
+ }
196
+ selectedPort = null;
197
+ disconnectMode = false;
198
+ } else {
199
+ selectedPort = null;
200
+ disconnectMode = false;
201
+ }
202
  });
 
203
  });
204
 
205
  // Node data
 
207
  id: id,
208
  type: type,
209
  label: label,
210
+ inputs: inputPorts,
211
+ outputs: outputPorts,
212
  x: x,
213
  y: y
214
  };
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  // Update position and connections on drag
217
  node.on('dragmove', () => {
218
  node.data.x = node.x();
 
225
  }
226
 
227
  // Create a spline (Bezier curve) connection
228
+ function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
229
+ const fromPort = fromNode.data.outputs.find(p => p.id === fromPortId);
230
+ const toPort = toNode.data.inputs.find(p => p.id === toPortId);
231
+ if (!fromPort || !toPort) return;
232
+
233
+ const startX = fromNode.x() + fromPort.circle.x();
234
+ const startY = fromNode.y() + fromPort.circle.y();
235
+ const endX = toNode.x() + toPort.circle.x();
236
+ const endY = toNode.y() + toPort.circle.y();
237
 
238
  // Control points for Bezier curve
239
  const control1X = startX + (endX - startX) / 3;
 
252
  strokeWidth: 2
253
  });
254
 
255
+ spline.data = {
256
+ fromNodeId: fromNode.data.id,
257
+ fromPortId: fromPortId,
258
+ toNodeId: toNode.data.id,
259
+ toPortId: toPortId
260
+ };
261
  layer.add(spline);
262
  layer.draw();
263
  }
 
266
  function autoConnect() {
267
  // Clear existing connections
268
  layer.find('Shape').forEach(shape => {
269
+ if (shape.data && shape.data.fromNodeId !== undefined) {
270
  shape.destroy();
271
  }
272
  });
 
277
  const fromNode = nodes.find(n => n.data.id === conn.from);
278
  const toNode = nodes.find(n => n.data.id === conn.to);
279
  if (fromNode && toNode) {
280
+ // Find first available output and input ports
281
+ const fromPort = fromNode.data.outputs[0];
282
+ const toPort = toNode.data.inputs[0];
283
+ if (fromPort && toPort) {
284
+ createSplineConnection(fromNode, fromPort.id, toNode, toPort.id);
285
+ connections.push({
286
+ fromNodeId: fromNode.data.id,
287
+ fromPortId: fromPort.id,
288
+ toNodeId: toNode.data.id,
289
+ toPortId: toPort.id
290
+ });
291
+ }
292
  }
293
  });
294
 
 
303
  Math.random() * (stage.height() - 100),
304
  'Function',
305
  'function',
306
+ ['in1'],
307
+ ['out1'],
308
  nodes.length
309
  );
310
  nodes.push(node);
 
316
  // Update spline connections when nodes move
317
  function updateConnections() {
318
  layer.find('Shape').forEach(shape => {
319
+ if (shape.data && shape.data.fromNodeId !== undefined) {
320
+ const fromNode = nodes.find(n => n.data.id === shape.data.fromNodeId);
321
+ const toNode = nodes.find(n => n.data.id === shape.data.toNodeId);
322
  if (fromNode && toNode) {
323
+ const fromPort = fromNode.data.outputs.find(p => p.id === shape.data.fromPortId);
324
+ const toPort = toNode.data.inputs.find(p => p.id === shape.data.toPortId);
325
+ if (fromPort && toPort) {
326
+ const startX = fromNode.x() + fromPort.circle.x();
327
+ const startY = fromNode.y() + fromPort.circle.y();
328
+ const endX = toNode.x() + toPort.circle.x();
329
+ const endY = toNode.y() + toPort.circle.y();
330
+
331
+ const control1X = startX + (endX - startX) / 3;
332
+ const control1Y = startY;
333
+ const control2X = startX + 2 * (endX - startX) / 3;
334
+ const control2Y = endY;
335
+
336
+ shape.sceneFunc(function(context, shape) {
337
+ context.beginPath();
338
+ context.moveTo(startX, startY);
339
+ context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY);
340
+ context.fillStrokeShape(shape);
341
+ });
342
+ }
343
  }
344
  }
345
  });
 
354
  'Content-Type': 'application/json'
355
  },
356
  body: JSON.stringify({
357
+ nodes: nodes.map(n => ({
358
+ id: n.data.id,
359
+ type: n.data.type,
360
+ label: n.data.label,
361
+ x: n.data.x,
362
+ y: n.data.y,
363
+ inputs: n.data.inputs.map(p => p.name),
364
+ outputs: n.data.outputs.map(p => p.name)
365
+ })),
366
  connections: connections
367
  })
368
  }).then(response => response.json())