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