dragon-graph-lib 0.1.1 → 0.1.3
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 +659 -59
- package/src/dragon-graph-lib.css +18 -2
- package/src/dragon-graph-lib.js +657 -59
|
@@ -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,29 +169,55 @@ 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
|
}
|
|
129
207
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Adds a connection to the connector
|
|
210
|
+
* @param {Connection} connection
|
|
211
|
+
*/
|
|
135
212
|
connect(connection)
|
|
136
213
|
{
|
|
137
214
|
this.connections.push(connection);
|
|
138
215
|
}
|
|
139
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Removes a connection from the connector
|
|
219
|
+
* @param {Connection} connection
|
|
220
|
+
*/
|
|
140
221
|
disconnect(connection)
|
|
141
222
|
{
|
|
142
223
|
let index = this.connections.indexOf(connection);
|
|
@@ -145,12 +226,26 @@ class NodeIO extends VisualElement
|
|
|
145
226
|
}
|
|
146
227
|
}
|
|
147
228
|
|
|
148
|
-
|
|
149
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Input connector
|
|
231
|
+
* @extends Connector
|
|
232
|
+
*/
|
|
233
|
+
class NodeInput extends Connector
|
|
150
234
|
{
|
|
235
|
+
/**
|
|
236
|
+
* @param {Node} node Node owning this connector
|
|
237
|
+
* @param {string} name Name of the connector within the node
|
|
238
|
+
* @param {string} label User-visible label / title
|
|
239
|
+
* @param {Socket?} socket Optional socket for incoming connectors
|
|
240
|
+
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
241
|
+
*/
|
|
151
242
|
constructor(node, name, label, socket, control)
|
|
152
243
|
{
|
|
153
244
|
super(node, name, label, socket);
|
|
245
|
+
/**
|
|
246
|
+
* Optional control to set the value without a connection
|
|
247
|
+
* @member {HTMLInputElement?}
|
|
248
|
+
*/
|
|
154
249
|
this.control = control;
|
|
155
250
|
}
|
|
156
251
|
|
|
@@ -165,13 +260,10 @@ class NodeInput extends NodeIO
|
|
|
165
260
|
container.classList.add("dgl-socketed");
|
|
166
261
|
}
|
|
167
262
|
|
|
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
263
|
if ( this.control )
|
|
174
|
-
container.appendChild(this.control);
|
|
264
|
+
container.appendChild(this.control.to_dom(this.label, this.name));
|
|
265
|
+
else
|
|
266
|
+
container.appendChild(NodeControl.make_label(this.label));
|
|
175
267
|
|
|
176
268
|
return container;
|
|
177
269
|
}
|
|
@@ -188,17 +280,32 @@ class NodeInput extends NodeIO
|
|
|
188
280
|
this.connections.push(connection);
|
|
189
281
|
|
|
190
282
|
if ( this.control )
|
|
191
|
-
this.control.
|
|
283
|
+
this.control.hide();
|
|
192
284
|
}
|
|
193
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Sets the control value
|
|
288
|
+
* @param value
|
|
289
|
+
*/
|
|
194
290
|
set_value(value)
|
|
195
291
|
{
|
|
196
292
|
if ( this.control )
|
|
197
293
|
{
|
|
198
|
-
this.control.value
|
|
294
|
+
this.control.set_value(value);
|
|
199
295
|
}
|
|
200
296
|
}
|
|
201
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Gets the input value
|
|
300
|
+
*
|
|
301
|
+
* If there's input connections, the value is based on those.
|
|
302
|
+
* Otherwise the value comes from the control.
|
|
303
|
+
*
|
|
304
|
+
* If there is a socket on this connector with multi_input, the returned value
|
|
305
|
+
* is always an array.
|
|
306
|
+
*
|
|
307
|
+
* @return {any}
|
|
308
|
+
*/
|
|
202
309
|
get_value()
|
|
203
310
|
{
|
|
204
311
|
if ( this.connections.length )
|
|
@@ -215,7 +322,7 @@ class NodeInput extends NodeIO
|
|
|
215
322
|
return null;
|
|
216
323
|
}
|
|
217
324
|
|
|
218
|
-
let value = this.control.
|
|
325
|
+
let value = this.control.get_value();
|
|
219
326
|
if ( this.socket && this.socket.multi_input )
|
|
220
327
|
return [value];
|
|
221
328
|
return value;
|
|
@@ -225,12 +332,22 @@ class NodeInput extends NodeIO
|
|
|
225
332
|
{
|
|
226
333
|
super.disconnect(connection);
|
|
227
334
|
if ( this.control && this.connections.length == 0 )
|
|
228
|
-
this.control.
|
|
335
|
+
this.control.show();
|
|
229
336
|
}
|
|
230
337
|
}
|
|
231
338
|
|
|
232
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Output connector
|
|
341
|
+
* @extends Connector
|
|
342
|
+
*/
|
|
343
|
+
class NodeOutput extends Connector
|
|
233
344
|
{
|
|
345
|
+
/**
|
|
346
|
+
* @param {Node} node Node owning this connector
|
|
347
|
+
* @param {string} name Name of the connector within the node
|
|
348
|
+
* @param {string} label User-visible label / title
|
|
349
|
+
* @param {Socket} socket Socket for outgoing connectors
|
|
350
|
+
*/
|
|
234
351
|
constructor(node, name, label, socket)
|
|
235
352
|
{
|
|
236
353
|
super(node, name, label, socket);
|
|
@@ -251,19 +368,33 @@ class NodeOutput extends NodeIO
|
|
|
251
368
|
return container;
|
|
252
369
|
}
|
|
253
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Clears the stored value.
|
|
373
|
+
*
|
|
374
|
+
* Will force re-evaluation of the node on get_value()
|
|
375
|
+
*/
|
|
254
376
|
clear_value()
|
|
255
377
|
{
|
|
256
378
|
this.value = undefined;
|
|
257
379
|
}
|
|
258
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Sets value for outgoing connections
|
|
383
|
+
* @param value
|
|
384
|
+
*/
|
|
259
385
|
set_value(value)
|
|
260
386
|
{
|
|
261
387
|
this.value = value;
|
|
262
388
|
}
|
|
263
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Gets the output value
|
|
392
|
+
*
|
|
393
|
+
* Used by outgoing connections. Might cause the node to be evaluated
|
|
394
|
+
*/
|
|
264
395
|
get_value()
|
|
265
396
|
{
|
|
266
|
-
if ( this.value
|
|
397
|
+
if ( this.value === undefined || this.node.dirty )
|
|
267
398
|
this.node.evaluate();
|
|
268
399
|
return this.value;
|
|
269
400
|
}
|
|
@@ -275,34 +406,248 @@ function force_default(ev)
|
|
|
275
406
|
ev.stopPropagation();
|
|
276
407
|
}
|
|
277
408
|
|
|
278
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Custom control interface
|
|
411
|
+
*/
|
|
412
|
+
class NodeControl
|
|
413
|
+
{
|
|
414
|
+
/**
|
|
415
|
+
* @param {object} config Control config
|
|
416
|
+
* @param {any} config.default Control default value
|
|
417
|
+
*/
|
|
418
|
+
constructor(config={})
|
|
419
|
+
{
|
|
420
|
+
this.on_change = null;
|
|
421
|
+
this.config = config;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
to_dom(label, name)
|
|
425
|
+
{
|
|
426
|
+
this.element = document.createElement("div");
|
|
427
|
+
this.element.classList.add("dgl-control-parent");
|
|
428
|
+
if ( label.length )
|
|
429
|
+
this.element.appendChild(NodeControl.make_label(label));
|
|
430
|
+
this.setup_dom(this.element, name);
|
|
431
|
+
if ( "default" in this.config )
|
|
432
|
+
this.set_value(this.config.default);
|
|
433
|
+
return this.element;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Sets up the DOM
|
|
438
|
+
* @param {HTMLElement} parent Element to add additional content to
|
|
439
|
+
* @param {name} name Control name
|
|
440
|
+
*/
|
|
441
|
+
setup_dom(_parent, _name)
|
|
442
|
+
{
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Dispatches the change event to update the graph
|
|
447
|
+
*/
|
|
448
|
+
notify_change()
|
|
449
|
+
{
|
|
450
|
+
this.on_change();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Override to return the control value
|
|
455
|
+
* @returns {any} value
|
|
456
|
+
*/
|
|
457
|
+
get_value()
|
|
458
|
+
{
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Override to return set a value to the control
|
|
463
|
+
* @param {any} value
|
|
464
|
+
*/
|
|
465
|
+
set_value(_value)
|
|
466
|
+
{
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Ensures an element uses the default mouse interactions instead of
|
|
471
|
+
* propagating to the node editor
|
|
472
|
+
*/
|
|
473
|
+
use_default_events(element)
|
|
474
|
+
{
|
|
475
|
+
element.addEventListener("mousedown", force_default);
|
|
476
|
+
element.addEventListener("mouseup", force_default);
|
|
477
|
+
element.addEventListener("mousemove", force_default);
|
|
478
|
+
element.addEventListener("wheel", force_default);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Creates a label element
|
|
483
|
+
* @param {string} label Text to display
|
|
484
|
+
*/
|
|
485
|
+
static make_label(label)
|
|
486
|
+
{
|
|
487
|
+
let elem = document.createElement("span");
|
|
488
|
+
elem.textContent = label;
|
|
489
|
+
elem.classList.add("dgl-label", "dgl-input-label");
|
|
490
|
+
return elem;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
set_visibility(value)
|
|
494
|
+
{
|
|
495
|
+
for ( let ch of this.element.children )
|
|
496
|
+
if ( !ch.classList.contains("dgl-label") )
|
|
497
|
+
ch.style.visibility = value;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Hides the control when an input connection is attached
|
|
502
|
+
*/
|
|
503
|
+
hide()
|
|
504
|
+
{
|
|
505
|
+
this.set_visibility("hidden");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Shows the control when an input connection is detached
|
|
510
|
+
*/
|
|
511
|
+
show()
|
|
512
|
+
{
|
|
513
|
+
this.set_visibility("visible");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Control for simple input elements
|
|
519
|
+
*/
|
|
520
|
+
class InputControl extends NodeControl
|
|
279
521
|
{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
522
|
+
/**
|
|
523
|
+
* @param {object} config Control config
|
|
524
|
+
* @param {string} config.type Input type
|
|
525
|
+
* @param {object} config.attrs Input attributes
|
|
526
|
+
* @param {any} config.default Default value
|
|
527
|
+
*/
|
|
528
|
+
constructor(config={type: "text"})
|
|
529
|
+
{
|
|
530
|
+
super(config);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
setup_dom(parent, name)
|
|
534
|
+
{
|
|
535
|
+
this.input = document.createElement("input");
|
|
536
|
+
parent.appendChild(this.input);
|
|
537
|
+
this.input.type = this.config.type;
|
|
538
|
+
this.input.classList.add("dgl-control");
|
|
539
|
+
for ( let [k, v] of Object.entries(this.config.attrs ?? {}) )
|
|
540
|
+
this.input.setAttribute(k, v);
|
|
541
|
+
this.use_default_events(this.input);
|
|
542
|
+
this.input.name = name;
|
|
543
|
+
this.input.addEventListener("input", () => this.notify_change());
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
get_value()
|
|
547
|
+
{
|
|
548
|
+
if ( this.config.type == "checkbox" )
|
|
549
|
+
return this.input.checked;
|
|
550
|
+
return this.input.value;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
set_value(value)
|
|
554
|
+
{
|
|
555
|
+
if ( this.config.type == "checkbox" )
|
|
556
|
+
this.input.checked = value;
|
|
557
|
+
else
|
|
558
|
+
this.input.value = value;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Drop-down Control
|
|
564
|
+
*/
|
|
565
|
+
class DropDownControl extends NodeControl
|
|
566
|
+
{
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @param {object} config Control config
|
|
570
|
+
* @param {Array.<string, string>} config.options Drop down options ([value, display])
|
|
571
|
+
* @param {any} config.default Default value
|
|
572
|
+
*/
|
|
573
|
+
constructor(config={options: []})
|
|
574
|
+
{
|
|
575
|
+
super(config);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
setup_dom(parent, name)
|
|
579
|
+
{
|
|
580
|
+
this.input = document.createElement("select");
|
|
581
|
+
for ( let [value, display] of this.config.options )
|
|
582
|
+
{
|
|
583
|
+
let option = document.createElement("option");
|
|
584
|
+
this.input.appendChild(option);
|
|
585
|
+
option.value = value;
|
|
586
|
+
option.textContent = display ?? value;
|
|
587
|
+
}
|
|
588
|
+
this.input.name = name;
|
|
589
|
+
parent.appendChild(this.input);
|
|
590
|
+
this.use_default_events(this.input);
|
|
591
|
+
this.input.addEventListener("input", () => this.notify_change());
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
get_value()
|
|
595
|
+
{
|
|
596
|
+
return this.input.value;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
set_value(value)
|
|
600
|
+
{
|
|
601
|
+
this.input.value = value;
|
|
602
|
+
}
|
|
290
603
|
}
|
|
291
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Main node class
|
|
607
|
+
*/
|
|
292
608
|
class Node extends VisualElement
|
|
293
609
|
{
|
|
610
|
+
/**
|
|
611
|
+
* @param {string} title Node title, shown to the user
|
|
612
|
+
* @param {NodeType?} type Type controlling the node
|
|
613
|
+
*/
|
|
294
614
|
constructor(title, type)
|
|
295
615
|
{
|
|
296
616
|
super();
|
|
297
617
|
this.title = title;
|
|
618
|
+
/**
|
|
619
|
+
* Registered outputs
|
|
620
|
+
* @member {Object.<string, NodeOutput>}
|
|
621
|
+
*/
|
|
298
622
|
this.outputs = {};
|
|
623
|
+
/**
|
|
624
|
+
* Registered inputs
|
|
625
|
+
* @member {Object.<string, NodeInput>}
|
|
626
|
+
*/
|
|
299
627
|
this.inputs = {};
|
|
628
|
+
/**
|
|
629
|
+
* Element that can be used to preview the node effect on the editor
|
|
630
|
+
* @member {HTMLElement}
|
|
631
|
+
*/
|
|
300
632
|
this.preview = null;
|
|
301
633
|
this.type = type;
|
|
634
|
+
/**
|
|
635
|
+
* X position in the editor
|
|
636
|
+
* @member {number}
|
|
637
|
+
*/
|
|
302
638
|
this.x = 0;
|
|
639
|
+
/**
|
|
640
|
+
* Y position in the editor
|
|
641
|
+
* @member {number}
|
|
642
|
+
*/
|
|
303
643
|
this.y = 0;
|
|
644
|
+
this.dirty = true;
|
|
304
645
|
}
|
|
305
646
|
|
|
647
|
+
/**
|
|
648
|
+
* Sets control values for inputs
|
|
649
|
+
* @param {object} data Object with input names as keys and associated values
|
|
650
|
+
*/
|
|
306
651
|
set_input_value(data)
|
|
307
652
|
{
|
|
308
653
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
@@ -312,6 +657,10 @@ class Node extends VisualElement
|
|
|
312
657
|
}
|
|
313
658
|
}
|
|
314
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Gets values from the inputs
|
|
662
|
+
* @returns {object} Object with input names as keys and associated values
|
|
663
|
+
*/
|
|
315
664
|
get_input_value()
|
|
316
665
|
{
|
|
317
666
|
let data = {};
|
|
@@ -337,11 +686,30 @@ class Node extends VisualElement
|
|
|
337
686
|
return connection;
|
|
338
687
|
}
|
|
339
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Adds a new input
|
|
691
|
+
*
|
|
692
|
+
* The resulting input will be accessible from this.inputs[name]
|
|
693
|
+
*
|
|
694
|
+
* @param {string} name Name of the connector within the node
|
|
695
|
+
* @param {string} label User-visible label / title
|
|
696
|
+
* @param {Socket?} socket Optional socket for incoming connectors
|
|
697
|
+
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
698
|
+
*/
|
|
340
699
|
add_input(name, title, socket, control)
|
|
341
700
|
{
|
|
342
701
|
this.inputs[name] = new NodeInput(this, name, title, socket, control);
|
|
343
702
|
}
|
|
344
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Adds a new output
|
|
706
|
+
*
|
|
707
|
+
* The resulting input will be accessible from this.outputs[name]
|
|
708
|
+
*
|
|
709
|
+
* @param {string} name Name of the connector within the node
|
|
710
|
+
* @param {string} label User-visible label / title
|
|
711
|
+
* @param {Socket} socket Socket for outgoing connectors
|
|
712
|
+
*/
|
|
345
713
|
add_output(name, title, socket)
|
|
346
714
|
{
|
|
347
715
|
this.outputs[name] = new NodeOutput(this, name, title, socket);
|
|
@@ -349,7 +717,8 @@ class Node extends VisualElement
|
|
|
349
717
|
|
|
350
718
|
update_dom()
|
|
351
719
|
{
|
|
352
|
-
this.dom.style.
|
|
720
|
+
this.dom.style.left = `${this.x}px`;
|
|
721
|
+
this.dom.style.top = `${this.y}px`;
|
|
353
722
|
}
|
|
354
723
|
|
|
355
724
|
build_dom()
|
|
@@ -375,10 +744,39 @@ class Node extends VisualElement
|
|
|
375
744
|
|
|
376
745
|
evaluate()
|
|
377
746
|
{
|
|
747
|
+
this.dirty = false;
|
|
378
748
|
if ( this.type )
|
|
379
749
|
this.type.evaluate_node(this);
|
|
380
750
|
}
|
|
381
751
|
|
|
752
|
+
/**
|
|
753
|
+
* Returns a list of all input connections
|
|
754
|
+
* @returns {Connection[]}
|
|
755
|
+
*/
|
|
756
|
+
input_connections()
|
|
757
|
+
{
|
|
758
|
+
return Object.values(this.inputs)
|
|
759
|
+
.map(c => c.connections)
|
|
760
|
+
.reduce((a, b) => a.concat(b), [])
|
|
761
|
+
;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Returns a list of all output connections
|
|
766
|
+
* @returns {Connection[]}
|
|
767
|
+
*/
|
|
768
|
+
all_connections()
|
|
769
|
+
{
|
|
770
|
+
return Object.values(this.outputs)
|
|
771
|
+
.map(c => c.connections)
|
|
772
|
+
.reduce((a, b) => a.concat(b), [])
|
|
773
|
+
;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Returns a list of all connections (both input and output)
|
|
778
|
+
* @returns {Connection[]}
|
|
779
|
+
*/
|
|
382
780
|
all_connections()
|
|
383
781
|
{
|
|
384
782
|
return Object.values(this.inputs).concat(Object.values(this.outputs))
|
|
@@ -388,13 +786,32 @@ class Node extends VisualElement
|
|
|
388
786
|
}
|
|
389
787
|
}
|
|
390
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Node type
|
|
791
|
+
*
|
|
792
|
+
* This class is responsible for creating nodes, and adding logic for output values
|
|
793
|
+
*/
|
|
391
794
|
class NodeType
|
|
392
795
|
{
|
|
796
|
+
/**
|
|
797
|
+
* @param {string} name Type name
|
|
798
|
+
*/
|
|
393
799
|
constructor(name)
|
|
394
800
|
{
|
|
801
|
+
/**
|
|
802
|
+
* Type name
|
|
803
|
+
* @member {string}
|
|
804
|
+
*/
|
|
395
805
|
this.name = name;
|
|
396
806
|
}
|
|
397
807
|
|
|
808
|
+
/**
|
|
809
|
+
* Creates and sets up a node for this type
|
|
810
|
+
*
|
|
811
|
+
* Custom types should override populate_node() to add connectors or otherwise customize the node
|
|
812
|
+
*
|
|
813
|
+
* @returns Node
|
|
814
|
+
*/
|
|
398
815
|
create_node()
|
|
399
816
|
{
|
|
400
817
|
let node = new Node(this.name, this);
|
|
@@ -402,23 +819,43 @@ class NodeType
|
|
|
402
819
|
return node;
|
|
403
820
|
}
|
|
404
821
|
|
|
405
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Allows custom types to add connectors or otherwise customize the node
|
|
824
|
+
* @protected
|
|
825
|
+
* @param {Node} node
|
|
826
|
+
*/
|
|
827
|
+
populate_node(_node)
|
|
406
828
|
{
|
|
407
829
|
}
|
|
408
830
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
831
|
+
/**
|
|
832
|
+
* Evaluates node logic
|
|
833
|
+
*
|
|
834
|
+
* Generally you should get input values with `node.inputs[name].get_value()`
|
|
835
|
+
* And set output values with `node.outputs[name].set_value()`.
|
|
836
|
+
*
|
|
837
|
+
* You can also alter the `node.preview` to add custom HTML to the node
|
|
838
|
+
* based on these values
|
|
839
|
+
*
|
|
840
|
+
* @param {Node} node
|
|
841
|
+
*/
|
|
842
|
+
evaluate_node(_node)
|
|
415
843
|
{
|
|
416
844
|
|
|
417
845
|
}
|
|
418
846
|
}
|
|
419
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Context menu for the node editor
|
|
850
|
+
*/
|
|
420
851
|
class ContextMenu extends VisualElement
|
|
421
852
|
{
|
|
853
|
+
/**
|
|
854
|
+
* @param {object|NodeType[]} actions If an object,
|
|
855
|
+
* keys will be items in the menu while values are the performed actions.
|
|
856
|
+
* Possible values are callbacks, NodeType instances (which will create new nodes),
|
|
857
|
+
* Or other actions definitions for submenus
|
|
858
|
+
*/
|
|
422
859
|
constructor(actions)
|
|
423
860
|
{
|
|
424
861
|
super();
|
|
@@ -427,11 +864,17 @@ class ContextMenu extends VisualElement
|
|
|
427
864
|
|
|
428
865
|
to_dom(editor)
|
|
429
866
|
{
|
|
430
|
-
this.dom = this.
|
|
431
|
-
this.dom.style.display = "none";
|
|
867
|
+
this.dom = this.build(editor);
|
|
432
868
|
return this.dom;
|
|
433
869
|
}
|
|
434
870
|
|
|
871
|
+
build(editor)
|
|
872
|
+
{
|
|
873
|
+
let dom = this.make_menu("dgl-menu", this.actions, editor);
|
|
874
|
+
dom.style.display = "none";
|
|
875
|
+
return dom;
|
|
876
|
+
}
|
|
877
|
+
|
|
435
878
|
make_action(title, action, editor)
|
|
436
879
|
{
|
|
437
880
|
let onclick = action;
|
|
@@ -487,6 +930,11 @@ class ContextMenu extends VisualElement
|
|
|
487
930
|
return item;
|
|
488
931
|
}
|
|
489
932
|
|
|
933
|
+
/**
|
|
934
|
+
* Opens the menu at the given point
|
|
935
|
+
* @param {number} x
|
|
936
|
+
* @param {number} y
|
|
937
|
+
*/
|
|
490
938
|
show(x, y)
|
|
491
939
|
{
|
|
492
940
|
this.dom.style.display = "";
|
|
@@ -494,19 +942,44 @@ class ContextMenu extends VisualElement
|
|
|
494
942
|
this.dom.style.top = y + "px";
|
|
495
943
|
}
|
|
496
944
|
|
|
945
|
+
/**
|
|
946
|
+
* Closes the menu
|
|
947
|
+
*/
|
|
497
948
|
close()
|
|
498
949
|
{
|
|
499
950
|
this.dom.style.display = "none";
|
|
500
951
|
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Rebuilds the DOM element
|
|
955
|
+
*/
|
|
956
|
+
rebuild(editor)
|
|
957
|
+
{
|
|
958
|
+
let parent = this.dom.parentNode;
|
|
959
|
+
let old_e = this.dom;
|
|
960
|
+
let new_e = this.build(editor);
|
|
961
|
+
parent.replaceChild(new_e, old_e);
|
|
962
|
+
this.dom = new_e;
|
|
963
|
+
}
|
|
501
964
|
}
|
|
502
965
|
|
|
966
|
+
/**
|
|
967
|
+
* Node Editor
|
|
968
|
+
*/
|
|
503
969
|
class Editor
|
|
504
970
|
{
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* @param {HTMLElement} container Parent element in the DOM
|
|
974
|
+
* @param {object|NodeType[]} Actions for the context menu (See ContextMenu)
|
|
975
|
+
*/
|
|
505
976
|
constructor(container, context_menu={})
|
|
506
977
|
{
|
|
507
978
|
this.nodes = [];
|
|
508
979
|
|
|
509
980
|
this.container = container;
|
|
981
|
+
if ( !container.hasAttribute("tabindex") )
|
|
982
|
+
container.setAttribute("tabindex", "0");
|
|
510
983
|
|
|
511
984
|
this.area = container.appendChild(document.createElement("div"));
|
|
512
985
|
container.classList.add("dgl-editor");
|
|
@@ -532,6 +1005,12 @@ class Editor
|
|
|
532
1005
|
this.container.addEventListener("mousedown", this._on_area_mouse_down.bind(this));
|
|
533
1006
|
this.container.addEventListener("mousemove", this._on_area_mouse_move.bind(this));
|
|
534
1007
|
this.container.addEventListener("mouseup", this._on_area_mouse_up.bind(this));
|
|
1008
|
+
|
|
1009
|
+
this.container.addEventListener("touchstart", this._on_area_mouse_down.bind(this));
|
|
1010
|
+
this.container.addEventListener("touchmove", this._on_area_mouse_move.bind(this));
|
|
1011
|
+
this.container.addEventListener("touchend", this._on_area_mouse_up.bind(this));
|
|
1012
|
+
this.container.addEventListener("touchcancel", this._on_area_mouse_up.bind(this));
|
|
1013
|
+
|
|
535
1014
|
this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
|
|
536
1015
|
this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
|
|
537
1016
|
|
|
@@ -544,8 +1023,6 @@ class Editor
|
|
|
544
1023
|
"Duplicate": () => {
|
|
545
1024
|
if ( this.selected_node ) {
|
|
546
1025
|
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
1026
|
this._select_node(node);
|
|
550
1027
|
|
|
551
1028
|
}
|
|
@@ -556,10 +1033,12 @@ class Editor
|
|
|
556
1033
|
this.container.appendChild(this.context_menu.to_dom(this));
|
|
557
1034
|
}
|
|
558
1035
|
|
|
559
|
-
|
|
1036
|
+
/**
|
|
1037
|
+
* Adds a new node to the graph
|
|
1038
|
+
* @param {Node} node
|
|
1039
|
+
*/
|
|
1040
|
+
add_node(node)
|
|
560
1041
|
{
|
|
561
|
-
let node = type.create_node();
|
|
562
|
-
node.set_input_value(data);
|
|
563
1042
|
node.x = this.cursor_x;
|
|
564
1043
|
node.y = this.cursor_y;
|
|
565
1044
|
this.nodes.push(node);
|
|
@@ -567,11 +1046,57 @@ class Editor
|
|
|
567
1046
|
this.area.appendChild(node_dom);
|
|
568
1047
|
|
|
569
1048
|
node_dom.addEventListener("mousedown", ev => this._on_node_mouse_down(ev, node));
|
|
1049
|
+
node_dom.addEventListener("touchstart", ev => this._on_node_mouse_down(ev, node));
|
|
570
1050
|
node_dom.addEventListener("contextmenu", ev => this._on_node_menu(ev, node));
|
|
1051
|
+
for ( let input of Object.values(node.inputs) )
|
|
1052
|
+
{
|
|
1053
|
+
if ( input.control )
|
|
1054
|
+
{
|
|
1055
|
+
input.control.on_change = () => {
|
|
1056
|
+
node.dirty = true;
|
|
1057
|
+
this._refresh_graph();
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Removes a node from the graph
|
|
1065
|
+
* @param {Node} node
|
|
1066
|
+
*/
|
|
1067
|
+
remove_node(node)
|
|
1068
|
+
{
|
|
1069
|
+
for ( let conn of node.all_connections() )
|
|
1070
|
+
conn.disconnect();
|
|
571
1071
|
|
|
1072
|
+
let ind = this.nodes.indexOf(node);
|
|
1073
|
+
if ( ind != -1 )
|
|
1074
|
+
this.nodes.splice(ind, 1);
|
|
1075
|
+
node.destroy();
|
|
1076
|
+
|
|
1077
|
+
this._refresh_graph();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Creates a new node from the given type
|
|
1082
|
+
* @param {NodeType} type
|
|
1083
|
+
* @param {object} data Optional data to populate node inputs
|
|
1084
|
+
* @returns {Node} The created node
|
|
1085
|
+
*/
|
|
1086
|
+
create_node(type, data={})
|
|
1087
|
+
{
|
|
1088
|
+
let node = type.create_node();
|
|
1089
|
+
this.add_node(node);
|
|
1090
|
+
node.set_input_value(data);
|
|
1091
|
+
this._refresh_graph();
|
|
572
1092
|
return node;
|
|
573
1093
|
}
|
|
574
1094
|
|
|
1095
|
+
/**
|
|
1096
|
+
* Automatically lays out the graph
|
|
1097
|
+
*
|
|
1098
|
+
* It assumes all nodes and connections are set up
|
|
1099
|
+
*/
|
|
575
1100
|
auto_layout()
|
|
576
1101
|
{
|
|
577
1102
|
setTimeout(() => this._on_auto_layout());
|
|
@@ -664,8 +1189,60 @@ class Editor
|
|
|
664
1189
|
}
|
|
665
1190
|
|
|
666
1191
|
this._refresh_graph();
|
|
1192
|
+
this.fit_view();
|
|
667
1193
|
}
|
|
668
1194
|
|
|
1195
|
+
/**
|
|
1196
|
+
* Pan and zoom to fit all the nodes
|
|
1197
|
+
* @param {number} margin Margin to leave around the area
|
|
1198
|
+
*/
|
|
1199
|
+
fit_view(margin=20)
|
|
1200
|
+
{
|
|
1201
|
+
if ( this.nodes.length == 0 )
|
|
1202
|
+
return;
|
|
1203
|
+
|
|
1204
|
+
let left = Infinity;
|
|
1205
|
+
let right = -Infinity;
|
|
1206
|
+
let top = Infinity;
|
|
1207
|
+
let bottom = -Infinity;
|
|
1208
|
+
|
|
1209
|
+
for ( let node of this.nodes )
|
|
1210
|
+
{
|
|
1211
|
+
let rect = node.dom.getBoundingClientRect();
|
|
1212
|
+
if ( rect.left < left ) left = rect.left;
|
|
1213
|
+
if ( rect.right > right ) right = rect.right;
|
|
1214
|
+
if ( rect.top < top ) top = rect.top;
|
|
1215
|
+
if ( rect.bottom > bottom ) bottom = rect.bottom;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
let crect = this.container.getBoundingClientRect();
|
|
1219
|
+
|
|
1220
|
+
left += -margin - crect.left;
|
|
1221
|
+
right += margin - crect.left;
|
|
1222
|
+
top += -margin - crect.top;
|
|
1223
|
+
bottom += margin - crect.top;
|
|
1224
|
+
|
|
1225
|
+
let width = right - left;
|
|
1226
|
+
let height = bottom - top;
|
|
1227
|
+
let x_scale = crect.width / width;
|
|
1228
|
+
let y_scale = crect.height / height;
|
|
1229
|
+
let scale = Math.min(x_scale, y_scale);
|
|
1230
|
+
this.scale = scale;
|
|
1231
|
+
|
|
1232
|
+
this.offset_x = (crect.width - width * scale) / 2 - left + margin;
|
|
1233
|
+
this.offset_y = (crect.height - height * scale) / 2 - top + margin;
|
|
1234
|
+
this._apply_transform();
|
|
1235
|
+
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Adds a connection to the graph
|
|
1240
|
+
* @param {Node} output_node Node the connection is coming from
|
|
1241
|
+
* @param {string} output_name Name of the output connector within output_node
|
|
1242
|
+
* @param {Node} input_node Node the connection is going to
|
|
1243
|
+
* @param {string} input_name Name of the input connector within input_node
|
|
1244
|
+
* @return {Connection?} Connection (Or null if the connection is impossible)
|
|
1245
|
+
*/
|
|
669
1246
|
connect(output_node, output_name, input_node, input_name)
|
|
670
1247
|
{
|
|
671
1248
|
let conn = output_node.connect(output_name, input_node, input_name);
|
|
@@ -681,6 +1258,10 @@ class Editor
|
|
|
681
1258
|
return conn;
|
|
682
1259
|
}
|
|
683
1260
|
|
|
1261
|
+
/**
|
|
1262
|
+
* Removes a connection from the graph
|
|
1263
|
+
* @param {Connection} connection
|
|
1264
|
+
*/
|
|
684
1265
|
disconnect(connection)
|
|
685
1266
|
{
|
|
686
1267
|
connection.disconnect();
|
|
@@ -700,18 +1281,27 @@ class Editor
|
|
|
700
1281
|
this.node_menu.close();
|
|
701
1282
|
}
|
|
702
1283
|
|
|
1284
|
+
_touch_mouse_pos(ev)
|
|
1285
|
+
{
|
|
1286
|
+
if ( ev.changedTouches )
|
|
1287
|
+
return ev.changedTouches[0];
|
|
1288
|
+
return ev;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
703
1291
|
_on_area_mouse_down(ev)
|
|
704
1292
|
{
|
|
1293
|
+
this.container.focus();
|
|
705
1294
|
this._close_menus();
|
|
706
1295
|
if ( ev.button == 0 )
|
|
707
1296
|
{
|
|
708
1297
|
this._select_node(null);
|
|
709
1298
|
}
|
|
710
|
-
else if ( ev.button == 1 )
|
|
1299
|
+
else if ( ev.button == 1 || ev.changedTouches )
|
|
711
1300
|
{
|
|
712
1301
|
let rect = this.container.getBoundingClientRect();
|
|
713
|
-
|
|
714
|
-
this.
|
|
1302
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1303
|
+
this.drag_start_x = evp.clientX - rect.left;
|
|
1304
|
+
this.drag_start_y = evp.clientY - rect.top;
|
|
715
1305
|
this.drag_start_ox = this.offset_x;
|
|
716
1306
|
this.drag_start_oy = this.offset_y;
|
|
717
1307
|
this.drag = "pan";
|
|
@@ -737,8 +1327,9 @@ class Editor
|
|
|
737
1327
|
_on_area_mouse_move(ev)
|
|
738
1328
|
{
|
|
739
1329
|
let rect = this.container.getBoundingClientRect();
|
|
740
|
-
let
|
|
741
|
-
let
|
|
1330
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1331
|
+
let drag_x = evp.clientX - rect.left;
|
|
1332
|
+
let drag_y = evp.clientY - rect.top;
|
|
742
1333
|
this.cursor_x = (drag_x - this.offset_x) / this.scale;
|
|
743
1334
|
this.cursor_y = (drag_y - this.offset_y) / this.scale;
|
|
744
1335
|
|
|
@@ -758,7 +1349,8 @@ class Editor
|
|
|
758
1349
|
let y = this.drag_start_oy + delta_y / this.scale;
|
|
759
1350
|
this.selected_node.x = x;
|
|
760
1351
|
this.selected_node.y = y;
|
|
761
|
-
this.selected_node.dom.style.
|
|
1352
|
+
this.selected_node.dom.style.left = `${x}px`;
|
|
1353
|
+
this.selected_node.dom.style.top = `${y}px`;
|
|
762
1354
|
for ( let connection of this.selected_node.all_connections() )
|
|
763
1355
|
this._update_connection(connection);
|
|
764
1356
|
}
|
|
@@ -803,7 +1395,8 @@ class Editor
|
|
|
803
1395
|
|
|
804
1396
|
_get_dest_connector(ev)
|
|
805
1397
|
{
|
|
806
|
-
let
|
|
1398
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1399
|
+
let elem = document.elementFromPoint(evp.clientX, evp.clientY);
|
|
807
1400
|
if ( !elem.classList.contains("dgl-socket") )
|
|
808
1401
|
return null;
|
|
809
1402
|
|
|
@@ -845,6 +1438,9 @@ class Editor
|
|
|
845
1438
|
ev.preventDefault();
|
|
846
1439
|
ev.stopPropagation();
|
|
847
1440
|
|
|
1441
|
+
if ( !this.container.contains(document.activeElement) )
|
|
1442
|
+
return;
|
|
1443
|
+
|
|
848
1444
|
let rect = this.container.getBoundingClientRect();
|
|
849
1445
|
let mouse_x = ev.clientX - rect.left;
|
|
850
1446
|
let mouse_y = ev.clientY - rect.top;
|
|
@@ -870,15 +1466,17 @@ class Editor
|
|
|
870
1466
|
|
|
871
1467
|
_on_node_mouse_down(ev, node)
|
|
872
1468
|
{
|
|
1469
|
+
// this.container.focus();
|
|
873
1470
|
this._close_menus();
|
|
874
|
-
if ( ev.button == 0 )
|
|
1471
|
+
if ( ev.button == 0 || ev.changedTouches )
|
|
875
1472
|
{
|
|
876
1473
|
ev.stopPropagation();
|
|
877
1474
|
this._select_node(node);
|
|
878
1475
|
this.drag = "node";
|
|
879
1476
|
let rect = this.container.getBoundingClientRect();
|
|
880
|
-
|
|
881
|
-
this.
|
|
1477
|
+
let evp = this._touch_mouse_pos(ev);
|
|
1478
|
+
this.drag_start_x = evp.clientX - rect.left;
|
|
1479
|
+
this.drag_start_y = evp.clientY - rect.top;
|
|
882
1480
|
this.drag_start_ox = node.x;
|
|
883
1481
|
this.drag_start_oy = node.y;
|
|
884
1482
|
if ( ev.target.classList.contains("dgl-socket") )
|
|
@@ -926,6 +1524,8 @@ Connection,
|
|
|
926
1524
|
NodeInput,
|
|
927
1525
|
NodeOutput,
|
|
928
1526
|
NodeControl,
|
|
1527
|
+
InputControl,
|
|
1528
|
+
DropDownControl,
|
|
929
1529
|
Node,
|
|
930
1530
|
NodeType,
|
|
931
1531
|
ContextMenu,
|