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