dragon-graph-lib 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/dragon-graph-lib-global.js +474 -79
- package/src/dragon-graph-lib.css +8 -5
- package/src/dragon-graph-lib.js +472 -79
|
@@ -15,16 +15,14 @@ class Socket
|
|
|
15
15
|
{
|
|
16
16
|
/**
|
|
17
17
|
* @param {string} name Socket type name
|
|
18
|
-
* @param {boolean} multi_input (For input sockets) whether it should accept multiple incoming connections
|
|
19
18
|
*/
|
|
20
|
-
constructor(name
|
|
19
|
+
constructor(name)
|
|
21
20
|
{
|
|
22
21
|
/**
|
|
23
22
|
* Socket type name
|
|
24
23
|
* @member {string}
|
|
25
24
|
*/
|
|
26
25
|
this.name = name;
|
|
27
|
-
this.multi_input = multi_input;
|
|
28
26
|
this.accepts_incoming = new Set();
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -205,11 +203,6 @@ class Connector extends VisualElement
|
|
|
205
203
|
this.dom_items = {};
|
|
206
204
|
}
|
|
207
205
|
|
|
208
|
-
update_dom()
|
|
209
|
-
{
|
|
210
|
-
this.dom_items.label.textContent = this.label;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
206
|
/**
|
|
214
207
|
* Adds a connection to the connector
|
|
215
208
|
* @param {Connection} connection
|
|
@@ -243,8 +236,9 @@ class NodeInput extends Connector
|
|
|
243
236
|
* @param {string} label User-visible label / title
|
|
244
237
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
245
238
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
239
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
246
240
|
*/
|
|
247
|
-
constructor(node, name, label, socket, control)
|
|
241
|
+
constructor(node, name, label, socket, control, multiple=false)
|
|
248
242
|
{
|
|
249
243
|
super(node, name, label, socket);
|
|
250
244
|
/**
|
|
@@ -252,6 +246,11 @@ class NodeInput extends Connector
|
|
|
252
246
|
* @member {HTMLInputElement?}
|
|
253
247
|
*/
|
|
254
248
|
this.control = control;
|
|
249
|
+
/**
|
|
250
|
+
* Whether this input accepts multiple incoming connections
|
|
251
|
+
* @member {boolean}
|
|
252
|
+
*/
|
|
253
|
+
this.multiple = multiple;
|
|
255
254
|
}
|
|
256
255
|
|
|
257
256
|
build_dom()
|
|
@@ -265,20 +264,17 @@ class NodeInput extends Connector
|
|
|
265
264
|
container.classList.add("dgl-socketed");
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
this.dom_items.label = document.createElement("span");
|
|
269
|
-
this.dom_items.label.textContent = this.name;
|
|
270
|
-
this.dom_items.label.classList.add("dgl-label", "dgl-input-label");
|
|
271
|
-
container.appendChild(this.dom_items.label);
|
|
272
|
-
|
|
273
267
|
if ( this.control )
|
|
274
|
-
container.appendChild(this.control);
|
|
268
|
+
container.appendChild(this.control.to_dom(this.label, this.name));
|
|
269
|
+
else
|
|
270
|
+
container.appendChild(NodeControl.make_label(this.label));
|
|
275
271
|
|
|
276
272
|
return container;
|
|
277
273
|
}
|
|
278
274
|
|
|
279
275
|
connect(connection)
|
|
280
276
|
{
|
|
281
|
-
if ( !this.
|
|
277
|
+
if ( !this.multiple )
|
|
282
278
|
{
|
|
283
279
|
for ( let conn of this.connections )
|
|
284
280
|
conn.disconnect();
|
|
@@ -288,7 +284,7 @@ class NodeInput extends Connector
|
|
|
288
284
|
this.connections.push(connection);
|
|
289
285
|
|
|
290
286
|
if ( this.control )
|
|
291
|
-
this.control.
|
|
287
|
+
this.control.hide();
|
|
292
288
|
}
|
|
293
289
|
|
|
294
290
|
/**
|
|
@@ -299,7 +295,7 @@ class NodeInput extends Connector
|
|
|
299
295
|
{
|
|
300
296
|
if ( this.control )
|
|
301
297
|
{
|
|
302
|
-
this.control.value
|
|
298
|
+
this.control.set_value(value);
|
|
303
299
|
}
|
|
304
300
|
}
|
|
305
301
|
|
|
@@ -309,7 +305,7 @@ class NodeInput extends Connector
|
|
|
309
305
|
* If there's input connections, the value is based on those.
|
|
310
306
|
* Otherwise the value comes from the control.
|
|
311
307
|
*
|
|
312
|
-
* If there
|
|
308
|
+
* If there this connector accepts multiple inputs, the returned value
|
|
313
309
|
* is always an array.
|
|
314
310
|
*
|
|
315
311
|
* @return {any}
|
|
@@ -318,20 +314,20 @@ class NodeInput extends Connector
|
|
|
318
314
|
{
|
|
319
315
|
if ( this.connections.length )
|
|
320
316
|
{
|
|
321
|
-
if ( this.
|
|
317
|
+
if ( this.multiple )
|
|
322
318
|
return this.connections.map(c => c.output.get_value());
|
|
323
319
|
return this.connections[0].output.get_value();
|
|
324
320
|
}
|
|
325
321
|
|
|
326
322
|
if ( !this.control )
|
|
327
323
|
{
|
|
328
|
-
if ( this.socket && this.
|
|
324
|
+
if ( this.socket && this.multiple )
|
|
329
325
|
return [];
|
|
330
326
|
return null;
|
|
331
327
|
}
|
|
332
328
|
|
|
333
|
-
let value = this.control.
|
|
334
|
-
if ( this.socket && this.
|
|
329
|
+
let value = this.control.get_value();
|
|
330
|
+
if ( this.socket && this.multiple )
|
|
335
331
|
return [value];
|
|
336
332
|
return value;
|
|
337
333
|
}
|
|
@@ -340,7 +336,7 @@ class NodeInput extends Connector
|
|
|
340
336
|
{
|
|
341
337
|
super.disconnect(connection);
|
|
342
338
|
if ( this.control && this.connections.length == 0 )
|
|
343
|
-
this.control.
|
|
339
|
+
this.control.show();
|
|
344
340
|
}
|
|
345
341
|
}
|
|
346
342
|
|
|
@@ -402,7 +398,7 @@ class NodeOutput extends Connector
|
|
|
402
398
|
*/
|
|
403
399
|
get_value()
|
|
404
400
|
{
|
|
405
|
-
if ( this.value
|
|
401
|
+
if ( this.value === undefined || this.node.dirty )
|
|
406
402
|
this.node.evaluate();
|
|
407
403
|
return this.value;
|
|
408
404
|
}
|
|
@@ -415,23 +411,221 @@ function force_default(ev)
|
|
|
415
411
|
}
|
|
416
412
|
|
|
417
413
|
/**
|
|
418
|
-
*
|
|
419
|
-
* @param {string} type Input type
|
|
420
|
-
* @param {object} attrs Input attributes as an object
|
|
421
|
-
* @returns {HTMLInputElement}
|
|
414
|
+
* Custom control interface
|
|
422
415
|
*/
|
|
423
|
-
|
|
416
|
+
class NodeControl
|
|
424
417
|
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
418
|
+
/**
|
|
419
|
+
* @param {object} config Control config
|
|
420
|
+
* @param {any} config.default Control default value
|
|
421
|
+
*/
|
|
422
|
+
constructor(config={})
|
|
423
|
+
{
|
|
424
|
+
this.on_change = null;
|
|
425
|
+
this.config = config;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
to_dom(label, name)
|
|
429
|
+
{
|
|
430
|
+
this.element = document.createElement("div");
|
|
431
|
+
this.element.classList.add("dgl-control-parent");
|
|
432
|
+
if ( label.length )
|
|
433
|
+
this.element.appendChild(NodeControl.make_label(label));
|
|
434
|
+
this.setup_dom(this.element, name);
|
|
435
|
+
if ( "default" in this.config )
|
|
436
|
+
this.set_value(this.config.default);
|
|
437
|
+
return this.element;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Sets up the DOM
|
|
442
|
+
* @param {HTMLElement} parent Element to add additional content to
|
|
443
|
+
* @param {name} name Control name
|
|
444
|
+
*/
|
|
445
|
+
setup_dom(_parent, _name)
|
|
446
|
+
{
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Dispatches the change event to update the graph
|
|
451
|
+
*/
|
|
452
|
+
notify_change()
|
|
453
|
+
{
|
|
454
|
+
this.on_change();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Override to return the control value
|
|
459
|
+
* @returns {any} value
|
|
460
|
+
*/
|
|
461
|
+
get_value()
|
|
462
|
+
{
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Override to return set a value to the control
|
|
467
|
+
* @param {any} value
|
|
468
|
+
*/
|
|
469
|
+
set_value(_value)
|
|
470
|
+
{
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Ensures an element uses the default mouse interactions instead of
|
|
475
|
+
* propagating to the node editor
|
|
476
|
+
*/
|
|
477
|
+
use_default_events(element)
|
|
478
|
+
{
|
|
479
|
+
element.addEventListener("mousedown", force_default);
|
|
480
|
+
element.addEventListener("mouseup", force_default);
|
|
481
|
+
element.addEventListener("mousemove", force_default);
|
|
482
|
+
element.addEventListener("wheel", force_default);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Creates a label element
|
|
487
|
+
* @param {string} label Text to display
|
|
488
|
+
*/
|
|
489
|
+
static make_label(label)
|
|
490
|
+
{
|
|
491
|
+
let elem = document.createElement("span");
|
|
492
|
+
elem.textContent = label;
|
|
493
|
+
elem.classList.add("dgl-label", "dgl-input-label");
|
|
494
|
+
return elem;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
set_visibility(value)
|
|
498
|
+
{
|
|
499
|
+
for ( let ch of this.element.children )
|
|
500
|
+
if ( !ch.classList.contains("dgl-label") )
|
|
501
|
+
ch.style.visibility = value;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Hides the control when an input connection is attached
|
|
506
|
+
*/
|
|
507
|
+
hide()
|
|
508
|
+
{
|
|
509
|
+
this.set_visibility("hidden");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Shows the control when an input connection is detached
|
|
514
|
+
*/
|
|
515
|
+
show()
|
|
516
|
+
{
|
|
517
|
+
this.set_visibility("visible");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Serializes a value to JSON
|
|
522
|
+
* @param {any} value Value to serialize, as returned from this.get_value()
|
|
523
|
+
* @return {any} value Value that can be serialized to JSON
|
|
524
|
+
*/
|
|
525
|
+
serialize_value(value)
|
|
526
|
+
{
|
|
527
|
+
return value;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Deserializes a value from JSON
|
|
532
|
+
* @param {any} value Value from a JSON object
|
|
533
|
+
* @return {any} value Value that can be passed to this.set_value()
|
|
534
|
+
*/
|
|
535
|
+
deserialize_value(value)
|
|
536
|
+
{
|
|
537
|
+
return value;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Control for simple input elements
|
|
543
|
+
*/
|
|
544
|
+
class InputControl extends NodeControl
|
|
545
|
+
{
|
|
546
|
+
/**
|
|
547
|
+
* @param {object} config Control config
|
|
548
|
+
* @param {string} config.type Input type
|
|
549
|
+
* @param {object} config.attrs Input attributes
|
|
550
|
+
* @param {any} config.default Default value
|
|
551
|
+
*/
|
|
552
|
+
constructor(config={type: "text"})
|
|
553
|
+
{
|
|
554
|
+
super(config);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
setup_dom(parent, name)
|
|
558
|
+
{
|
|
559
|
+
this.input = document.createElement("input");
|
|
560
|
+
parent.appendChild(this.input);
|
|
561
|
+
this.input.type = this.config.type;
|
|
562
|
+
this.input.classList.add("dgl-control");
|
|
563
|
+
for ( let [k, v] of Object.entries(this.config.attrs ?? {}) )
|
|
564
|
+
this.input.setAttribute(k, v);
|
|
565
|
+
this.use_default_events(this.input);
|
|
566
|
+
this.input.name = name;
|
|
567
|
+
this.input.addEventListener("input", () => this.notify_change());
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
get_value()
|
|
571
|
+
{
|
|
572
|
+
if ( this.config.type == "checkbox" )
|
|
573
|
+
return this.input.checked;
|
|
574
|
+
if ( this.config.type == "number" || this.config.type == "range" )
|
|
575
|
+
return Number(this.input.value);
|
|
576
|
+
return this.input.value;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
set_value(value)
|
|
580
|
+
{
|
|
581
|
+
if ( this.config.type == "checkbox" )
|
|
582
|
+
this.input.checked = value;
|
|
583
|
+
else
|
|
584
|
+
this.input.value = value;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Drop-down Control
|
|
590
|
+
*/
|
|
591
|
+
class DropDownControl extends NodeControl
|
|
592
|
+
{
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* @param {object} config Control config
|
|
596
|
+
* @param {Array.<string, string>} config.options Drop down options ([value, display])
|
|
597
|
+
* @param {any} config.default Default value
|
|
598
|
+
*/
|
|
599
|
+
constructor(config={options: []})
|
|
600
|
+
{
|
|
601
|
+
super(config);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
setup_dom(parent, name)
|
|
605
|
+
{
|
|
606
|
+
this.input = document.createElement("select");
|
|
607
|
+
for ( let [value, display] of this.config.options )
|
|
608
|
+
{
|
|
609
|
+
let option = document.createElement("option");
|
|
610
|
+
this.input.appendChild(option);
|
|
611
|
+
option.value = value;
|
|
612
|
+
option.textContent = display ?? value;
|
|
613
|
+
}
|
|
614
|
+
this.input.name = name;
|
|
615
|
+
parent.appendChild(this.input);
|
|
616
|
+
this.use_default_events(this.input);
|
|
617
|
+
this.input.addEventListener("input", () => this.notify_change());
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
get_value()
|
|
621
|
+
{
|
|
622
|
+
return this.input.value;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
set_value(value)
|
|
626
|
+
{
|
|
627
|
+
this.input.value = value;
|
|
628
|
+
}
|
|
435
629
|
}
|
|
436
630
|
|
|
437
631
|
/**
|
|
@@ -473,13 +667,14 @@ class Node extends VisualElement
|
|
|
473
667
|
* @member {number}
|
|
474
668
|
*/
|
|
475
669
|
this.y = 0;
|
|
670
|
+
this.dirty = true;
|
|
476
671
|
}
|
|
477
672
|
|
|
478
673
|
/**
|
|
479
674
|
* Sets control values for inputs
|
|
480
675
|
* @param {object} data Object with input names as keys and associated values
|
|
481
676
|
*/
|
|
482
|
-
|
|
677
|
+
set_input_data(data)
|
|
483
678
|
{
|
|
484
679
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
485
680
|
{
|
|
@@ -492,7 +687,7 @@ class Node extends VisualElement
|
|
|
492
687
|
* Gets values from the inputs
|
|
493
688
|
* @returns {object} Object with input names as keys and associated values
|
|
494
689
|
*/
|
|
495
|
-
|
|
690
|
+
get_input_data()
|
|
496
691
|
{
|
|
497
692
|
let data = {};
|
|
498
693
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
@@ -526,10 +721,11 @@ class Node extends VisualElement
|
|
|
526
721
|
* @param {string} label User-visible label / title
|
|
527
722
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
528
723
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
724
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
529
725
|
*/
|
|
530
|
-
add_input(name, title, socket, control)
|
|
726
|
+
add_input(name, title, socket, control, multiple)
|
|
531
727
|
{
|
|
532
|
-
this.inputs[name] = new NodeInput(this, name, title, socket, control);
|
|
728
|
+
this.inputs[name] = new NodeInput(this, name, title, socket, control, multiple);
|
|
533
729
|
}
|
|
534
730
|
|
|
535
731
|
/**
|
|
@@ -575,6 +771,7 @@ class Node extends VisualElement
|
|
|
575
771
|
|
|
576
772
|
evaluate()
|
|
577
773
|
{
|
|
774
|
+
this.dirty = false;
|
|
578
775
|
if ( this.type )
|
|
579
776
|
this.type.evaluate_node(this);
|
|
580
777
|
}
|
|
@@ -595,7 +792,7 @@ class Node extends VisualElement
|
|
|
595
792
|
* Returns a list of all output connections
|
|
596
793
|
* @returns {Connection[]}
|
|
597
794
|
*/
|
|
598
|
-
|
|
795
|
+
output_connections()
|
|
599
796
|
{
|
|
600
797
|
return Object.values(this.outputs)
|
|
601
798
|
.map(c => c.connections)
|
|
@@ -681,29 +878,37 @@ class NodeType
|
|
|
681
878
|
class ContextMenu extends VisualElement
|
|
682
879
|
{
|
|
683
880
|
/**
|
|
881
|
+
* @param {Editor} editor
|
|
684
882
|
* @param {object|NodeType[]} actions If an object,
|
|
685
883
|
* keys will be items in the menu while values are the performed actions.
|
|
686
884
|
* Possible values are callbacks, NodeType instances (which will create new nodes),
|
|
687
885
|
* Or other actions definitions for submenus
|
|
688
886
|
*/
|
|
689
|
-
constructor(actions)
|
|
887
|
+
constructor(editor, actions)
|
|
690
888
|
{
|
|
691
889
|
super();
|
|
890
|
+
this.editor = editor;
|
|
692
891
|
this.actions = actions;
|
|
693
892
|
}
|
|
694
893
|
|
|
695
|
-
to_dom(
|
|
894
|
+
to_dom()
|
|
696
895
|
{
|
|
697
|
-
this.dom = this.
|
|
698
|
-
this.dom.style.display = "none";
|
|
896
|
+
this.dom = this.build();
|
|
699
897
|
return this.dom;
|
|
700
898
|
}
|
|
701
899
|
|
|
702
|
-
|
|
900
|
+
build()
|
|
901
|
+
{
|
|
902
|
+
let dom = this.make_menu("dgl-menu", this.actions);
|
|
903
|
+
dom.style.display = "none";
|
|
904
|
+
return dom;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
make_action(title, action)
|
|
703
908
|
{
|
|
704
909
|
let onclick = action;
|
|
705
910
|
if ( action instanceof NodeType )
|
|
706
|
-
onclick = () => editor.create_node(action);
|
|
911
|
+
onclick = () => this.editor.create_node(action);
|
|
707
912
|
|
|
708
913
|
let item = document.createElement("li");
|
|
709
914
|
item.classList.add("dgl-menu-item");
|
|
@@ -718,7 +923,7 @@ class ContextMenu extends VisualElement
|
|
|
718
923
|
return item;
|
|
719
924
|
}
|
|
720
925
|
|
|
721
|
-
make_menu(cls, actions
|
|
926
|
+
make_menu(cls, actions)
|
|
722
927
|
{
|
|
723
928
|
let dom = document.createElement("ul");
|
|
724
929
|
dom.classList.add(cls);
|
|
@@ -727,7 +932,7 @@ class ContextMenu extends VisualElement
|
|
|
727
932
|
{
|
|
728
933
|
for ( let action of actions )
|
|
729
934
|
{
|
|
730
|
-
dom.appendChild(this.make_action(action.name, action
|
|
935
|
+
dom.appendChild(this.make_action(action.name, action));
|
|
731
936
|
}
|
|
732
937
|
}
|
|
733
938
|
else
|
|
@@ -735,7 +940,7 @@ class ContextMenu extends VisualElement
|
|
|
735
940
|
for ( let [name, val] of Object.entries(actions) )
|
|
736
941
|
{
|
|
737
942
|
if ( Array.isArray(val) || (typeof val == "object" && !(val instanceof NodeType)) )
|
|
738
|
-
dom.appendChild(this.make_submenu(name, val
|
|
943
|
+
dom.appendChild(this.make_submenu(name, val));
|
|
739
944
|
else
|
|
740
945
|
dom.appendChild(this.make_action(name, val));
|
|
741
946
|
}
|
|
@@ -743,14 +948,14 @@ class ContextMenu extends VisualElement
|
|
|
743
948
|
return dom;
|
|
744
949
|
}
|
|
745
950
|
|
|
746
|
-
make_submenu(name, actions
|
|
951
|
+
make_submenu(name, actions)
|
|
747
952
|
{
|
|
748
953
|
let item = document.createElement("li");
|
|
749
954
|
item.classList.add("dgl-menu-item");
|
|
750
955
|
let label = item.appendChild(document.createElement("span"));
|
|
751
956
|
label.textContent = name;
|
|
752
957
|
label.classList.add("dgl-submenu-label");
|
|
753
|
-
item.appendChild(this.make_menu("dgl-submenu", actions
|
|
958
|
+
item.appendChild(this.make_menu("dgl-submenu", actions));
|
|
754
959
|
return item;
|
|
755
960
|
}
|
|
756
961
|
|
|
@@ -773,6 +978,18 @@ class ContextMenu extends VisualElement
|
|
|
773
978
|
{
|
|
774
979
|
this.dom.style.display = "none";
|
|
775
980
|
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Rebuilds the DOM element
|
|
984
|
+
*/
|
|
985
|
+
rebuild(editor)
|
|
986
|
+
{
|
|
987
|
+
let parent = this.dom.parentNode;
|
|
988
|
+
let old_e = this.dom;
|
|
989
|
+
let new_e = this.build(editor);
|
|
990
|
+
parent.replaceChild(new_e, old_e);
|
|
991
|
+
this.dom = new_e;
|
|
992
|
+
}
|
|
776
993
|
}
|
|
777
994
|
|
|
778
995
|
/**
|
|
@@ -788,6 +1005,7 @@ class Editor
|
|
|
788
1005
|
constructor(container, context_menu={})
|
|
789
1006
|
{
|
|
790
1007
|
this.nodes = [];
|
|
1008
|
+
this.type_registry = {};
|
|
791
1009
|
|
|
792
1010
|
this.container = container;
|
|
793
1011
|
if ( !container.hasAttribute("tabindex") )
|
|
@@ -826,25 +1044,142 @@ class Editor
|
|
|
826
1044
|
this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
|
|
827
1045
|
this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
|
|
828
1046
|
|
|
829
|
-
this.context_menu = new ContextMenu(context_menu);
|
|
830
|
-
this.node_menu = new ContextMenu({
|
|
1047
|
+
this.context_menu = new ContextMenu(this, context_menu);
|
|
1048
|
+
this.node_menu = new ContextMenu(this, {
|
|
831
1049
|
"Delete": () => {
|
|
832
1050
|
if ( this.selected_node )
|
|
833
1051
|
this.remove_node(this.selected_node);
|
|
834
1052
|
},
|
|
835
1053
|
"Duplicate": () => {
|
|
836
1054
|
if ( this.selected_node ) {
|
|
837
|
-
let node = this.create_node(this.selected_node.type, this.selected_node.
|
|
1055
|
+
let node = this.create_node(this.selected_node.type, this.selected_node.get_input_data());
|
|
838
1056
|
this._select_node(node);
|
|
839
1057
|
|
|
840
1058
|
}
|
|
841
1059
|
}
|
|
842
1060
|
});
|
|
843
1061
|
|
|
844
|
-
this.container.appendChild(this.node_menu.to_dom(
|
|
845
|
-
this.container.appendChild(this.context_menu.to_dom(
|
|
1062
|
+
this.container.appendChild(this.node_menu.to_dom());
|
|
1063
|
+
this.container.appendChild(this.context_menu.to_dom());
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Registers node types for deserialization of graph data
|
|
1068
|
+
* @param {NodeType} type Type to register, you must ensure names are unique.
|
|
1069
|
+
* @param {string} name Name override in the registry
|
|
1070
|
+
*/
|
|
1071
|
+
register_type(type, name=null)
|
|
1072
|
+
{
|
|
1073
|
+
if ( !name )
|
|
1074
|
+
name = type.name;
|
|
1075
|
+
type.registry_name = name;
|
|
1076
|
+
this.type_registry[name] = type;
|
|
1077
|
+
return type;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Returns a list of registered node types
|
|
1082
|
+
* @returns {NodeType[]}
|
|
1083
|
+
*/
|
|
1084
|
+
registered_types()
|
|
1085
|
+
{
|
|
1086
|
+
return Object.values(this.type_registry);
|
|
846
1087
|
}
|
|
847
1088
|
|
|
1089
|
+
/**
|
|
1090
|
+
* Serializes the editor data to a JSON object
|
|
1091
|
+
* Requires all node types to be registered
|
|
1092
|
+
* @returns {object}
|
|
1093
|
+
*/
|
|
1094
|
+
toJSON()
|
|
1095
|
+
{
|
|
1096
|
+
let data = {
|
|
1097
|
+
view: {
|
|
1098
|
+
x: this.offset_x,
|
|
1099
|
+
y: this.offset_y,
|
|
1100
|
+
scale: this.scale
|
|
1101
|
+
},
|
|
1102
|
+
nodes: [],
|
|
1103
|
+
connections: [],
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
for ( let i = 0; i < this.nodes.length; i++ )
|
|
1108
|
+
{
|
|
1109
|
+
this.nodes[i]._json_index = i;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
for ( let node of this.nodes )
|
|
1113
|
+
{
|
|
1114
|
+
let node_data = {
|
|
1115
|
+
index: node._json_index,
|
|
1116
|
+
type: node.type.registry_name || node.type.name,
|
|
1117
|
+
title: node.title,
|
|
1118
|
+
x: node.x,
|
|
1119
|
+
y: node.y,
|
|
1120
|
+
inputs: {},
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
for ( let [name, input] of Object.entries(node.inputs) )
|
|
1124
|
+
{
|
|
1125
|
+
if ( input.control )
|
|
1126
|
+
node_data.inputs[name] = input.control.serialize_value(input.control.get_value());
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
for ( let conn of node.output_connections() )
|
|
1130
|
+
{
|
|
1131
|
+
data.connections.push([
|
|
1132
|
+
conn.output.node._json_index,
|
|
1133
|
+
conn.output.name,
|
|
1134
|
+
conn.input.node._json_index,
|
|
1135
|
+
conn.input.name
|
|
1136
|
+
]);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
data.nodes.push(node_data);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
return data;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Loads editor data from JSON
|
|
1147
|
+
* Requires all node types to be registered
|
|
1148
|
+
* @param {object} data Editor data as returned from toJSON
|
|
1149
|
+
*/
|
|
1150
|
+
from_json(data)
|
|
1151
|
+
{
|
|
1152
|
+
this.clear();
|
|
1153
|
+
|
|
1154
|
+
this.offset_x = data.view.x;
|
|
1155
|
+
this.offset_y = data.view.y;
|
|
1156
|
+
this.scale = data.view.scale;
|
|
1157
|
+
this._apply_transform();
|
|
1158
|
+
|
|
1159
|
+
for ( let node_data of data.nodes )
|
|
1160
|
+
{
|
|
1161
|
+
let node = this.type_registry[node_data.type].create_node();
|
|
1162
|
+
node.title = node_data.title;
|
|
1163
|
+
node.x = node_data.x;
|
|
1164
|
+
node.y = node_data.y;
|
|
1165
|
+
this.add_node(node);
|
|
1166
|
+
for ( let [name, input] of Object.entries(node.inputs) )
|
|
1167
|
+
{
|
|
1168
|
+
let val = node_data.inputs[name];
|
|
1169
|
+
if ( val !== undefined && input.control )
|
|
1170
|
+
{
|
|
1171
|
+
input.set_value(input.control.deserialize_value(val));
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
for ( let [oind, oname, iind, iname] of data.connections )
|
|
1177
|
+
{
|
|
1178
|
+
this._create_connection(this.nodes[oind], oname, this.nodes[iind], iname);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
this._refresh_graph();
|
|
1182
|
+
}
|
|
848
1183
|
|
|
849
1184
|
/**
|
|
850
1185
|
* Adds a new node to the graph
|
|
@@ -852,8 +1187,6 @@ class Editor
|
|
|
852
1187
|
*/
|
|
853
1188
|
add_node(node)
|
|
854
1189
|
{
|
|
855
|
-
node.x = this.cursor_x;
|
|
856
|
-
node.y = this.cursor_y;
|
|
857
1190
|
this.nodes.push(node);
|
|
858
1191
|
let node_dom = node.to_dom();
|
|
859
1192
|
this.area.appendChild(node_dom);
|
|
@@ -865,11 +1198,30 @@ class Editor
|
|
|
865
1198
|
{
|
|
866
1199
|
if ( input.control )
|
|
867
1200
|
{
|
|
868
|
-
input.control.
|
|
1201
|
+
input.control.on_change = () => {
|
|
1202
|
+
node.dirty = true;
|
|
1203
|
+
this._refresh_graph();
|
|
1204
|
+
};
|
|
869
1205
|
}
|
|
870
1206
|
}
|
|
871
1207
|
}
|
|
872
1208
|
|
|
1209
|
+
/**
|
|
1210
|
+
* Removes all nodes from the graph
|
|
1211
|
+
*/
|
|
1212
|
+
clear()
|
|
1213
|
+
{
|
|
1214
|
+
for ( let node of this.nodes )
|
|
1215
|
+
{
|
|
1216
|
+
for ( let conn of node.all_connections() )
|
|
1217
|
+
conn.destroy();
|
|
1218
|
+
node.destroy();
|
|
1219
|
+
}
|
|
1220
|
+
this.nodes = [];
|
|
1221
|
+
this.reset_view();
|
|
1222
|
+
this._refresh_graph();
|
|
1223
|
+
}
|
|
1224
|
+
|
|
873
1225
|
/**
|
|
874
1226
|
* Removes a node from the graph
|
|
875
1227
|
* @param {Node} node
|
|
@@ -896,8 +1248,11 @@ class Editor
|
|
|
896
1248
|
create_node(type, data={})
|
|
897
1249
|
{
|
|
898
1250
|
let node = type.create_node();
|
|
899
|
-
node.
|
|
1251
|
+
node.x = this.cursor_x;
|
|
1252
|
+
node.y = this.cursor_y;
|
|
900
1253
|
this.add_node(node);
|
|
1254
|
+
node.set_input_data(data);
|
|
1255
|
+
this._refresh_graph();
|
|
901
1256
|
return node;
|
|
902
1257
|
}
|
|
903
1258
|
|
|
@@ -1001,6 +1356,17 @@ class Editor
|
|
|
1001
1356
|
this.fit_view();
|
|
1002
1357
|
}
|
|
1003
1358
|
|
|
1359
|
+
/**
|
|
1360
|
+
* Clears scale and offset
|
|
1361
|
+
*/
|
|
1362
|
+
reset_view()
|
|
1363
|
+
{
|
|
1364
|
+
this.offset_x = 0;
|
|
1365
|
+
this.offset_y = 0;
|
|
1366
|
+
this.scale = 1;
|
|
1367
|
+
this._apply_transform();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1004
1370
|
/**
|
|
1005
1371
|
* Pan and zoom to fit all the nodes
|
|
1006
1372
|
* @param {number} margin Margin to leave around the area
|
|
@@ -1008,28 +1374,38 @@ class Editor
|
|
|
1008
1374
|
fit_view(margin=20)
|
|
1009
1375
|
{
|
|
1010
1376
|
if ( this.nodes.length == 0 )
|
|
1377
|
+
{
|
|
1378
|
+
this.reset_view();
|
|
1011
1379
|
return;
|
|
1380
|
+
}
|
|
1012
1381
|
|
|
1013
1382
|
let left = Infinity;
|
|
1014
1383
|
let right = -Infinity;
|
|
1015
1384
|
let top = Infinity;
|
|
1016
1385
|
let bottom = -Infinity;
|
|
1017
1386
|
|
|
1387
|
+
let crect = this.container.getBoundingClientRect();
|
|
1388
|
+
|
|
1018
1389
|
for ( let node of this.nodes )
|
|
1019
1390
|
{
|
|
1020
|
-
let
|
|
1391
|
+
let client_rect = node.dom.getBoundingClientRect();
|
|
1392
|
+
|
|
1393
|
+
let rect = {};
|
|
1394
|
+
rect.left = node.x;
|
|
1395
|
+
rect.right = node.x + client_rect.width / this.scale;
|
|
1396
|
+
rect.top = node.y;
|
|
1397
|
+
rect.bottom = node.y + client_rect.height / this.scale;
|
|
1398
|
+
|
|
1021
1399
|
if ( rect.left < left ) left = rect.left;
|
|
1022
1400
|
if ( rect.right > right ) right = rect.right;
|
|
1023
1401
|
if ( rect.top < top ) top = rect.top;
|
|
1024
1402
|
if ( rect.bottom > bottom ) bottom = rect.bottom;
|
|
1025
1403
|
}
|
|
1026
1404
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
top += -margin - crect.top;
|
|
1032
|
-
bottom += margin - crect.top;
|
|
1405
|
+
left += -margin;
|
|
1406
|
+
right += margin;
|
|
1407
|
+
top += -margin;
|
|
1408
|
+
bottom += margin;
|
|
1033
1409
|
|
|
1034
1410
|
let width = right - left;
|
|
1035
1411
|
let height = bottom - top;
|
|
@@ -1038,12 +1414,25 @@ class Editor
|
|
|
1038
1414
|
let scale = Math.min(x_scale, y_scale);
|
|
1039
1415
|
this.scale = scale;
|
|
1040
1416
|
|
|
1041
|
-
this.offset_x = (crect.width - width * scale) / 2 - left
|
|
1042
|
-
this.offset_y = (crect.height - height * scale) / 2 - top
|
|
1417
|
+
this.offset_x = (crect.width - width * scale) / 2 - left * scale;
|
|
1418
|
+
this.offset_y = (crect.height - height * scale) / 2 - top * scale;
|
|
1043
1419
|
this._apply_transform();
|
|
1044
1420
|
|
|
1045
1421
|
}
|
|
1046
1422
|
|
|
1423
|
+
_create_connection(output_node, output_name, input_node, input_name)
|
|
1424
|
+
{
|
|
1425
|
+
let conn = output_node.connect(output_name, input_node, input_name);
|
|
1426
|
+
if ( conn )
|
|
1427
|
+
{
|
|
1428
|
+
conn.dom = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1429
|
+
conn.dom.classList.add("connection-from-" + to_slug(output_node.outputs[output_name].socket.name));
|
|
1430
|
+
conn.dom.classList.add("connection-to-" + to_slug(input_node.inputs[input_name].socket.name));
|
|
1431
|
+
this.connection_parent.appendChild(conn.dom);
|
|
1432
|
+
}
|
|
1433
|
+
return conn;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1047
1436
|
/**
|
|
1048
1437
|
* Adds a connection to the graph
|
|
1049
1438
|
* @param {Node} output_node Node the connection is coming from
|
|
@@ -1054,13 +1443,9 @@ class Editor
|
|
|
1054
1443
|
*/
|
|
1055
1444
|
connect(output_node, output_name, input_node, input_name)
|
|
1056
1445
|
{
|
|
1057
|
-
let conn =
|
|
1446
|
+
let conn = this._create_connection(output_node, output_name, input_node, input_name)
|
|
1058
1447
|
if ( conn )
|
|
1059
1448
|
{
|
|
1060
|
-
conn.dom = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1061
|
-
conn.dom.classList.add("connection-from-" + to_slug(output_node.outputs[output_name].socket.name));
|
|
1062
|
-
conn.dom.classList.add("connection-to-" + to_slug(input_node.inputs[input_name].socket.name));
|
|
1063
|
-
this.connection_parent.appendChild(conn.dom);
|
|
1064
1449
|
this._update_connection(conn);
|
|
1065
1450
|
this._refresh_graph();
|
|
1066
1451
|
}
|
|
@@ -1184,6 +1569,14 @@ class Editor
|
|
|
1184
1569
|
this.area.style.transform = `translate(${this.offset_x}px, ${this.offset_y}px) scale(${this.scale})`;
|
|
1185
1570
|
}
|
|
1186
1571
|
|
|
1572
|
+
/**
|
|
1573
|
+
* Re-evaluates all the nodes
|
|
1574
|
+
*/
|
|
1575
|
+
update()
|
|
1576
|
+
{
|
|
1577
|
+
this._refresh_graph();
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1187
1580
|
_refresh_graph()
|
|
1188
1581
|
{
|
|
1189
1582
|
for ( let node of this.nodes )
|
|
@@ -1333,6 +1726,8 @@ Connection,
|
|
|
1333
1726
|
NodeInput,
|
|
1334
1727
|
NodeOutput,
|
|
1335
1728
|
NodeControl,
|
|
1729
|
+
InputControl,
|
|
1730
|
+
DropDownControl,
|
|
1336
1731
|
Node,
|
|
1337
1732
|
NodeType,
|
|
1338
1733
|
ContextMenu,
|