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