senangh commited on
Commit
6abfbcd
·
verified ·
1 Parent(s): 4c0eb4a

undefined - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +763 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: V1 Nodevis
3
- emoji: 🦀
4
- colorFrom: red
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: v1-nodevis
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,763 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NodeVis - Interactive Node Visualization</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/konva@8.3.2/konva.min.js"></script>
9
+ <style>
10
+ .tooltip {
11
+ position: relative;
12
+ display: inline-block;
13
+ }
14
+ .tooltip .tooltiptext {
15
+ visibility: hidden;
16
+ width: 120px;
17
+ background-color: #555;
18
+ color: #fff;
19
+ text-align: center;
20
+ border-radius: 6px;
21
+ padding: 5px;
22
+ position: absolute;
23
+ z-index: 1;
24
+ bottom: 125%;
25
+ left: 50%;
26
+ margin-left: -60px;
27
+ opacity: 0;
28
+ transition: opacity 0.3s;
29
+ }
30
+ .tooltip:hover .tooltiptext {
31
+ visibility: visible;
32
+ opacity: 1;
33
+ }
34
+ #container {
35
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
36
+ }
37
+ </style>
38
+ </head>
39
+ <body class="bg-gray-100 min-h-screen">
40
+ <div class="container mx-auto px-4 py-8">
41
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8">
42
+ <div class="flex items-center mb-4 md:mb-0">
43
+ <div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center mr-3">
44
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
45
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
46
+ </svg>
47
+ </div>
48
+ <h1 class="text-3xl font-bold text-gray-800">NodeVis</h1>
49
+ </div>
50
+ <div class="flex space-x-4">
51
+ <button id="addNodeBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center transition">
52
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
53
+ <path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
54
+ </svg>
55
+ Add Node
56
+ </button>
57
+ <button id="clearBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg flex items-center transition">
58
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
59
+ <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
60
+ </svg>
61
+ Clear All
62
+ </button>
63
+ <div class="tooltip">
64
+ <button id="helpBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg flex items-center transition">
65
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
66
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
67
+ </svg>
68
+ </button>
69
+ <span class="tooltiptext">Click to add nodes, drag to connect them</span>
70
+ </div>
71
+ </div>
72
+ </header>
73
+
74
+ <div class="bg-white rounded-xl p-4 shadow-lg mb-6">
75
+ <div class="flex flex-wrap gap-4 mb-4">
76
+ <div class="flex items-center">
77
+ <div class="w-4 h-4 rounded-full bg-blue-500 mr-2"></div>
78
+ <span class="text-sm">Nodes</span>
79
+ </div>
80
+ <div class="flex items-center">
81
+ <div class="w-4 h-4 rounded-full bg-green-500 mr-2"></div>
82
+ <span class="text-sm">Start Node</span>
83
+ </div>
84
+ <div class="flex items-center">
85
+ <div class="w-4 h-4 rounded-full bg-red-500 mr-2"></div>
86
+ <span class="text-sm">End Node</span>
87
+ </div>
88
+ <div class="flex items-center">
89
+ <svg height="20" width="20">
90
+ <line x1="0" y1="10" x2="20" y2="10" style="stroke:gray;stroke-width:2" />
91
+ </svg>
92
+ <span class="text-sm ml-2">Connections</span>
93
+ </div>
94
+ </div>
95
+ <div id="nodeCounter" class="text-gray-600 text-sm">0 nodes created</div>
96
+ </div>
97
+
98
+ <div id="container" class="w-full h-96 bg-white rounded-xl overflow-hidden"></div>
99
+
100
+ <div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4">
101
+ <div class="bg-white p-4 rounded-lg shadow">
102
+ <h3 class="font-semibold text-lg mb-2">Node Properties</h3>
103
+ <div id="nodeProps" class="text-gray-600">Select a node to edit properties</div>
104
+ </div>
105
+ <div class="bg-white p-4 rounded-lg shadow">
106
+ <h3 class="font-semibold text-lg mb-2">Connection Info</h3>
107
+ <div id="connectionInfo" class="text-gray-600">Click on a connection for details</div>
108
+ </div>
109
+ <div class="bg-white p-4 rounded-lg shadow">
110
+ <h3 class="font-semibold text-lg mb-2">Export/Import</h3>
111
+ <div class="flex space-x-2">
112
+ <button id="exportBtn" class="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded text-sm">Export JSON</button>
113
+ <button id="importBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-3 py-1 rounded text-sm">Import JSON</button>
114
+ <input type="file" id="fileInput" class="hidden" accept=".json">
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <script>
121
+ document.addEventListener('DOMContentLoaded', function() {
122
+ // Initialize Konva stage
123
+ const width = document.getElementById('container').offsetWidth;
124
+ const height = 600;
125
+
126
+ const stage = new Konva.Stage({
127
+ container: 'container',
128
+ width: width,
129
+ height: height
130
+ });
131
+
132
+ const layer = new Konva.Layer();
133
+ stage.add(layer);
134
+
135
+ // State variables
136
+ let nodes = [];
137
+ let connections = [];
138
+ let selectedNode = null;
139
+ let drawingConnection = false;
140
+ let tempLine = null;
141
+ let startNode = null;
142
+ let nodeCounter = 0;
143
+
144
+ // Update node counter display
145
+ function updateNodeCounter() {
146
+ document.getElementById('nodeCounter').textContent =
147
+ `${nodes.length} node${nodes.length !== 1 ? 's' : ''} created, ${connections.length} connection${connections.length !== 1 ? 's' : ''}`;
148
+ }
149
+
150
+ // Create a new node
151
+ function createNode(x, y) {
152
+ nodeCounter++;
153
+ const nodeId = `node-${nodeCounter}`;
154
+
155
+ const nodeGroup = new Konva.Group({
156
+ x: x,
157
+ y: y,
158
+ id: nodeId,
159
+ draggable: true
160
+ });
161
+
162
+ const circle = new Konva.Circle({
163
+ radius: 30,
164
+ fill: '#3B82F6',
165
+ stroke: '#1D4ED8',
166
+ strokeWidth: 2,
167
+ shadowColor: 'black',
168
+ shadowBlur: 10,
169
+ shadowOpacity: 0.2,
170
+ shadowOffset: { x: 2, y: 2 }
171
+ });
172
+
173
+ const text = new Konva.Text({
174
+ text: nodeCounter.toString(),
175
+ fontSize: 18,
176
+ fontFamily: 'Arial',
177
+ fill: 'white',
178
+ align: 'center',
179
+ verticalAlign: 'middle',
180
+ width: circle.radius() * 2,
181
+ height: circle.radius() * 2,
182
+ offsetX: circle.radius(),
183
+ offsetY: circle.radius() / 1.5
184
+ });
185
+
186
+ nodeGroup.add(circle);
187
+ nodeGroup.add(text);
188
+ layer.add(nodeGroup);
189
+ layer.draw();
190
+
191
+ nodes.push({
192
+ id: nodeId,
193
+ group: nodeGroup,
194
+ connections: [],
195
+ isStart: false,
196
+ isEnd: false
197
+ });
198
+
199
+ updateNodeCounter();
200
+
201
+ // Add event listeners
202
+ nodeGroup.on('dragstart', function() {
203
+ this.moveToTop();
204
+ layer.draw();
205
+ });
206
+
207
+ nodeGroup.on('dragmove', function() {
208
+ // Update all connections from/to this node
209
+ updateConnectionsForNode(this);
210
+ });
211
+
212
+ nodeGroup.on('click tap', function(e) {
213
+ e.cancelBubble = true;
214
+
215
+ // Deselect previously selected node
216
+ if (selectedNode) {
217
+ selectedNode.group.children[0].stroke('#1D4ED8');
218
+ selectedNode.group.children[0].strokeWidth(2);
219
+ }
220
+
221
+ // Select this node
222
+ selectedNode = nodes.find(n => n.id === this.id());
223
+ this.children[0].stroke('#F59E0B');
224
+ this.children[0].strokeWidth(3);
225
+
226
+ // Update properties panel
227
+ updateNodePropertiesPanel();
228
+
229
+ layer.draw();
230
+ });
231
+
232
+ return nodeGroup;
233
+ }
234
+
235
+ // Update connections when a node is moved
236
+ function updateConnectionsForNode(nodeGroup) {
237
+ const nodeId = nodeGroup.id();
238
+
239
+ // Update connections where this node is the start
240
+ connections.forEach(conn => {
241
+ if (conn.startNode.id() === nodeId) {
242
+ conn.line.points([
243
+ nodeGroup.x(),
244
+ nodeGroup.y(),
245
+ conn.endNode.x(),
246
+ conn.endNode.y()
247
+ ]);
248
+ }
249
+ });
250
+
251
+ // Update connections where this node is the end
252
+ connections.forEach(conn => {
253
+ if (conn.endNode.id() === nodeId) {
254
+ conn.line.points([
255
+ conn.startNode.x(),
256
+ conn.startNode.y(),
257
+ nodeGroup.x(),
258
+ nodeGroup.y()
259
+ ]);
260
+ }
261
+ });
262
+
263
+ layer.draw();
264
+ }
265
+
266
+ // Start drawing a connection
267
+ function startConnection(nodeGroup) {
268
+ if (drawingConnection) return;
269
+
270
+ drawingConnection = true;
271
+ startNode = nodeGroup;
272
+
273
+ tempLine = new Konva.Line({
274
+ points: [nodeGroup.x(), nodeGroup.y(), nodeGroup.x(), nodeGroup.y()],
275
+ stroke: 'gray',
276
+ strokeWidth: 2,
277
+ lineCap: 'round',
278
+ lineJoin: 'round',
279
+ dash: [10, 5]
280
+ });
281
+
282
+ layer.add(tempLine);
283
+ layer.draw();
284
+ }
285
+
286
+ // Update temporary connection line while drawing
287
+ function updateTempConnection(pos) {
288
+ if (!drawingConnection || !tempLine) return;
289
+
290
+ tempLine.points([
291
+ startNode.x(),
292
+ startNode.y(),
293
+ pos.x,
294
+ pos.y
295
+ ]);
296
+
297
+ layer.draw();
298
+ }
299
+
300
+ // Complete the connection
301
+ function completeConnection(endNode) {
302
+ if (!drawingConnection || !startNode || startNode.id() === endNode.id()) {
303
+ cancelConnection();
304
+ return;
305
+ }
306
+
307
+ // Check if connection already exists
308
+ const existingConnection = connections.find(conn =>
309
+ (conn.startNode.id() === startNode.id() && conn.endNode.id() === endNode.id()) ||
310
+ (conn.startNode.id() === endNode.id() && conn.endNode.id() === startNode.id())
311
+ );
312
+
313
+ if (existingConnection) {
314
+ cancelConnection();
315
+ return;
316
+ }
317
+
318
+ // Create the connection line
319
+ const line = new Konva.Line({
320
+ points: [
321
+ startNode.x(),
322
+ startNode.y(),
323
+ endNode.x(),
324
+ endNode.y()
325
+ ],
326
+ stroke: 'gray',
327
+ strokeWidth: 2,
328
+ lineCap: 'round',
329
+ lineJoin: 'round'
330
+ });
331
+
332
+ layer.add(line);
333
+
334
+ // Store the connection
335
+ connections.push({
336
+ line: line,
337
+ startNode: startNode,
338
+ endNode: endNode
339
+ });
340
+
341
+ // Add to nodes' connection lists
342
+ const startNodeData = nodes.find(n => n.id === startNode.id());
343
+ const endNodeData = nodes.find(n => n.id === endNode.id());
344
+
345
+ if (startNodeData && endNodeData) {
346
+ startNodeData.connections.push(endNode.id());
347
+ endNodeData.connections.push(startNode.id());
348
+ }
349
+
350
+ // Add click event to connection
351
+ line.on('click tap', function() {
352
+ document.getElementById('connectionInfo').innerHTML = `
353
+ <div class="mb-2"><strong>Connection:</strong> ${startNodeData.group.children[1].text()} ↔ ${endNodeData.group.children[1].text()}</div>
354
+ <div><strong>Length:</strong> ${Math.sqrt(
355
+ Math.pow(endNode.x() - startNode.x(), 2) +
356
+ Math.pow(endNode.y() - startNode.y(), 2)
357
+ ).toFixed(1)}px</div>
358
+ `;
359
+ });
360
+
361
+ updateNodeCounter();
362
+ cancelConnection();
363
+ layer.draw();
364
+ }
365
+
366
+ // Cancel the current connection drawing
367
+ function cancelConnection() {
368
+ drawingConnection = false;
369
+ startNode = null;
370
+
371
+ if (tempLine) {
372
+ tempLine.destroy();
373
+ tempLine = null;
374
+ }
375
+
376
+ layer.draw();
377
+ }
378
+
379
+ // Update node properties panel
380
+ function updateNodePropertiesPanel() {
381
+ if (!selectedNode) return;
382
+
383
+ const node = selectedNode.group;
384
+ const nodeData = nodes.find(n => n.id === node.id());
385
+
386
+ document.getElementById('nodeProps').innerHTML = `
387
+ <div class="mb-2"><strong>Node ID:</strong> ${node.children[1].text()}</div>
388
+ <div class="mb-2"><strong>Position:</strong> (${node.x().toFixed(0)}, ${node.y().toFixed(0)})</div>
389
+ <div class="mb-3"><strong>Connections:</strong> ${nodeData.connections.length}</div>
390
+
391
+ <div class="flex space-x-2 mb-3">
392
+ <button onclick="setAsStartNode('${node.id()}')" class="bg-green-500 hover:bg-green-600 text-white px-2 py-1 rounded text-sm ${nodeData.isStart ? 'opacity-50 cursor-not-allowed' : ''}">
393
+ Set as Start
394
+ </button>
395
+ <button onclick="setAsEndNode('${node.id()}')" class="bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded text-sm ${nodeData.isEnd ? 'opacity-50 cursor-not-allowed' : ''}">
396
+ Set as End
397
+ </button>
398
+ </div>
399
+
400
+ <button onclick="deleteNode('${node.id()}')" class="bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded text-sm w-full">
401
+ Delete Node
402
+ </button>
403
+ `;
404
+ }
405
+
406
+ // Set node as start node
407
+ window.setAsStartNode = function(nodeId) {
408
+ // Reset previous start node
409
+ nodes.forEach(n => {
410
+ if (n.isStart) {
411
+ n.isStart = false;
412
+ n.group.children[0].fill('#3B82F6');
413
+ }
414
+ });
415
+
416
+ // Set new start node
417
+ const node = nodes.find(n => n.id === nodeId);
418
+ if (node) {
419
+ node.isStart = true;
420
+ node.group.children[0].fill('#10B981');
421
+
422
+ if (selectedNode && selectedNode.id === nodeId) {
423
+ updateNodePropertiesPanel();
424
+ }
425
+
426
+ layer.draw();
427
+ }
428
+ };
429
+
430
+ // Set node as end node
431
+ window.setAsEndNode = function(nodeId) {
432
+ // Reset previous end node
433
+ nodes.forEach(n => {
434
+ if (n.isEnd) {
435
+ n.isEnd = false;
436
+ n.group.children[0].fill('#3B82F6');
437
+ }
438
+ });
439
+
440
+ // Set new end node
441
+ const node = nodes.find(n => n.id === nodeId);
442
+ if (node) {
443
+ node.isEnd = true;
444
+ node.group.children[0].fill('#EF4444');
445
+
446
+ if (selectedNode && selectedNode.id === nodeId) {
447
+ updateNodePropertiesPanel();
448
+ }
449
+
450
+ layer.draw();
451
+ }
452
+ };
453
+
454
+ // Delete a node
455
+ window.deleteNode = function(nodeId) {
456
+ const nodeIndex = nodes.findIndex(n => n.id === nodeId);
457
+ if (nodeIndex === -1) return;
458
+
459
+ const node = nodes[nodeIndex];
460
+
461
+ // Remove all connections to this node
462
+ const connectionsToRemove = connections.filter(conn =>
463
+ conn.startNode.id() === nodeId || conn.endNode.id() === nodeId
464
+ );
465
+
466
+ connectionsToRemove.forEach(conn => {
467
+ // Remove from the other node's connections list
468
+ const otherNodeId = conn.startNode.id() === nodeId ? conn.endNode.id() : conn.startNode.id();
469
+ const otherNode = nodes.find(n => n.id === otherNodeId);
470
+
471
+ if (otherNode) {
472
+ otherNode.connections = otherNode.connections.filter(id => id !== nodeId);
473
+ }
474
+
475
+ // Remove the connection line
476
+ conn.line.destroy();
477
+
478
+ // Remove from connections array
479
+ connections = connections.filter(c => c !== conn);
480
+ });
481
+
482
+ // Remove the node
483
+ node.group.destroy();
484
+ nodes.splice(nodeIndex, 1);
485
+
486
+ // Reset selection if needed
487
+ if (selectedNode && selectedNode.id === nodeId) {
488
+ selectedNode = null;
489
+ document.getElementById('nodeProps').textContent = 'Select a node to edit properties';
490
+ }
491
+
492
+ updateNodeCounter();
493
+ layer.draw();
494
+ };
495
+
496
+ // Clear all nodes and connections
497
+ document.getElementById('clearBtn').addEventListener('click', function() {
498
+ if (confirm('Are you sure you want to clear all nodes and connections?')) {
499
+ // Remove all connections
500
+ connections.forEach(conn => conn.line.destroy());
501
+ connections = [];
502
+
503
+ // Remove all nodes
504
+ nodes.forEach(node => node.group.destroy());
505
+ nodes = [];
506
+
507
+ // Reset state
508
+ selectedNode = null;
509
+ drawingConnection = false;
510
+ startNode = null;
511
+
512
+ if (tempLine) {
513
+ tempLine.destroy();
514
+ tempLine = null;
515
+ }
516
+
517
+ document.getElementById('nodeProps').textContent = 'Select a node to edit properties';
518
+ document.getElementById('connectionInfo').textContent = 'Click on a connection for details';
519
+ updateNodeCounter();
520
+ layer.draw();
521
+ }
522
+ });
523
+
524
+ // Add new node on button click
525
+ document.getElementById('addNodeBtn').addEventListener('click', function() {
526
+ const x = Math.random() * (width - 100) + 50;
527
+ const y = Math.random() * (height - 100) + 50;
528
+ createNode(x, y);
529
+ });
530
+
531
+ // Export to JSON
532
+ document.getElementById('exportBtn').addEventListener('click', function() {
533
+ const data = {
534
+ nodes: nodes.map(node => ({
535
+ id: node.id,
536
+ x: node.group.x(),
537
+ y: node.group.y(),
538
+ text: node.group.children[1].text(),
539
+ isStart: node.isStart,
540
+ isEnd: node.isEnd,
541
+ connections: node.connections
542
+ })),
543
+ connections: connections.map(conn => ({
544
+ startNodeId: conn.startNode.id(),
545
+ endNodeId: conn.endNode.id()
546
+ }))
547
+ };
548
+
549
+ const dataStr = JSON.stringify(data, null, 2);
550
+ const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
551
+
552
+ const exportFileDefaultName = 'nodevis-export.json';
553
+
554
+ const linkElement = document.createElement('a');
555
+ linkElement.setAttribute('href', dataUri);
556
+ linkElement.setAttribute('download', exportFileDefaultName);
557
+ linkElement.click();
558
+ });
559
+
560
+ // Import from JSON
561
+ document.getElementById('importBtn').addEventListener('click', function() {
562
+ document.getElementById('fileInput').click();
563
+ });
564
+
565
+ document.getElementById('fileInput').addEventListener('change', function(e) {
566
+ const file = e.target.files[0];
567
+ if (!file) return;
568
+
569
+ const reader = new FileReader();
570
+ reader.onload = function(e) {
571
+ try {
572
+ const data = JSON.parse(e.target.result);
573
+
574
+ // Clear existing nodes and connections
575
+ document.getElementById('clearBtn').click();
576
+
577
+ // Create nodes
578
+ data.nodes.forEach(nodeData => {
579
+ const nodeGroup = new Konva.Group({
580
+ x: nodeData.x,
581
+ y: nodeData.y,
582
+ id: nodeData.id,
583
+ draggable: true
584
+ });
585
+
586
+ const circle = new Konva.Circle({
587
+ radius: 30,
588
+ fill: nodeData.isStart ? '#10B981' :
589
+ nodeData.isEnd ? '#EF4444' : '#3B82F6',
590
+ stroke: '#1D4ED8',
591
+ strokeWidth: 2
592
+ });
593
+
594
+ const text = new Konva.Text({
595
+ text: nodeData.text,
596
+ fontSize: 18,
597
+ fontFamily: 'Arial',
598
+ fill: 'white',
599
+ align: 'center',
600
+ verticalAlign: 'middle',
601
+ width: circle.radius() * 2,
602
+ height: circle.radius() * 2,
603
+ offsetX: circle.radius(),
604
+ offsetY: circle.radius() / 1.5
605
+ });
606
+
607
+ nodeGroup.add(circle);
608
+ nodeGroup.add(text);
609
+ layer.add(nodeGroup);
610
+
611
+ nodes.push({
612
+ id: nodeData.id,
613
+ group: nodeGroup,
614
+ connections: nodeData.connections,
615
+ isStart: nodeData.isStart,
616
+ isEnd: nodeData.isEnd
617
+ });
618
+
619
+ // Add event listeners
620
+ nodeGroup.on('dragstart', function() {
621
+ this.moveToTop();
622
+ layer.draw();
623
+ });
624
+
625
+ nodeGroup.on('dragmove', function() {
626
+ updateConnectionsForNode(this);
627
+ });
628
+
629
+ nodeGroup.on('click tap', function(e) {
630
+ e.cancelBubble = true;
631
+
632
+ if (selectedNode) {
633
+ selectedNode.group.children[0].stroke('#1D4ED8');
634
+ selectedNode.group.children[0].strokeWidth(2);
635
+ }
636
+
637
+ selectedNode = nodes.find(n => n.id === this.id());
638
+ this.children[0].stroke('#F59E0B');
639
+ this.children[0].strokeWidth(3);
640
+ updateNodePropertiesPanel();
641
+ layer.draw();
642
+ });
643
+ });
644
+
645
+ // Create connections
646
+ data.connections.forEach(connData => {
647
+ const startNode = nodes.find(n => n.id === connData.startNodeId).group;
648
+ const endNode = nodes.find(n => n.id === connData.endNodeId).group;
649
+
650
+ const line = new Konva.Line({
651
+ points: [
652
+ startNode.x(),
653
+ startNode.y(),
654
+ endNode.x(),
655
+ endNode.y()
656
+ ],
657
+ stroke: 'gray',
658
+ strokeWidth: 2,
659
+ lineCap: 'round',
660
+ lineJoin: 'round'
661
+ });
662
+
663
+ layer.add(line);
664
+ connections.push({
665
+ line: line,
666
+ startNode: startNode,
667
+ endNode: endNode
668
+ });
669
+
670
+ // Add click event to connection
671
+ line.on('click tap', function() {
672
+ const startNodeData = nodes.find(n => n.id === startNode.id());
673
+ const endNodeData = nodes.find(n => n.id === endNode.id());
674
+
675
+ document.getElementById('connectionInfo').innerHTML = `
676
+ <div class="mb-2"><strong>Connection:</strong> ${startNodeData.group.children[1].text()} ↔ ${endNodeData.group.children[1].text()}</div>
677
+ <div><strong>Length:</strong> ${Math.sqrt(
678
+ Math.pow(endNode.x() - startNode.x(), 2) +
679
+ Math.pow(endNode.y() - startNode.y(), 2)
680
+ ).toFixed(1)}px</div>
681
+ `;
682
+ });
683
+ });
684
+
685
+ nodeCounter = nodes.length;
686
+ updateNodeCounter();
687
+ layer.draw();
688
+
689
+ } catch (error) {
690
+ alert('Error importing file: ' + error.message);
691
+ }
692
+ };
693
+ reader.readAsText(file);
694
+ });
695
+
696
+ // Stage event listeners
697
+ stage.on('click tap', function(e) {
698
+ // Clicked on empty space - deselect node
699
+ if (e.target === stage) {
700
+ if (selectedNode) {
701
+ selectedNode.group.children[0].stroke('#1D4ED8');
702
+ selectedNode.group.children[0].strokeWidth(2);
703
+ selectedNode = null;
704
+ document.getElementById('nodeProps').textContent = 'Select a node to edit properties';
705
+ layer.draw();
706
+ }
707
+
708
+ if (drawingConnection) {
709
+ cancelConnection();
710
+ }
711
+ }
712
+ });
713
+
714
+ stage.on('mousemove', function(e) {
715
+ if (drawingConnection) {
716
+ updateTempConnection(stage.getPointerPosition());
717
+ }
718
+ });
719
+
720
+ // Make stage responsive
721
+ function resizeStage() {
722
+ const container = document.getElementById('container');
723
+ const newWidth = container.offsetWidth;
724
+
725
+ stage.width(newWidth);
726
+ stage.height(height);
727
+ layer.draw();
728
+ }
729
+
730
+ window.addEventListener('resize', resizeStage);
731
+
732
+ // Initial setup
733
+ updateNodeCounter();
734
+
735
+ // Add initial nodes for demo
736
+ const centerX = width / 2;
737
+ const centerY = height / 2;
738
+
739
+ const node1 = createNode(centerX - 100, centerY - 100);
740
+ const node2 = createNode(centerX + 100, centerY - 100);
741
+ const node3 = createNode(centerX, centerY + 100);
742
+
743
+ // Set node1 as start node
744
+ setAsStartNode(node1.id());
745
+
746
+ // Create some initial connections
747
+ setTimeout(() => {
748
+ startConnection(node1);
749
+ completeConnection(node2);
750
+
751
+ startConnection(node2);
752
+ completeConnection(node3);
753
+
754
+ startConnection(node3);
755
+ completeConnection(node1);
756
+
757
+ // Select the first node
758
+ node1.fire('click');
759
+ }, 100);
760
+ });
761
+ </script>
762
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=senangh/v1-nodevis" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
763
+ </html>