dragon-graph-lib 0.1.0 → 0.1.2
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.
- package/Readme.md +43 -1
- package/package.json +21 -4
- package/src/dragon-graph-lib-global.js +437 -30
- package/src/dragon-graph-lib.css +12 -1
- package/src/dragon-graph-lib.js +437 -30
|
@@ -5,15 +5,33 @@ function to_slug(string)
|
|
|
5
5
|
return string.replace(/[^a-z0-9_-]+/ig, "-").toLowerCase();
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Connection socket
|
|
10
|
+
*
|
|
11
|
+
* This is what allows nodes to be connected between each other,
|
|
12
|
+
* it provides simple type checking but it can be customized for more complex cases.
|
|
13
|
+
*/
|
|
8
14
|
class Socket
|
|
9
15
|
{
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} name Socket type name
|
|
18
|
+
* @param {boolean} multi_input (For input sockets) whether it should accept multiple incoming connections
|
|
19
|
+
*/
|
|
10
20
|
constructor(name, multi_input=false)
|
|
11
21
|
{
|
|
22
|
+
/**
|
|
23
|
+
* Socket type name
|
|
24
|
+
* @member {string}
|
|
25
|
+
*/
|
|
12
26
|
this.name = name;
|
|
13
27
|
this.multi_input = multi_input;
|
|
14
28
|
this.accepts_incoming = new Set();
|
|
15
29
|
}
|
|
16
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Allows incoming connections from the given type
|
|
33
|
+
* @param {Socket|string} type Type name or Socket instance
|
|
34
|
+
*/
|
|
17
35
|
accept(type)
|
|
18
36
|
{
|
|
19
37
|
let type_name = type instanceof Socket ? type.name : type;
|
|
@@ -27,16 +45,41 @@ class Socket
|
|
|
27
45
|
return elem;
|
|
28
46
|
}
|
|
29
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether an output sockets allows a connection to the given input socket
|
|
50
|
+
*
|
|
51
|
+
* The default implementation defers to the input socket
|
|
52
|
+
*
|
|
53
|
+
* @param {Socket} input_socket
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
30
56
|
output_can_connect(input_socket)
|
|
31
57
|
{
|
|
32
58
|
return input_socket.input_can_connect(this);
|
|
33
59
|
}
|
|
34
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Checks whether an input sockets allows a connection from the given output socket
|
|
63
|
+
*
|
|
64
|
+
* The default implementation checks types based on name (same or added with accept())
|
|
65
|
+
*
|
|
66
|
+
* @param {Socket} output_socket
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
35
69
|
input_can_connect(output_socket)
|
|
36
70
|
{
|
|
37
71
|
return output_socket.name == this.name || this.accepts_incoming.has(output_socket.name);
|
|
38
72
|
}
|
|
39
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether two sockets allow connections between them
|
|
76
|
+
*
|
|
77
|
+
* Equivalent to output_socket.output_can_connect(input_socket)
|
|
78
|
+
*
|
|
79
|
+
* @param {Socket} output_socket
|
|
80
|
+
* @param {Socket} input_socket
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
40
83
|
static can_connect(output_socket, input_socket)
|
|
41
84
|
{
|
|
42
85
|
return output_socket.output_can_connect(input_socket);
|
|
@@ -77,14 +120,27 @@ class VisualElement
|
|
|
77
120
|
}
|
|
78
121
|
}
|
|
79
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Connection between two sockets
|
|
125
|
+
*/
|
|
80
126
|
class Connection extends VisualElement
|
|
81
127
|
{
|
|
82
|
-
|
|
83
|
-
|
|
128
|
+
/**
|
|
129
|
+
* @param {NodeOutput} output Output connector
|
|
130
|
+
* @param {NodeInput} input Input connector
|
|
131
|
+
*/
|
|
84
132
|
constructor(output, input)
|
|
85
133
|
{
|
|
86
134
|
super();
|
|
135
|
+
/**
|
|
136
|
+
* Output connector
|
|
137
|
+
* @member {NodeOutput}
|
|
138
|
+
*/
|
|
87
139
|
this.output = output;
|
|
140
|
+
/**
|
|
141
|
+
* Input connector
|
|
142
|
+
* @member {NodeInput}
|
|
143
|
+
*/
|
|
88
144
|
this.input = input;
|
|
89
145
|
}
|
|
90
146
|
|
|
@@ -104,7 +160,6 @@ class Connection extends VisualElement
|
|
|
104
160
|
static path_d(x1, y1, x2, y2)
|
|
105
161
|
{
|
|
106
162
|
let w = x2 - x1;
|
|
107
|
-
let h = y2 - y1;
|
|
108
163
|
|
|
109
164
|
return `M ${x1} ${y1}
|
|
110
165
|
C ${x1 + w / 3} ${y1}
|
|
@@ -114,15 +169,38 @@ class Connection extends VisualElement
|
|
|
114
169
|
}
|
|
115
170
|
}
|
|
116
171
|
|
|
117
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Base connector class
|
|
174
|
+
*/
|
|
175
|
+
class Connector extends VisualElement
|
|
118
176
|
{
|
|
119
177
|
constructor(node, name, label, socket)
|
|
120
178
|
{
|
|
121
179
|
super();
|
|
180
|
+
/**
|
|
181
|
+
* Node this connector is part of
|
|
182
|
+
* @member {Node}
|
|
183
|
+
*/
|
|
122
184
|
this.node = node;
|
|
185
|
+
/**
|
|
186
|
+
* Name of the connector within the node
|
|
187
|
+
* @member {string}
|
|
188
|
+
*/
|
|
123
189
|
this.name = name;
|
|
190
|
+
/**
|
|
191
|
+
* User-visible label / title
|
|
192
|
+
* @member {NodeOutput}
|
|
193
|
+
*/
|
|
124
194
|
this.label = label;
|
|
195
|
+
/**
|
|
196
|
+
* Associated socket
|
|
197
|
+
* @member {Socket?}
|
|
198
|
+
*/
|
|
125
199
|
this.socket = socket;
|
|
200
|
+
/**
|
|
201
|
+
* Active connections
|
|
202
|
+
* @member {Connection[]}
|
|
203
|
+
*/
|
|
126
204
|
this.connections = [];
|
|
127
205
|
this.dom_items = {};
|
|
128
206
|
}
|
|
@@ -132,11 +210,19 @@ class NodeIO extends VisualElement
|
|
|
132
210
|
this.dom_items.label.textContent = this.label;
|
|
133
211
|
}
|
|
134
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Adds a connection to the connector
|
|
215
|
+
* @param {Connection} connection
|
|
216
|
+
*/
|
|
135
217
|
connect(connection)
|
|
136
218
|
{
|
|
137
219
|
this.connections.push(connection);
|
|
138
220
|
}
|
|
139
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Removes a connection from the connector
|
|
224
|
+
* @param {Connection} connection
|
|
225
|
+
*/
|
|
140
226
|
disconnect(connection)
|
|
141
227
|
{
|
|
142
228
|
let index = this.connections.indexOf(connection);
|
|
@@ -145,12 +231,26 @@ class NodeIO extends VisualElement
|
|
|
145
231
|
}
|
|
146
232
|
}
|
|
147
233
|
|
|
148
|
-
|
|
149
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Input connector
|
|
236
|
+
* @extends Connector
|
|
237
|
+
*/
|
|
238
|
+
class NodeInput extends Connector
|
|
150
239
|
{
|
|
240
|
+
/**
|
|
241
|
+
* @param {Node} node Node owning this connector
|
|
242
|
+
* @param {string} name Name of the connector within the node
|
|
243
|
+
* @param {string} label User-visible label / title
|
|
244
|
+
* @param {Socket?} socket Optional socket for incoming connectors
|
|
245
|
+
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
246
|
+
*/
|
|
151
247
|
constructor(node, name, label, socket, control)
|
|
152
248
|
{
|
|
153
249
|
super(node, name, label, socket);
|
|
250
|
+
/**
|
|
251
|
+
* Optional control to set the value without a connection
|
|
252
|
+
* @member {HTMLInputElement?}
|
|
253
|
+
*/
|
|
154
254
|
this.control = control;
|
|
155
255
|
}
|
|
156
256
|
|
|
@@ -191,6 +291,10 @@ class NodeInput extends NodeIO
|
|
|
191
291
|
this.control.style.visibility = "hidden";
|
|
192
292
|
}
|
|
193
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Sets the control value
|
|
296
|
+
* @param value
|
|
297
|
+
*/
|
|
194
298
|
set_value(value)
|
|
195
299
|
{
|
|
196
300
|
if ( this.control )
|
|
@@ -199,6 +303,17 @@ class NodeInput extends NodeIO
|
|
|
199
303
|
}
|
|
200
304
|
}
|
|
201
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Gets the input value
|
|
308
|
+
*
|
|
309
|
+
* If there's input connections, the value is based on those.
|
|
310
|
+
* Otherwise the value comes from the control.
|
|
311
|
+
*
|
|
312
|
+
* If there is a socket on this connector with multi_input, the returned value
|
|
313
|
+
* is always an array.
|
|
314
|
+
*
|
|
315
|
+
* @return {any}
|
|
316
|
+
*/
|
|
202
317
|
get_value()
|
|
203
318
|
{
|
|
204
319
|
if ( this.connections.length )
|
|
@@ -229,8 +344,18 @@ class NodeInput extends NodeIO
|
|
|
229
344
|
}
|
|
230
345
|
}
|
|
231
346
|
|
|
232
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Output connector
|
|
349
|
+
* @extends Connector
|
|
350
|
+
*/
|
|
351
|
+
class NodeOutput extends Connector
|
|
233
352
|
{
|
|
353
|
+
/**
|
|
354
|
+
* @param {Node} node Node owning this connector
|
|
355
|
+
* @param {string} name Name of the connector within the node
|
|
356
|
+
* @param {string} label User-visible label / title
|
|
357
|
+
* @param {Socket} socket Socket for outgoing connectors
|
|
358
|
+
*/
|
|
234
359
|
constructor(node, name, label, socket)
|
|
235
360
|
{
|
|
236
361
|
super(node, name, label, socket);
|
|
@@ -251,16 +376,30 @@ class NodeOutput extends NodeIO
|
|
|
251
376
|
return container;
|
|
252
377
|
}
|
|
253
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Clears the stored value.
|
|
381
|
+
*
|
|
382
|
+
* Will force re-evaluation of the node on get_value()
|
|
383
|
+
*/
|
|
254
384
|
clear_value()
|
|
255
385
|
{
|
|
256
386
|
this.value = undefined;
|
|
257
387
|
}
|
|
258
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Sets value for outgoing connections
|
|
391
|
+
* @param value
|
|
392
|
+
*/
|
|
259
393
|
set_value(value)
|
|
260
394
|
{
|
|
261
395
|
this.value = value;
|
|
262
396
|
}
|
|
263
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Gets the output value
|
|
400
|
+
*
|
|
401
|
+
* Used by outgoing connections. Might cause the node to be evaluated
|
|
402
|
+
*/
|
|
264
403
|
get_value()
|
|
265
404
|
{
|
|
266
405
|
if ( this.value == undefined )
|
|
@@ -275,6 +414,12 @@ function force_default(ev)
|
|
|
275
414
|
ev.stopPropagation();
|
|
276
415
|
}
|
|
277
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Creates a node control
|
|
419
|
+
* @param {string} type Input type
|
|
420
|
+
* @param {object} attrs Input attributes as an object
|
|
421
|
+
* @returns {HTMLInputElement}
|
|
422
|
+
*/
|
|
278
423
|
function NodeControl(type, attrs={})
|
|
279
424
|
{
|
|
280
425
|
let elem = document.createElement("input");
|
|
@@ -289,20 +434,51 @@ function NodeControl(type, attrs={})
|
|
|
289
434
|
return elem;
|
|
290
435
|
}
|
|
291
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Main node class
|
|
439
|
+
*/
|
|
292
440
|
class Node extends VisualElement
|
|
293
441
|
{
|
|
442
|
+
/**
|
|
443
|
+
* @param {string} title Node title, shown to the user
|
|
444
|
+
* @param {NodeType?} type Type controlling the node
|
|
445
|
+
*/
|
|
294
446
|
constructor(title, type)
|
|
295
447
|
{
|
|
296
448
|
super();
|
|
297
449
|
this.title = title;
|
|
450
|
+
/**
|
|
451
|
+
* Registered outputs
|
|
452
|
+
* @member {Object.<string, NodeOutput>}
|
|
453
|
+
*/
|
|
298
454
|
this.outputs = {};
|
|
455
|
+
/**
|
|
456
|
+
* Registered inputs
|
|
457
|
+
* @member {Object.<string, NodeInput>}
|
|
458
|
+
*/
|
|
299
459
|
this.inputs = {};
|
|
460
|
+
/**
|
|
461
|
+
* Element that can be used to preview the node effect on the editor
|
|
462
|
+
* @member {HTMLElement}
|
|
463
|
+
*/
|
|
300
464
|
this.preview = null;
|
|
301
465
|
this.type = type;
|
|
466
|
+
/**
|
|
467
|
+
* X position in the editor
|
|
468
|
+
* @member {number}
|
|
469
|
+
*/
|
|
302
470
|
this.x = 0;
|
|
471
|
+
/**
|
|
472
|
+
* Y position in the editor
|
|
473
|
+
* @member {number}
|
|
474
|
+
*/
|
|
303
475
|
this.y = 0;
|
|
304
476
|
}
|
|
305
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Sets control values for inputs
|
|
480
|
+
* @param {object} data Object with input names as keys and associated values
|
|
481
|
+
*/
|
|
306
482
|
set_input_value(data)
|
|
307
483
|
{
|
|
308
484
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
@@ -312,6 +488,10 @@ class Node extends VisualElement
|
|
|
312
488
|
}
|
|
313
489
|
}
|
|
314
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Gets values from the inputs
|
|
493
|
+
* @returns {object} Object with input names as keys and associated values
|
|
494
|
+
*/
|
|
315
495
|
get_input_value()
|
|
316
496
|
{
|
|
317
497
|
let data = {};
|
|
@@ -337,11 +517,30 @@ class Node extends VisualElement
|
|
|
337
517
|
return connection;
|
|
338
518
|
}
|
|
339
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Adds a new input
|
|
522
|
+
*
|
|
523
|
+
* The resulting input will be accessible from this.inputs[name]
|
|
524
|
+
*
|
|
525
|
+
* @param {string} name Name of the connector within the node
|
|
526
|
+
* @param {string} label User-visible label / title
|
|
527
|
+
* @param {Socket?} socket Optional socket for incoming connectors
|
|
528
|
+
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
529
|
+
*/
|
|
340
530
|
add_input(name, title, socket, control)
|
|
341
531
|
{
|
|
342
532
|
this.inputs[name] = new NodeInput(this, name, title, socket, control);
|
|
343
533
|
}
|
|
344
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Adds a new output
|
|
537
|
+
*
|
|
538
|
+
* The resulting input will be accessible from this.outputs[name]
|
|
539
|
+
*
|
|
540
|
+
* @param {string} name Name of the connector within the node
|
|
541
|
+
* @param {string} label User-visible label / title
|
|
542
|
+
* @param {Socket} socket Socket for outgoing connectors
|
|
543
|
+
*/
|
|
345
544
|
add_output(name, title, socket)
|
|
346
545
|
{
|
|
347
546
|
this.outputs[name] = new NodeOutput(this, name, title, socket);
|
|
@@ -349,7 +548,8 @@ class Node extends VisualElement
|
|
|
349
548
|
|
|
350
549
|
update_dom()
|
|
351
550
|
{
|
|
352
|
-
this.dom.style.
|
|
551
|
+
this.dom.style.left = `${this.x}px`;
|
|
552
|
+
this.dom.style.top = `${this.y}px`;
|
|
353
553
|
}
|
|
354
554
|
|
|
355
555
|
build_dom()
|
|
@@ -379,6 +579,34 @@ class Node extends VisualElement
|
|
|
379
579
|
this.type.evaluate_node(this);
|
|
380
580
|
}
|
|
381
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Returns a list of all input connections
|
|
584
|
+
* @returns {Connection[]}
|
|
585
|
+
*/
|
|
586
|
+
input_connections()
|
|
587
|
+
{
|
|
588
|
+
return Object.values(this.inputs)
|
|
589
|
+
.map(c => c.connections)
|
|
590
|
+
.reduce((a, b) => a.concat(b), [])
|
|
591
|
+
;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Returns a list of all output connections
|
|
596
|
+
* @returns {Connection[]}
|
|
597
|
+
*/
|
|
598
|
+
all_connections()
|
|
599
|
+
{
|
|
600
|
+
return Object.values(this.outputs)
|
|
601
|
+
.map(c => c.connections)
|
|
602
|
+
.reduce((a, b) => a.concat(b), [])
|
|
603
|
+
;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Returns a list of all connections (both input and output)
|
|
608
|
+
* @returns {Connection[]}
|
|
609
|
+
*/
|
|
382
610
|
all_connections()
|
|
383
611
|
{
|
|
384
612
|
return Object.values(this.inputs).concat(Object.values(this.outputs))
|
|
@@ -388,13 +616,32 @@ class Node extends VisualElement
|
|
|
388
616
|
}
|
|
389
617
|
}
|
|
390
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Node type
|
|
621
|
+
*
|
|
622
|
+
* This class is responsible for creating nodes, and adding logic for output values
|
|
623
|
+
*/
|
|
391
624
|
class NodeType
|
|
392
625
|
{
|
|
626
|
+
/**
|
|
627
|
+
* @param {string} name Type name
|
|
628
|
+
*/
|
|
393
629
|
constructor(name)
|
|
394
630
|
{
|
|
631
|
+
/**
|
|
632
|
+
* Type name
|
|
633
|
+
* @member {string}
|
|
634
|
+
*/
|
|
395
635
|
this.name = name;
|
|
396
636
|
}
|
|
397
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Creates and sets up a node for this type
|
|
640
|
+
*
|
|
641
|
+
* Custom types should override populate_node() to add connectors or otherwise customize the node
|
|
642
|
+
*
|
|
643
|
+
* @returns Node
|
|
644
|
+
*/
|
|
398
645
|
create_node()
|
|
399
646
|
{
|
|
400
647
|
let node = new Node(this.name, this);
|
|
@@ -402,23 +649,43 @@ class NodeType
|
|
|
402
649
|
return node;
|
|
403
650
|
}
|
|
404
651
|
|
|
405
|
-
|
|
652
|
+
/**
|
|
653
|
+
* Allows custom types to add connectors or otherwise customize the node
|
|
654
|
+
* @protected
|
|
655
|
+
* @param {Node} node
|
|
656
|
+
*/
|
|
657
|
+
populate_node(_node)
|
|
406
658
|
{
|
|
407
659
|
}
|
|
408
660
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Evaluates node logic
|
|
663
|
+
*
|
|
664
|
+
* Generally you should get input values with `node.inputs[name].get_value()`
|
|
665
|
+
* And set output values with `node.outputs[name].set_value()`.
|
|
666
|
+
*
|
|
667
|
+
* You can also alter the `node.preview` to add custom HTML to the node
|
|
668
|
+
* based on these values
|
|
669
|
+
*
|
|
670
|
+
* @param {Node} node
|
|
671
|
+
*/
|
|
672
|
+
evaluate_node(_node)
|
|
415
673
|
{
|
|
416
674
|
|
|
417
675
|
}
|
|
418
676
|
}
|
|
419
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Context menu for the node editor
|
|
680
|
+
*/
|
|
420
681
|
class ContextMenu extends VisualElement
|
|
421
682
|
{
|
|
683
|
+
/**
|
|
684
|
+
* @param {object|NodeType[]} actions If an object,
|
|
685
|
+
* keys will be items in the menu while values are the performed actions.
|
|
686
|
+
* Possible values are callbacks, NodeType instances (which will create new nodes),
|
|
687
|
+
* Or other actions definitions for submenus
|
|
688
|
+
*/
|
|
422
689
|
constructor(actions)
|
|
423
690
|
{
|
|
424
691
|
super();
|
|
@@ -487,6 +754,11 @@ class ContextMenu extends VisualElement
|
|
|
487
754
|
return item;
|
|
488
755
|
}
|
|
489
756
|
|
|
757
|
+
/**
|
|
758
|
+
* Opens the menu at the given point
|
|
759
|
+
* @param {number} x
|
|
760
|
+
* @param {number} y
|
|
761
|
+
*/
|
|
490
762
|
show(x, y)
|
|
491
763
|
{
|
|
492
764
|
this.dom.style.display = "";
|
|
@@ -494,19 +766,32 @@ class ContextMenu extends VisualElement
|
|
|
494
766
|
this.dom.style.top = y + "px";
|
|
495
767
|
}
|
|
496
768
|
|
|
769
|
+
/**
|
|
770
|
+
* Closes the menu
|
|
771
|
+
*/
|
|
497
772
|
close()
|
|
498
773
|
{
|
|
499
774
|
this.dom.style.display = "none";
|
|
500
775
|
}
|
|
501
776
|
}
|
|
502
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Node Editor
|
|
780
|
+
*/
|
|
503
781
|
class Editor
|
|
504
782
|
{
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* @param {HTMLElement} container Parent element in the DOM
|
|
786
|
+
* @param {object|NodeType[]} Actions for the context menu (See ContextMenu)
|
|
787
|
+
*/
|
|
505
788
|
constructor(container, context_menu={})
|
|
506
789
|
{
|
|
507
790
|
this.nodes = [];
|
|
508
791
|
|
|
509
792
|
this.container = container;
|
|
793
|
+
if ( !container.hasAttribute("tabindex") )
|
|
794
|
+
container.setAttribute("tabindex", "0");
|
|
510
795
|
|
|
511
796
|
this.area = container.appendChild(document.createElement("div"));
|
|
512
797
|
container.classList.add("dgl-editor");
|
|
@@ -532,6 +817,12 @@ class Editor
|
|
|
532
817
|
this.container.addEventListener("mousedown", this._on_area_mouse_down.bind(this));
|
|
533
818
|
this.container.addEventListener("mousemove", this._on_area_mouse_move.bind(this));
|
|
534
819
|
this.container.addEventListener("mouseup", this._on_area_mouse_up.bind(this));
|
|
820
|
+
|
|
821
|
+
this.container.addEventListener("touchstart", this._on_area_mouse_down.bind(this));
|
|
822
|
+
this.container.addEventListener("touchmove", this._on_area_mouse_move.bind(this));
|
|
823
|
+
this.container.addEventListener("touchend", this._on_area_mouse_up.bind(this));
|
|
824
|
+
this.container.addEventListener("touchcancel", this._on_area_mouse_up.bind(this));
|
|
825
|
+
|
|
535
826
|
this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
|
|
536
827
|
this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
|
|
537
828
|
|
|
@@ -544,8 +835,6 @@ class Editor
|
|
|
544
835
|
"Duplicate": () => {
|
|
545
836
|
if ( this.selected_node ) {
|
|
546
837
|
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
838
|
this._select_node(node);
|
|
550
839
|
|
|
551
840
|
}
|
|
@@ -556,10 +845,13 @@ class Editor
|
|
|
556
845
|
this.container.appendChild(this.context_menu.to_dom(this));
|
|
557
846
|
}
|
|
558
847
|
|
|
559
|
-
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Adds a new node to the graph
|
|
851
|
+
* @param {Node} node
|
|
852
|
+
*/
|
|
853
|
+
add_node(node)
|
|
560
854
|
{
|
|
561
|
-
let node = type.create_node();
|
|
562
|
-
node.set_input_value(data);
|
|
563
855
|
node.x = this.cursor_x;
|
|
564
856
|
node.y = this.cursor_y;
|
|
565
857
|
this.nodes.push(node);
|
|
@@ -567,11 +859,53 @@ class Editor
|
|
|
567
859
|
this.area.appendChild(node_dom);
|
|
568
860
|
|
|
569
861
|
node_dom.addEventListener("mousedown", ev => this._on_node_mouse_down(ev, node));
|
|
862
|
+
node_dom.addEventListener("touchstart", ev => this._on_node_mouse_down(ev, node));
|
|
570
863
|
node_dom.addEventListener("contextmenu", ev => this._on_node_menu(ev, node));
|
|
864
|
+
for ( let input of Object.values(node.inputs) )
|
|
865
|
+
{
|
|
866
|
+
if ( input.control )
|
|
867
|
+
{
|
|
868
|
+
input.control.addEventListener("change", () => this._refresh_graph());
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
571
872
|
|
|
873
|
+
/**
|
|
874
|
+
* Removes a node from the graph
|
|
875
|
+
* @param {Node} node
|
|
876
|
+
*/
|
|
877
|
+
remove_node(node)
|
|
878
|
+
{
|
|
879
|
+
for ( let conn of node.all_connections() )
|
|
880
|
+
conn.disconnect();
|
|
881
|
+
|
|
882
|
+
let ind = this.nodes.indexOf(node);
|
|
883
|
+
if ( ind != -1 )
|
|
884
|
+
this.nodes.splice(ind, 1);
|
|
885
|
+
node.destroy();
|
|
886
|
+
|
|
887
|
+
this._refresh_graph();
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Creates a new node from the given type
|
|
892
|
+
* @param {NodeType} type
|
|
893
|
+
* @param {object} data Optional data to populate node inputs
|
|
894
|
+
* @returns {Node} The created node
|
|
895
|
+
*/
|
|
896
|
+
create_node(type, data={})
|
|
897
|
+
{
|
|
898
|
+
let node = type.create_node();
|
|
899
|
+
node.set_input_value(data);
|
|
900
|
+
this.add_node(node);
|
|
572
901
|
return node;
|
|
573
902
|
}
|
|
574
903
|
|
|
904
|
+
/**
|
|
905
|
+
* Automatically lays out the graph
|
|
906
|
+
*
|
|
907
|
+
* It assumes all nodes and connections are set up
|
|
908
|
+
*/
|
|
575
909
|
auto_layout()
|
|
576
910
|
{
|
|
577
911
|
setTimeout(() => this._on_auto_layout());
|
|
@@ -664,8 +998,60 @@ class Editor
|
|
|
664
998
|
}
|
|
665
999
|
|
|
666
1000
|
this._refresh_graph();
|
|
1001
|
+
this.fit_view();
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Pan and zoom to fit all the nodes
|
|
1006
|
+
* @param {number} margin Margin to leave around the area
|
|
1007
|
+
*/
|
|
1008
|
+
fit_view(margin=20)
|
|
1009
|
+
{
|
|
1010
|
+
if ( this.nodes.length == 0 )
|
|
1011
|
+
return;
|
|
1012
|
+
|
|
1013
|
+
let left = Infinity;
|
|
1014
|
+
let right = -Infinity;
|
|
1015
|
+
let top = Infinity;
|
|
1016
|
+
let bottom = -Infinity;
|
|
1017
|
+
|
|
1018
|
+
for ( let node of this.nodes )
|
|
1019
|
+
{
|
|
1020
|
+
let rect = node.dom.getBoundingClientRect();
|
|
1021
|
+
if ( rect.left < left ) left = rect.left;
|
|
1022
|
+
if ( rect.right > right ) right = rect.right;
|
|
1023
|
+
if ( rect.top < top ) top = rect.top;
|
|
1024
|
+
if ( rect.bottom > bottom ) bottom = rect.bottom;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
let crect = this.container.getBoundingClientRect();
|
|
1028
|
+
|
|
1029
|
+
left += -margin - crect.left;
|
|
1030
|
+
right += margin - crect.left;
|
|
1031
|
+
top += -margin - crect.top;
|
|
1032
|
+
bottom += margin - crect.top;
|
|
1033
|
+
|
|
1034
|
+
let width = right - left;
|
|
1035
|
+
let height = bottom - top;
|
|
1036
|
+
let x_scale = crect.width / width;
|
|
1037
|
+
let y_scale = crect.height / height;
|
|
1038
|
+
let scale = Math.min(x_scale, y_scale);
|
|
1039
|
+
this.scale = scale;
|
|
1040
|
+
|
|
1041
|
+
this.offset_x = (crect.width - width * scale) / 2 - left + margin;
|
|
1042
|
+
this.offset_y = (crect.height - height * scale) / 2 - top + margin;
|
|
1043
|
+
this._apply_transform();
|
|
1044
|
+
|
|
667
1045
|
}
|
|
668
1046
|
|
|
1047
|
+
/**
|
|
1048
|
+
* Adds a connection to the graph
|
|
1049
|
+
* @param {Node} output_node Node the connection is coming from
|
|
1050
|
+
* @param {string} output_name Name of the output connector within output_node
|
|
1051
|
+
* @param {Node} input_node Node the connection is going to
|
|
1052
|
+
* @param {string} input_name Name of the input connector within input_node
|
|
1053
|
+
* @return {Connection?} Connection (Or null if the connection is impossible)
|
|
1054
|
+
*/
|
|
669
1055
|
connect(output_node, output_name, input_node, input_name)
|
|
670
1056
|
{
|
|
671
1057
|
let conn = output_node.connect(output_name, input_node, input_name);
|
|
@@ -681,6 +1067,10 @@ class Editor
|
|
|
681
1067
|
return conn;
|
|
682
1068
|
}
|
|
683
1069
|
|
|
1070
|
+
/**
|
|
1071
|
+
* Removes a connection from the graph
|
|
1072
|
+
* @param {Connection} connection
|
|
1073
|
+
*/
|
|
684
1074
|
disconnect(connection)
|
|
685
1075
|
{
|
|
686
1076
|
connection.disconnect();
|
|
@@ -700,18 +1090,27 @@ class Editor
|
|
|
700
1090
|
this.node_menu.close();
|
|
701
1091
|
}
|
|
702
1092
|
|
|
1093
|
+
_touch_mouse_pos(ev)
|
|
1094
|
+
{
|
|
1095
|
+
if ( ev.changedTouches )
|
|
1096
|
+
return ev.changedTouches[0];
|
|
1097
|
+
return ev;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
703
1100
|
_on_area_mouse_down(ev)
|
|
704
1101
|
{
|
|
1102
|
+
this.container.focus();
|
|
705
1103
|
this._close_menus();
|
|
706
1104
|
if ( ev.button == 0 )
|
|
707
1105
|
{
|
|
708
1106
|
this._select_node(null);
|
|
709
1107
|
}
|
|
710
|
-
else if ( ev.button == 1 )
|
|
1108
|
+
else if ( ev.button == 1 || ev.changedTouches )
|
|
711
1109
|
{
|
|
712
1110
|
let rect = this.container.getBoundingClientRect();
|
|
713
|
-
|
|
714
|
-
this.
|
|
1111
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1112
|
+
this.drag_start_x = evp.clientX - rect.left;
|
|
1113
|
+
this.drag_start_y = evp.clientY - rect.top;
|
|
715
1114
|
this.drag_start_ox = this.offset_x;
|
|
716
1115
|
this.drag_start_oy = this.offset_y;
|
|
717
1116
|
this.drag = "pan";
|
|
@@ -737,8 +1136,9 @@ class Editor
|
|
|
737
1136
|
_on_area_mouse_move(ev)
|
|
738
1137
|
{
|
|
739
1138
|
let rect = this.container.getBoundingClientRect();
|
|
740
|
-
let
|
|
741
|
-
let
|
|
1139
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1140
|
+
let drag_x = evp.clientX - rect.left;
|
|
1141
|
+
let drag_y = evp.clientY - rect.top;
|
|
742
1142
|
this.cursor_x = (drag_x - this.offset_x) / this.scale;
|
|
743
1143
|
this.cursor_y = (drag_y - this.offset_y) / this.scale;
|
|
744
1144
|
|
|
@@ -758,7 +1158,8 @@ class Editor
|
|
|
758
1158
|
let y = this.drag_start_oy + delta_y / this.scale;
|
|
759
1159
|
this.selected_node.x = x;
|
|
760
1160
|
this.selected_node.y = y;
|
|
761
|
-
this.selected_node.dom.style.
|
|
1161
|
+
this.selected_node.dom.style.left = `${x}px`;
|
|
1162
|
+
this.selected_node.dom.style.top = `${y}px`;
|
|
762
1163
|
for ( let connection of this.selected_node.all_connections() )
|
|
763
1164
|
this._update_connection(connection);
|
|
764
1165
|
}
|
|
@@ -803,7 +1204,8 @@ class Editor
|
|
|
803
1204
|
|
|
804
1205
|
_get_dest_connector(ev)
|
|
805
1206
|
{
|
|
806
|
-
let
|
|
1207
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1208
|
+
let elem = document.elementFromPoint(evp.clientX, evp.clientY);
|
|
807
1209
|
if ( !elem.classList.contains("dgl-socket") )
|
|
808
1210
|
return null;
|
|
809
1211
|
|
|
@@ -845,6 +1247,9 @@ class Editor
|
|
|
845
1247
|
ev.preventDefault();
|
|
846
1248
|
ev.stopPropagation();
|
|
847
1249
|
|
|
1250
|
+
if ( !this.container.contains(document.activeElement) )
|
|
1251
|
+
return;
|
|
1252
|
+
|
|
848
1253
|
let rect = this.container.getBoundingClientRect();
|
|
849
1254
|
let mouse_x = ev.clientX - rect.left;
|
|
850
1255
|
let mouse_y = ev.clientY - rect.top;
|
|
@@ -870,15 +1275,17 @@ class Editor
|
|
|
870
1275
|
|
|
871
1276
|
_on_node_mouse_down(ev, node)
|
|
872
1277
|
{
|
|
1278
|
+
// this.container.focus();
|
|
873
1279
|
this._close_menus();
|
|
874
|
-
if ( ev.button == 0 )
|
|
1280
|
+
if ( ev.button == 0 || ev.changedTouches )
|
|
875
1281
|
{
|
|
876
1282
|
ev.stopPropagation();
|
|
877
1283
|
this._select_node(node);
|
|
878
1284
|
this.drag = "node";
|
|
879
1285
|
let rect = this.container.getBoundingClientRect();
|
|
880
|
-
|
|
881
|
-
this.
|
|
1286
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1287
|
+
this.drag_start_x = evp.clientX - rect.left;
|
|
1288
|
+
this.drag_start_y = evp.clientY - rect.top;
|
|
882
1289
|
this.drag_start_ox = node.x;
|
|
883
1290
|
this.drag_start_oy = node.y;
|
|
884
1291
|
if ( ev.target.classList.contains("dgl-socket") )
|