dragon-graph-lib 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,933 @@
1
+ const DragonGraphLib = (() => {
2
+
3
+ function to_slug(string)
4
+ {
5
+ return string.replace(/[^a-z0-9_-]+/ig, "-").toLowerCase();
6
+ }
7
+
8
+ class Socket
9
+ {
10
+ constructor(name, multi_input=false)
11
+ {
12
+ this.name = name;
13
+ this.multi_input = multi_input;
14
+ this.accepts_incoming = new Set();
15
+ }
16
+
17
+ accept(type)
18
+ {
19
+ let type_name = type instanceof Socket ? type.name : type;
20
+ this.accepts_incoming.add(type_name);
21
+ }
22
+
23
+ to_dom()
24
+ {
25
+ let elem = document.createElement("div");
26
+ elem.classList.add("dgl-socket", "socket-" + to_slug(this.name));
27
+ return elem;
28
+ }
29
+
30
+ output_can_connect(input_socket)
31
+ {
32
+ return input_socket.input_can_connect(this);
33
+ }
34
+
35
+ input_can_connect(output_socket)
36
+ {
37
+ return output_socket.name == this.name || this.accepts_incoming.has(output_socket.name);
38
+ }
39
+
40
+ static can_connect(output_socket, input_socket)
41
+ {
42
+ return output_socket.output_can_connect(input_socket);
43
+ }
44
+ }
45
+
46
+
47
+ class VisualElement
48
+ {
49
+ constructor()
50
+ {
51
+ this.dom = null;
52
+ }
53
+
54
+ to_dom()
55
+ {
56
+ this.dom = this.build_dom();
57
+ this.update_dom();
58
+ return this.dom;
59
+ }
60
+
61
+ build_dom()
62
+ {
63
+ return document.createElement("div");
64
+ }
65
+
66
+ update_dom()
67
+ {
68
+ }
69
+
70
+ destroy()
71
+ {
72
+ if ( this.dom && this.dom.parentNode )
73
+ {
74
+ this.dom.parentNode.removeChild(this.dom);
75
+ this.dom = null;
76
+ }
77
+ }
78
+ }
79
+
80
+ class Connection extends VisualElement
81
+ {
82
+ margin = 10;
83
+
84
+ constructor(output, input)
85
+ {
86
+ super();
87
+ this.output = output;
88
+ this.input = input;
89
+ }
90
+
91
+ connect()
92
+ {
93
+ this.output.connect(this);
94
+ this.input.connect(this);
95
+ }
96
+
97
+ disconnect()
98
+ {
99
+ this.destroy();
100
+ this.output.disconnect(this);
101
+ this.input.disconnect(this);
102
+ }
103
+
104
+ static path_d(x1, y1, x2, y2)
105
+ {
106
+ let w = x2 - x1;
107
+ let h = y2 - y1;
108
+
109
+ return `M ${x1} ${y1}
110
+ C ${x1 + w / 3} ${y1}
111
+ ${x2 - w / 3} ${y2}
112
+ ${x2} ${y2}
113
+ `;
114
+ }
115
+ }
116
+
117
+ class NodeIO extends VisualElement
118
+ {
119
+ constructor(node, name, label, socket)
120
+ {
121
+ super();
122
+ this.node = node;
123
+ this.name = name;
124
+ this.label = label;
125
+ this.socket = socket;
126
+ this.connections = [];
127
+ this.dom_items = {};
128
+ }
129
+
130
+ update_dom()
131
+ {
132
+ this.dom_items.label.textContent = this.label;
133
+ }
134
+
135
+ connect(connection)
136
+ {
137
+ this.connections.push(connection);
138
+ }
139
+
140
+ disconnect(connection)
141
+ {
142
+ let index = this.connections.indexOf(connection);
143
+ if ( index != -1 )
144
+ this.connections.splice(index, 1);
145
+ }
146
+ }
147
+
148
+
149
+ class NodeInput extends NodeIO
150
+ {
151
+ constructor(node, name, label, socket, control)
152
+ {
153
+ super(node, name, label, socket);
154
+ this.control = control;
155
+ }
156
+
157
+ build_dom()
158
+ {
159
+ let container = document.createElement("div");
160
+ container.classList.add("dgl-input");
161
+ if ( this.socket )
162
+ {
163
+ this.dom_items.socket = container.appendChild(this.socket.to_dom());
164
+ this.dom_items.socket.classList.add("dgl-input-socket");
165
+ container.classList.add("dgl-socketed");
166
+ }
167
+
168
+ this.dom_items.label = document.createElement("span");
169
+ this.dom_items.label.textContent = this.name;
170
+ this.dom_items.label.classList.add("dgl-label", "dgl-input-label");
171
+ container.appendChild(this.dom_items.label);
172
+
173
+ if ( this.control )
174
+ container.appendChild(this.control);
175
+
176
+ return container;
177
+ }
178
+
179
+ connect(connection)
180
+ {
181
+ if ( !this.socket.multi_input )
182
+ {
183
+ for ( let conn of this.connections )
184
+ conn.disconnect();
185
+ this.connections = [];
186
+ }
187
+
188
+ this.connections.push(connection);
189
+
190
+ if ( this.control )
191
+ this.control.style.visibility = "hidden";
192
+ }
193
+
194
+ set_value(value)
195
+ {
196
+ if ( this.control )
197
+ {
198
+ this.control.value = value;
199
+ }
200
+ }
201
+
202
+ get_value()
203
+ {
204
+ if ( this.connections.length )
205
+ {
206
+ if ( this.socket.multi_input )
207
+ return this.connections.map(c => c.output.get_value());
208
+ return this.connections[0].output.get_value();
209
+ }
210
+
211
+ if ( !this.control )
212
+ {
213
+ if ( this.socket && this.socket.multi_input )
214
+ return [];
215
+ return null;
216
+ }
217
+
218
+ let value = this.control.value
219
+ if ( this.socket && this.socket.multi_input )
220
+ return [value];
221
+ return value;
222
+ }
223
+
224
+ disconnect(connection)
225
+ {
226
+ super.disconnect(connection);
227
+ if ( this.control && this.connections.length == 0 )
228
+ this.control.style.visibility = "visible";
229
+ }
230
+ }
231
+
232
+ class NodeOutput extends NodeIO
233
+ {
234
+ constructor(node, name, label, socket)
235
+ {
236
+ super(node, name, label, socket);
237
+ this.value = undefined;
238
+ }
239
+
240
+ build_dom()
241
+ {
242
+ let container = document.createElement("div");
243
+ container.classList.add("dgl-output");
244
+ this.dom_items.label = document.createElement("span");
245
+ this.dom_items.label.textContent = this.name;
246
+ container.appendChild(this.dom_items.label);
247
+ this.dom_items.label.classList.add("dgl-label", "dgl-output-label");
248
+ this.dom_items.socket = container.appendChild(this.socket.to_dom());
249
+ this.dom_items.socket.classList.add("dgl-output-socket");
250
+
251
+ return container;
252
+ }
253
+
254
+ clear_value()
255
+ {
256
+ this.value = undefined;
257
+ }
258
+
259
+ set_value(value)
260
+ {
261
+ this.value = value;
262
+ }
263
+
264
+ get_value()
265
+ {
266
+ if ( this.value == undefined )
267
+ this.node.evaluate();
268
+ return this.value;
269
+ }
270
+ }
271
+
272
+
273
+ function force_default(ev)
274
+ {
275
+ ev.stopPropagation();
276
+ }
277
+
278
+ function NodeControl(type, attrs={})
279
+ {
280
+ let elem = document.createElement("input");
281
+ elem.type = type;
282
+ elem.classList.add("dgl-control");
283
+ for ( let [k, v] of Object.entries(attrs) )
284
+ elem.setAttribute(k, v);
285
+ elem.addEventListener("mousedown", force_default);
286
+ elem.addEventListener("mouseup", force_default);
287
+ elem.addEventListener("mousemove", force_default);
288
+ elem.addEventListener("wheel", force_default);
289
+ return elem;
290
+ }
291
+
292
+ class Node extends VisualElement
293
+ {
294
+ constructor(title, type)
295
+ {
296
+ super();
297
+ this.title = title;
298
+ this.outputs = {};
299
+ this.inputs = {};
300
+ this.preview = null;
301
+ this.type = type;
302
+ this.x = 0;
303
+ this.y = 0;
304
+ }
305
+
306
+ set_input_value(data)
307
+ {
308
+ for ( let [k, v] of Object.entries(this.inputs) )
309
+ {
310
+ if ( k in data )
311
+ v.set_value(data[k]);
312
+ }
313
+ }
314
+
315
+ get_input_value()
316
+ {
317
+ let data = {};
318
+ for ( let [k, v] of Object.entries(this.inputs) )
319
+ {
320
+ data[k] = v.get_value();
321
+ }
322
+ return data;
323
+ }
324
+
325
+ connect(output_name, node, input_name)
326
+ {
327
+ let output = this.outputs[output_name];
328
+ let input = node.inputs[input_name];
329
+ if ( !output || !input )
330
+ return null;
331
+
332
+ if ( !Socket.can_connect(output.socket, input.socket) )
333
+ return null;
334
+
335
+ let connection = new Connection(output, input);
336
+ connection.connect();
337
+ return connection;
338
+ }
339
+
340
+ add_input(name, title, socket, control)
341
+ {
342
+ this.inputs[name] = new NodeInput(this, name, title, socket, control);
343
+ }
344
+
345
+ add_output(name, title, socket)
346
+ {
347
+ this.outputs[name] = new NodeOutput(this, name, title, socket);
348
+ }
349
+
350
+ update_dom()
351
+ {
352
+ this.dom.style.transform = `translate(${this.x}px, ${this.y}px`;
353
+ }
354
+
355
+ build_dom()
356
+ {
357
+ let dom = document.createElement("div");
358
+ dom.classList.add("dgl-node", "node-" + to_slug(this.title));
359
+
360
+ let title = dom.appendChild(document.createElement("div"));
361
+ title.textContent = this.title;
362
+ title.classList.add("dgl-title");
363
+
364
+ for ( let output of Object.values(this.outputs) )
365
+ dom.appendChild(output.to_dom());
366
+
367
+ this.preview = dom.appendChild(document.createElement("div"));
368
+ this.preview.classList.add("dgl-preview");
369
+
370
+ for ( let input of Object.values(this.inputs) )
371
+ dom.appendChild(input.to_dom());
372
+
373
+ return dom;
374
+ }
375
+
376
+ evaluate()
377
+ {
378
+ if ( this.type )
379
+ this.type.evaluate_node(this);
380
+ }
381
+
382
+ all_connections()
383
+ {
384
+ return Object.values(this.inputs).concat(Object.values(this.outputs))
385
+ .map(c => c.connections)
386
+ .reduce((a, b) => a.concat(b), [])
387
+ ;
388
+ }
389
+ }
390
+
391
+ class NodeType
392
+ {
393
+ constructor(name)
394
+ {
395
+ this.name = name;
396
+ }
397
+
398
+ create_node()
399
+ {
400
+ let node = new Node(this.name, this);
401
+ this.populate_node(node);
402
+ return node;
403
+ }
404
+
405
+ populate_node(node)
406
+ {
407
+ }
408
+
409
+ update_node(node)
410
+ {
411
+
412
+ }
413
+
414
+ evaluate_node(node)
415
+ {
416
+
417
+ }
418
+ }
419
+
420
+ class ContextMenu extends VisualElement
421
+ {
422
+ constructor(actions)
423
+ {
424
+ super();
425
+ this.actions = actions;
426
+ }
427
+
428
+ to_dom(editor)
429
+ {
430
+ this.dom = this.make_menu("dgl-menu", this.actions, editor);
431
+ this.dom.style.display = "none";
432
+ return this.dom;
433
+ }
434
+
435
+ make_action(title, action, editor)
436
+ {
437
+ let onclick = action;
438
+ if ( action instanceof NodeType )
439
+ onclick = () => editor.create_node(action);
440
+
441
+ let item = document.createElement("li");
442
+ item.classList.add("dgl-menu-item");
443
+ item.addEventListener("mousedown", (ev) => {
444
+ onclick();
445
+ ev.stopPropagation();
446
+ ev.preventDefault();
447
+ this.dom.style.display = "none";
448
+ });
449
+ let label = item.appendChild(document.createElement("span"));
450
+ label.textContent = title;
451
+ return item;
452
+ }
453
+
454
+ make_menu(cls, actions, editor)
455
+ {
456
+ let dom = document.createElement("ul");
457
+ dom.classList.add(cls);
458
+
459
+ if ( Array.isArray(actions) )
460
+ {
461
+ for ( let action of actions )
462
+ {
463
+ dom.appendChild(this.make_action(action.name, action, editor));
464
+ }
465
+ }
466
+ else
467
+ {
468
+ for ( let [name, val] of Object.entries(actions) )
469
+ {
470
+ if ( Array.isArray(val) || (typeof val == "object" && !(val instanceof NodeType)) )
471
+ dom.appendChild(this.make_submenu(name, val, editor));
472
+ else
473
+ dom.appendChild(this.make_action(name, val));
474
+ }
475
+ }
476
+ return dom;
477
+ }
478
+
479
+ make_submenu(name, actions, editor)
480
+ {
481
+ let item = document.createElement("li");
482
+ item.classList.add("dgl-menu-item");
483
+ let label = item.appendChild(document.createElement("span"));
484
+ label.textContent = name;
485
+ label.classList.add("dgl-submenu-label");
486
+ item.appendChild(this.make_menu("dgl-submenu", actions, editor));
487
+ return item;
488
+ }
489
+
490
+ show(x, y)
491
+ {
492
+ this.dom.style.display = "";
493
+ this.dom.style.left = x + "px";
494
+ this.dom.style.top = y + "px";
495
+ }
496
+
497
+ close()
498
+ {
499
+ this.dom.style.display = "none";
500
+ }
501
+ }
502
+
503
+ class Editor
504
+ {
505
+ constructor(container, context_menu={})
506
+ {
507
+ this.nodes = [];
508
+
509
+ this.container = container;
510
+
511
+ this.area = container.appendChild(document.createElement("div"));
512
+ container.classList.add("dgl-editor");
513
+ this.area.classList.add("dgl-area");
514
+
515
+ this.connection_parent = document.createElementNS("http://www.w3.org/2000/svg", "svg");
516
+ this.area.appendChild(this.connection_parent);
517
+ this.connection_parent.classList.add("dgl-connection");
518
+ this.dragging_connection = document.createElementNS("http://www.w3.org/2000/svg", "path");
519
+ this.connection_parent.appendChild(this.dragging_connection);
520
+
521
+ this.selected_node = null;
522
+ this.scale = 1;
523
+ this.offset_x = 0;
524
+ this.offset_y = 0;
525
+ this.drag_start_x = 0;
526
+ this.drag_start_y = 0;
527
+ this.drag = null;
528
+ this.drag_connector = null;
529
+ this.cursor_x = 0;
530
+ this.cursor_y = 0;
531
+
532
+ this.container.addEventListener("mousedown", this._on_area_mouse_down.bind(this));
533
+ this.container.addEventListener("mousemove", this._on_area_mouse_move.bind(this));
534
+ this.container.addEventListener("mouseup", this._on_area_mouse_up.bind(this));
535
+ this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
536
+ this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
537
+
538
+ this.context_menu = new ContextMenu(context_menu);
539
+ this.node_menu = new ContextMenu({
540
+ "Delete": () => {
541
+ if ( this.selected_node )
542
+ this.remove_node(this.selected_node);
543
+ },
544
+ "Duplicate": () => {
545
+ if ( this.selected_node ) {
546
+ let node = this.create_node(this.selected_node.type, this.selected_node.get_input_value());
547
+ node.x = this.selected_node.x;
548
+ node.y = this.selected_node.y;
549
+ this._select_node(node);
550
+
551
+ }
552
+ }
553
+ });
554
+
555
+ this.container.appendChild(this.node_menu.to_dom(this));
556
+ this.container.appendChild(this.context_menu.to_dom(this));
557
+ }
558
+
559
+ create_node(type, data={})
560
+ {
561
+ let node = type.create_node();
562
+ node.set_input_value(data);
563
+ node.x = this.cursor_x;
564
+ node.y = this.cursor_y;
565
+ this.nodes.push(node);
566
+ let node_dom = node.to_dom();
567
+ this.area.appendChild(node_dom);
568
+
569
+ node_dom.addEventListener("mousedown", ev => this._on_node_mouse_down(ev, node));
570
+ node_dom.addEventListener("contextmenu", ev => this._on_node_menu(ev, node));
571
+
572
+ return node;
573
+ }
574
+
575
+ auto_layout()
576
+ {
577
+ setTimeout(() => this._on_auto_layout());
578
+ }
579
+
580
+ _auto_layout_traverse(node, depth)
581
+ {
582
+ if ( node._depth >= depth )
583
+ return depth;
584
+
585
+ if ( node._depth < depth )
586
+ node._depth = depth;
587
+
588
+ let max = depth;
589
+
590
+ for ( let inp of Object.values(node.inputs) )
591
+ {
592
+ for ( let conn of inp.connections )
593
+ {
594
+ let cd = this._auto_layout_traverse(conn.output.node, depth + 1);
595
+ if ( cd > max )
596
+ max = cd;
597
+ }
598
+ }
599
+
600
+ return max;
601
+ }
602
+
603
+ _on_auto_layout()
604
+ {
605
+ for ( let node of this.nodes )
606
+ {
607
+ node._depth = -1;
608
+ }
609
+ let max_depth = 0;
610
+
611
+ for ( let node of this.nodes )
612
+ {
613
+ let out_conns = 0;
614
+ for ( let outp of Object.values(node.outputs) )
615
+ {
616
+ out_conns += outp.connections.length;
617
+ }
618
+ if ( out_conns == 0 )
619
+ {
620
+ let depth = this._auto_layout_traverse(node, 0);
621
+ if ( depth > max_depth )
622
+ max_depth = depth;
623
+ }
624
+ }
625
+
626
+ let layers = new Array(max_depth + 1).fill(0).map(() => []);
627
+ for ( let node of this.nodes )
628
+ {
629
+ layers[max_depth - node._depth].push(node);
630
+ }
631
+
632
+ let xgap = 48;
633
+ let ygap = 12;
634
+ let x = 0;
635
+ let layer_heights = [];
636
+ let max_height = 0;
637
+ for ( let layer of layers )
638
+ {
639
+ let width = 0;
640
+ let y = 0;
641
+ for ( let node of layer )
642
+ {
643
+ node.x = x;
644
+ node.y = y;
645
+ let rect = node.dom.getBoundingClientRect();
646
+ if ( rect.width > width )
647
+ width = rect.width;
648
+ y += rect.height + ygap;
649
+ }
650
+
651
+ x += width + xgap;
652
+ let height = y - ygap;
653
+ layer_heights.push(height);
654
+ if ( height > max_height )
655
+ max_height = height;
656
+ }
657
+
658
+ // Center vertically
659
+ for ( let i = 0; i < layers.length; i++ )
660
+ {
661
+ let offset = (max_height - layer_heights[i]) / 2;
662
+ for ( let node of layers[i] )
663
+ node.y += offset;
664
+ }
665
+
666
+ this._refresh_graph();
667
+ }
668
+
669
+ connect(output_node, output_name, input_node, input_name)
670
+ {
671
+ let conn = output_node.connect(output_name, input_node, input_name);
672
+ if ( conn )
673
+ {
674
+ conn.dom = document.createElementNS("http://www.w3.org/2000/svg", "path");
675
+ conn.dom.classList.add("connection-from-" + to_slug(output_node.outputs[output_name].socket.name));
676
+ conn.dom.classList.add("connection-to-" + to_slug(input_node.inputs[input_name].socket.name));
677
+ this.connection_parent.appendChild(conn.dom);
678
+ this._update_connection(conn);
679
+ this._refresh_graph();
680
+ }
681
+ return conn;
682
+ }
683
+
684
+ disconnect(connection)
685
+ {
686
+ connection.disconnect();
687
+ this._refresh_graph();
688
+ }
689
+
690
+ _update_connection(conn)
691
+ {
692
+ let [x1, y1] = this._connection_point(conn.output);
693
+ let [x2, y2] = this._connection_point(conn.input);
694
+ conn.dom.setAttribute("d", Connection.path_d(x1, y1, x2, y2));
695
+ }
696
+
697
+ _close_menus()
698
+ {
699
+ this.context_menu.close();
700
+ this.node_menu.close();
701
+ }
702
+
703
+ _on_area_mouse_down(ev)
704
+ {
705
+ this._close_menus();
706
+ if ( ev.button == 0 )
707
+ {
708
+ this._select_node(null);
709
+ }
710
+ else if ( ev.button == 1 )
711
+ {
712
+ let rect = this.container.getBoundingClientRect();
713
+ this.drag_start_x = ev.clientX - rect.left;
714
+ this.drag_start_y = ev.clientY - rect.top;
715
+ this.drag_start_ox = this.offset_x;
716
+ this.drag_start_oy = this.offset_y;
717
+ this.drag = "pan";
718
+ }
719
+
720
+ ev.stopPropagation();
721
+ ev.preventDefault();
722
+ }
723
+
724
+ _connection_point(connector)
725
+ {
726
+
727
+ let rect = connector.dom_items.socket.getBoundingClientRect();
728
+ let px = rect.x + rect.width / 2;
729
+ let py = rect.y + rect.height / 2;
730
+ let crect = this.container.getBoundingClientRect();
731
+ return [
732
+ (px - crect.left - this.offset_x) / this.scale,
733
+ (py - crect.top - this.offset_y) / this.scale
734
+ ];
735
+ }
736
+
737
+ _on_area_mouse_move(ev)
738
+ {
739
+ let rect = this.container.getBoundingClientRect();
740
+ let drag_x = ev.clientX - rect.left;
741
+ let drag_y = ev.clientY - rect.top;
742
+ this.cursor_x = (drag_x - this.offset_x) / this.scale;
743
+ this.cursor_y = (drag_y - this.offset_y) / this.scale;
744
+
745
+ if ( this.drag )
746
+ {
747
+ let delta_x = drag_x - this.drag_start_x;
748
+ let delta_y = drag_y - this.drag_start_y;
749
+
750
+ if ( this.drag == "pan" )
751
+ {
752
+ this.offset_x = this.drag_start_ox + delta_x;
753
+ this.offset_y = this.drag_start_oy + delta_y;
754
+ }
755
+ else if ( this.drag == "node" )
756
+ {
757
+ let x = this.drag_start_ox + delta_x / this.scale;
758
+ let y = this.drag_start_oy + delta_y / this.scale;
759
+ this.selected_node.x = x;
760
+ this.selected_node.y = y;
761
+ this.selected_node.dom.style.transform = `translate(${x}px, ${y}px`;
762
+ for ( let connection of this.selected_node.all_connections() )
763
+ this._update_connection(connection);
764
+ }
765
+ else if ( this.drag == "socket" )
766
+ {
767
+ let x1 = (drag_x - this.offset_x) / this.scale;
768
+ let y1 = (drag_y - this.offset_y) / this.scale;
769
+ let [x2, y2] = this._connection_point(this.drag_connector);
770
+
771
+ this.dragging_connection.setAttribute("d", Connection.path_d(x1, y1, x2, y2));
772
+ }
773
+
774
+ this._apply_transform();
775
+ }
776
+
777
+ ev.stopPropagation();
778
+ ev.preventDefault();
779
+ }
780
+
781
+ _apply_transform()
782
+ {
783
+ this.area.style.transform = `translate(${this.offset_x}px, ${this.offset_y}px) scale(${this.scale})`;
784
+ }
785
+
786
+ _refresh_graph()
787
+ {
788
+ for ( let node of this.nodes )
789
+ {
790
+ node.evaluate();
791
+ node.update_dom();
792
+ }
793
+
794
+ window.setTimeout(() => {
795
+ // TODO could optimize this, currently each connection is updated twice
796
+ for ( let node of this.nodes )
797
+ {
798
+ for ( let connection of node.all_connections() )
799
+ this._update_connection(connection);
800
+ }
801
+ });
802
+ }
803
+
804
+ _get_dest_connector(ev)
805
+ {
806
+ let elem = document.elementFromPoint(ev.clientX, ev.clientY);
807
+ if ( !elem.classList.contains("dgl-socket") )
808
+ return null;
809
+
810
+ let conns = "inputs";
811
+ if ( this.drag_connector instanceof NodeInput )
812
+ conns = "outputs";
813
+
814
+ for ( let node of this.nodes )
815
+ {
816
+ for ( let conn of Object.values(node[conns]) )
817
+ if ( conn.dom_items.socket === elem )
818
+ return conn;
819
+ }
820
+ return null;
821
+ }
822
+
823
+ _on_area_mouse_up(ev)
824
+ {
825
+ if ( this.drag == "socket" )
826
+ {
827
+ let dest = this._get_dest_connector(ev);
828
+ if ( dest && dest.node !== this.selected_node )
829
+ {
830
+ if ( this.drag_connector instanceof NodeInput )
831
+ this.connect(dest.node, dest.name, this.selected_node, this.drag_connector.name);
832
+ else
833
+ this.connect(this.selected_node, this.drag_connector.name, dest.node, dest.name);
834
+ }
835
+ this.drag_connector = null;
836
+ this.dragging_connection.setAttribute("d", "");
837
+ }
838
+ this.drag = null;
839
+ ev.stopPropagation();
840
+ ev.preventDefault();
841
+ }
842
+
843
+ _on_area_wheel(ev)
844
+ {
845
+ ev.preventDefault();
846
+ ev.stopPropagation();
847
+
848
+ let rect = this.container.getBoundingClientRect();
849
+ let mouse_x = ev.clientX - rect.left;
850
+ let mouse_y = ev.clientY - rect.top;
851
+
852
+ let unscaled_x = (mouse_x - this.offset_x) / this.scale;
853
+ let unscaled_y = (mouse_y - this.offset_y) / this.scale;
854
+
855
+ let delta = ev.deltaY > 0 ? 0.9 : 1.1;
856
+ this.scale *= delta;
857
+
858
+ this.offset_x = mouse_x - unscaled_x * this.scale;
859
+ this.offset_y = mouse_y - unscaled_y * this.scale;
860
+ this._apply_transform();
861
+ }
862
+
863
+ _select_node(node)
864
+ {
865
+ this.area.querySelectorAll(".dgl-node").forEach(e => e.classList.remove("dgl-selected"));
866
+ if ( node )
867
+ node.dom.classList.add("dgl-selected");
868
+ this.selected_node = node;
869
+ }
870
+
871
+ _on_node_mouse_down(ev, node)
872
+ {
873
+ this._close_menus();
874
+ if ( ev.button == 0 )
875
+ {
876
+ ev.stopPropagation();
877
+ this._select_node(node);
878
+ this.drag = "node";
879
+ let rect = this.container.getBoundingClientRect();
880
+ this.drag_start_x = ev.clientX - rect.left;
881
+ this.drag_start_y = ev.clientY - rect.top;
882
+ this.drag_start_ox = node.x;
883
+ this.drag_start_oy = node.y;
884
+ if ( ev.target.classList.contains("dgl-socket") )
885
+ {
886
+ this.drag = "socket";
887
+ let is_input = ev.target.classList.contains("dgl-input-socket");
888
+ let connectors = is_input ? node.inputs : node.outputs;
889
+ for ( let connector of Object.values(connectors) )
890
+ {
891
+ if ( connector.dom_items.socket === ev.target)
892
+ {
893
+ this.drag_connector = connector;
894
+ if ( is_input )
895
+ {
896
+ for ( let conn of connector.connections.slice() )
897
+ conn.disconnect(conn);
898
+ this._refresh_graph();
899
+ }
900
+ break;
901
+ }
902
+ }
903
+ }
904
+ }
905
+ }
906
+
907
+ _on_node_menu(ev, node)
908
+ {
909
+ this._select_node(node);
910
+ this.node_menu.show(ev.clientX, ev.clientY);
911
+ ev.stopPropagation();
912
+ ev.preventDefault();
913
+ }
914
+
915
+ _on_area_menu(ev)
916
+ {
917
+ this.context_menu.show(ev.clientX, ev.clientY);
918
+ ev.stopPropagation();
919
+ ev.preventDefault();
920
+ }
921
+ }
922
+
923
+ return {
924
+ Socket,
925
+ Connection,
926
+ NodeInput,
927
+ NodeOutput,
928
+ NodeControl,
929
+ Node,
930
+ NodeType,
931
+ ContextMenu,
932
+ Editor};
933
+ })();