dragon-graph-lib 0.1.3 → 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 +250 -48
- package/src/dragon-graph-lib.css +3 -5
- package/src/dragon-graph-lib.js +250 -48
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -238,8 +236,9 @@ class NodeInput extends Connector
|
|
|
238
236
|
* @param {string} label User-visible label / title
|
|
239
237
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
240
238
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
239
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
241
240
|
*/
|
|
242
|
-
constructor(node, name, label, socket, control)
|
|
241
|
+
constructor(node, name, label, socket, control, multiple=false)
|
|
243
242
|
{
|
|
244
243
|
super(node, name, label, socket);
|
|
245
244
|
/**
|
|
@@ -247,6 +246,11 @@ class NodeInput extends Connector
|
|
|
247
246
|
* @member {HTMLInputElement?}
|
|
248
247
|
*/
|
|
249
248
|
this.control = control;
|
|
249
|
+
/**
|
|
250
|
+
* Whether this input accepts multiple incoming connections
|
|
251
|
+
* @member {boolean}
|
|
252
|
+
*/
|
|
253
|
+
this.multiple = multiple;
|
|
250
254
|
}
|
|
251
255
|
|
|
252
256
|
build_dom()
|
|
@@ -270,7 +274,7 @@ class NodeInput extends Connector
|
|
|
270
274
|
|
|
271
275
|
connect(connection)
|
|
272
276
|
{
|
|
273
|
-
if ( !this.
|
|
277
|
+
if ( !this.multiple )
|
|
274
278
|
{
|
|
275
279
|
for ( let conn of this.connections )
|
|
276
280
|
conn.disconnect();
|
|
@@ -301,7 +305,7 @@ class NodeInput extends Connector
|
|
|
301
305
|
* If there's input connections, the value is based on those.
|
|
302
306
|
* Otherwise the value comes from the control.
|
|
303
307
|
*
|
|
304
|
-
* If there
|
|
308
|
+
* If there this connector accepts multiple inputs, the returned value
|
|
305
309
|
* is always an array.
|
|
306
310
|
*
|
|
307
311
|
* @return {any}
|
|
@@ -310,20 +314,20 @@ class NodeInput extends Connector
|
|
|
310
314
|
{
|
|
311
315
|
if ( this.connections.length )
|
|
312
316
|
{
|
|
313
|
-
if ( this.
|
|
317
|
+
if ( this.multiple )
|
|
314
318
|
return this.connections.map(c => c.output.get_value());
|
|
315
319
|
return this.connections[0].output.get_value();
|
|
316
320
|
}
|
|
317
321
|
|
|
318
322
|
if ( !this.control )
|
|
319
323
|
{
|
|
320
|
-
if ( this.socket && this.
|
|
324
|
+
if ( this.socket && this.multiple )
|
|
321
325
|
return [];
|
|
322
326
|
return null;
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
let value = this.control.get_value();
|
|
326
|
-
if ( this.socket && this.
|
|
330
|
+
if ( this.socket && this.multiple )
|
|
327
331
|
return [value];
|
|
328
332
|
return value;
|
|
329
333
|
}
|
|
@@ -512,6 +516,26 @@ class NodeControl
|
|
|
512
516
|
{
|
|
513
517
|
this.set_visibility("visible");
|
|
514
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
|
+
}
|
|
515
539
|
}
|
|
516
540
|
|
|
517
541
|
/**
|
|
@@ -547,6 +571,8 @@ class InputControl extends NodeControl
|
|
|
547
571
|
{
|
|
548
572
|
if ( this.config.type == "checkbox" )
|
|
549
573
|
return this.input.checked;
|
|
574
|
+
if ( this.config.type == "number" || this.config.type == "range" )
|
|
575
|
+
return Number(this.input.value);
|
|
550
576
|
return this.input.value;
|
|
551
577
|
}
|
|
552
578
|
|
|
@@ -648,7 +674,7 @@ class Node extends VisualElement
|
|
|
648
674
|
* Sets control values for inputs
|
|
649
675
|
* @param {object} data Object with input names as keys and associated values
|
|
650
676
|
*/
|
|
651
|
-
|
|
677
|
+
set_input_data(data)
|
|
652
678
|
{
|
|
653
679
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
654
680
|
{
|
|
@@ -661,7 +687,7 @@ class Node extends VisualElement
|
|
|
661
687
|
* Gets values from the inputs
|
|
662
688
|
* @returns {object} Object with input names as keys and associated values
|
|
663
689
|
*/
|
|
664
|
-
|
|
690
|
+
get_input_data()
|
|
665
691
|
{
|
|
666
692
|
let data = {};
|
|
667
693
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
@@ -695,10 +721,11 @@ class Node extends VisualElement
|
|
|
695
721
|
* @param {string} label User-visible label / title
|
|
696
722
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
697
723
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
724
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
698
725
|
*/
|
|
699
|
-
add_input(name, title, socket, control)
|
|
726
|
+
add_input(name, title, socket, control, multiple)
|
|
700
727
|
{
|
|
701
|
-
this.inputs[name] = new NodeInput(this, name, title, socket, control);
|
|
728
|
+
this.inputs[name] = new NodeInput(this, name, title, socket, control, multiple);
|
|
702
729
|
}
|
|
703
730
|
|
|
704
731
|
/**
|
|
@@ -765,7 +792,7 @@ class Node extends VisualElement
|
|
|
765
792
|
* Returns a list of all output connections
|
|
766
793
|
* @returns {Connection[]}
|
|
767
794
|
*/
|
|
768
|
-
|
|
795
|
+
output_connections()
|
|
769
796
|
{
|
|
770
797
|
return Object.values(this.outputs)
|
|
771
798
|
.map(c => c.connections)
|
|
@@ -851,35 +878,37 @@ class NodeType
|
|
|
851
878
|
class ContextMenu extends VisualElement
|
|
852
879
|
{
|
|
853
880
|
/**
|
|
881
|
+
* @param {Editor} editor
|
|
854
882
|
* @param {object|NodeType[]} actions If an object,
|
|
855
883
|
* keys will be items in the menu while values are the performed actions.
|
|
856
884
|
* Possible values are callbacks, NodeType instances (which will create new nodes),
|
|
857
885
|
* Or other actions definitions for submenus
|
|
858
886
|
*/
|
|
859
|
-
constructor(actions)
|
|
887
|
+
constructor(editor, actions)
|
|
860
888
|
{
|
|
861
889
|
super();
|
|
890
|
+
this.editor = editor;
|
|
862
891
|
this.actions = actions;
|
|
863
892
|
}
|
|
864
893
|
|
|
865
|
-
to_dom(
|
|
894
|
+
to_dom()
|
|
866
895
|
{
|
|
867
|
-
this.dom = this.build(
|
|
896
|
+
this.dom = this.build();
|
|
868
897
|
return this.dom;
|
|
869
898
|
}
|
|
870
899
|
|
|
871
|
-
build(
|
|
900
|
+
build()
|
|
872
901
|
{
|
|
873
|
-
let dom = this.make_menu("dgl-menu", this.actions
|
|
902
|
+
let dom = this.make_menu("dgl-menu", this.actions);
|
|
874
903
|
dom.style.display = "none";
|
|
875
904
|
return dom;
|
|
876
905
|
}
|
|
877
906
|
|
|
878
|
-
make_action(title, action
|
|
907
|
+
make_action(title, action)
|
|
879
908
|
{
|
|
880
909
|
let onclick = action;
|
|
881
910
|
if ( action instanceof NodeType )
|
|
882
|
-
onclick = () => editor.create_node(action);
|
|
911
|
+
onclick = () => this.editor.create_node(action);
|
|
883
912
|
|
|
884
913
|
let item = document.createElement("li");
|
|
885
914
|
item.classList.add("dgl-menu-item");
|
|
@@ -894,7 +923,7 @@ class ContextMenu extends VisualElement
|
|
|
894
923
|
return item;
|
|
895
924
|
}
|
|
896
925
|
|
|
897
|
-
make_menu(cls, actions
|
|
926
|
+
make_menu(cls, actions)
|
|
898
927
|
{
|
|
899
928
|
let dom = document.createElement("ul");
|
|
900
929
|
dom.classList.add(cls);
|
|
@@ -903,7 +932,7 @@ class ContextMenu extends VisualElement
|
|
|
903
932
|
{
|
|
904
933
|
for ( let action of actions )
|
|
905
934
|
{
|
|
906
|
-
dom.appendChild(this.make_action(action.name, action
|
|
935
|
+
dom.appendChild(this.make_action(action.name, action));
|
|
907
936
|
}
|
|
908
937
|
}
|
|
909
938
|
else
|
|
@@ -911,7 +940,7 @@ class ContextMenu extends VisualElement
|
|
|
911
940
|
for ( let [name, val] of Object.entries(actions) )
|
|
912
941
|
{
|
|
913
942
|
if ( Array.isArray(val) || (typeof val == "object" && !(val instanceof NodeType)) )
|
|
914
|
-
dom.appendChild(this.make_submenu(name, val
|
|
943
|
+
dom.appendChild(this.make_submenu(name, val));
|
|
915
944
|
else
|
|
916
945
|
dom.appendChild(this.make_action(name, val));
|
|
917
946
|
}
|
|
@@ -919,14 +948,14 @@ class ContextMenu extends VisualElement
|
|
|
919
948
|
return dom;
|
|
920
949
|
}
|
|
921
950
|
|
|
922
|
-
make_submenu(name, actions
|
|
951
|
+
make_submenu(name, actions)
|
|
923
952
|
{
|
|
924
953
|
let item = document.createElement("li");
|
|
925
954
|
item.classList.add("dgl-menu-item");
|
|
926
955
|
let label = item.appendChild(document.createElement("span"));
|
|
927
956
|
label.textContent = name;
|
|
928
957
|
label.classList.add("dgl-submenu-label");
|
|
929
|
-
item.appendChild(this.make_menu("dgl-submenu", actions
|
|
958
|
+
item.appendChild(this.make_menu("dgl-submenu", actions));
|
|
930
959
|
return item;
|
|
931
960
|
}
|
|
932
961
|
|
|
@@ -976,6 +1005,7 @@ class Editor
|
|
|
976
1005
|
constructor(container, context_menu={})
|
|
977
1006
|
{
|
|
978
1007
|
this.nodes = [];
|
|
1008
|
+
this.type_registry = {};
|
|
979
1009
|
|
|
980
1010
|
this.container = container;
|
|
981
1011
|
if ( !container.hasAttribute("tabindex") )
|
|
@@ -1014,23 +1044,141 @@ class Editor
|
|
|
1014
1044
|
this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
|
|
1015
1045
|
this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
|
|
1016
1046
|
|
|
1017
|
-
this.context_menu = new ContextMenu(context_menu);
|
|
1018
|
-
this.node_menu = new ContextMenu({
|
|
1047
|
+
this.context_menu = new ContextMenu(this, context_menu);
|
|
1048
|
+
this.node_menu = new ContextMenu(this, {
|
|
1019
1049
|
"Delete": () => {
|
|
1020
1050
|
if ( this.selected_node )
|
|
1021
1051
|
this.remove_node(this.selected_node);
|
|
1022
1052
|
},
|
|
1023
1053
|
"Duplicate": () => {
|
|
1024
1054
|
if ( this.selected_node ) {
|
|
1025
|
-
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());
|
|
1026
1056
|
this._select_node(node);
|
|
1027
1057
|
|
|
1028
1058
|
}
|
|
1029
1059
|
}
|
|
1030
1060
|
});
|
|
1031
1061
|
|
|
1032
|
-
this.container.appendChild(this.node_menu.to_dom(
|
|
1033
|
-
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);
|
|
1087
|
+
}
|
|
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();
|
|
1034
1182
|
}
|
|
1035
1183
|
|
|
1036
1184
|
/**
|
|
@@ -1039,8 +1187,6 @@ class Editor
|
|
|
1039
1187
|
*/
|
|
1040
1188
|
add_node(node)
|
|
1041
1189
|
{
|
|
1042
|
-
node.x = this.cursor_x;
|
|
1043
|
-
node.y = this.cursor_y;
|
|
1044
1190
|
this.nodes.push(node);
|
|
1045
1191
|
let node_dom = node.to_dom();
|
|
1046
1192
|
this.area.appendChild(node_dom);
|
|
@@ -1060,6 +1206,22 @@ class Editor
|
|
|
1060
1206
|
}
|
|
1061
1207
|
}
|
|
1062
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
|
+
|
|
1063
1225
|
/**
|
|
1064
1226
|
* Removes a node from the graph
|
|
1065
1227
|
* @param {Node} node
|
|
@@ -1086,8 +1248,10 @@ class Editor
|
|
|
1086
1248
|
create_node(type, data={})
|
|
1087
1249
|
{
|
|
1088
1250
|
let node = type.create_node();
|
|
1251
|
+
node.x = this.cursor_x;
|
|
1252
|
+
node.y = this.cursor_y;
|
|
1089
1253
|
this.add_node(node);
|
|
1090
|
-
node.
|
|
1254
|
+
node.set_input_data(data);
|
|
1091
1255
|
this._refresh_graph();
|
|
1092
1256
|
return node;
|
|
1093
1257
|
}
|
|
@@ -1192,6 +1356,17 @@ class Editor
|
|
|
1192
1356
|
this.fit_view();
|
|
1193
1357
|
}
|
|
1194
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
|
+
|
|
1195
1370
|
/**
|
|
1196
1371
|
* Pan and zoom to fit all the nodes
|
|
1197
1372
|
* @param {number} margin Margin to leave around the area
|
|
@@ -1199,28 +1374,38 @@ class Editor
|
|
|
1199
1374
|
fit_view(margin=20)
|
|
1200
1375
|
{
|
|
1201
1376
|
if ( this.nodes.length == 0 )
|
|
1377
|
+
{
|
|
1378
|
+
this.reset_view();
|
|
1202
1379
|
return;
|
|
1380
|
+
}
|
|
1203
1381
|
|
|
1204
1382
|
let left = Infinity;
|
|
1205
1383
|
let right = -Infinity;
|
|
1206
1384
|
let top = Infinity;
|
|
1207
1385
|
let bottom = -Infinity;
|
|
1208
1386
|
|
|
1387
|
+
let crect = this.container.getBoundingClientRect();
|
|
1388
|
+
|
|
1209
1389
|
for ( let node of this.nodes )
|
|
1210
1390
|
{
|
|
1211
|
-
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
|
+
|
|
1212
1399
|
if ( rect.left < left ) left = rect.left;
|
|
1213
1400
|
if ( rect.right > right ) right = rect.right;
|
|
1214
1401
|
if ( rect.top < top ) top = rect.top;
|
|
1215
1402
|
if ( rect.bottom > bottom ) bottom = rect.bottom;
|
|
1216
1403
|
}
|
|
1217
1404
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
top += -margin - crect.top;
|
|
1223
|
-
bottom += margin - crect.top;
|
|
1405
|
+
left += -margin;
|
|
1406
|
+
right += margin;
|
|
1407
|
+
top += -margin;
|
|
1408
|
+
bottom += margin;
|
|
1224
1409
|
|
|
1225
1410
|
let width = right - left;
|
|
1226
1411
|
let height = bottom - top;
|
|
@@ -1229,12 +1414,25 @@ class Editor
|
|
|
1229
1414
|
let scale = Math.min(x_scale, y_scale);
|
|
1230
1415
|
this.scale = scale;
|
|
1231
1416
|
|
|
1232
|
-
this.offset_x = (crect.width - width * scale) / 2 - left
|
|
1233
|
-
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;
|
|
1234
1419
|
this._apply_transform();
|
|
1235
1420
|
|
|
1236
1421
|
}
|
|
1237
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
|
+
|
|
1238
1436
|
/**
|
|
1239
1437
|
* Adds a connection to the graph
|
|
1240
1438
|
* @param {Node} output_node Node the connection is coming from
|
|
@@ -1245,13 +1443,9 @@ class Editor
|
|
|
1245
1443
|
*/
|
|
1246
1444
|
connect(output_node, output_name, input_node, input_name)
|
|
1247
1445
|
{
|
|
1248
|
-
let conn =
|
|
1446
|
+
let conn = this._create_connection(output_node, output_name, input_node, input_name)
|
|
1249
1447
|
if ( conn )
|
|
1250
1448
|
{
|
|
1251
|
-
conn.dom = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1252
|
-
conn.dom.classList.add("connection-from-" + to_slug(output_node.outputs[output_name].socket.name));
|
|
1253
|
-
conn.dom.classList.add("connection-to-" + to_slug(input_node.inputs[input_name].socket.name));
|
|
1254
|
-
this.connection_parent.appendChild(conn.dom);
|
|
1255
1449
|
this._update_connection(conn);
|
|
1256
1450
|
this._refresh_graph();
|
|
1257
1451
|
}
|
|
@@ -1375,6 +1569,14 @@ class Editor
|
|
|
1375
1569
|
this.area.style.transform = `translate(${this.offset_x}px, ${this.offset_y}px) scale(${this.scale})`;
|
|
1376
1570
|
}
|
|
1377
1571
|
|
|
1572
|
+
/**
|
|
1573
|
+
* Re-evaluates all the nodes
|
|
1574
|
+
*/
|
|
1575
|
+
update()
|
|
1576
|
+
{
|
|
1577
|
+
this._refresh_graph();
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1378
1580
|
_refresh_graph()
|
|
1379
1581
|
{
|
|
1380
1582
|
for ( let node of this.nodes )
|
package/src/dragon-graph-lib.css
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.dgl-socket:hover {
|
|
62
|
-
|
|
62
|
+
outline: 3px solid var(--dgl-node-text);
|
|
63
63
|
border-width: 10px;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -74,6 +74,8 @@
|
|
|
74
74
|
.dgl-connection {
|
|
75
75
|
overflow: visible;
|
|
76
76
|
position: absolute;
|
|
77
|
+
left: 0;
|
|
78
|
+
right: 0;
|
|
77
79
|
}
|
|
78
80
|
.dgl-connection path {
|
|
79
81
|
stroke: var(--dgl-connection-color);
|
|
@@ -81,10 +83,6 @@
|
|
|
81
83
|
fill: none;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
.dgl-preview {
|
|
85
|
-
max-width: 20ch;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
86
|
.dgl-title {
|
|
89
87
|
font-weight: bold;
|
|
90
88
|
border-bottom: 1px solid var(--dgl-node-text);
|
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
|
|
|
@@ -237,8 +235,9 @@ export class NodeInput extends Connector
|
|
|
237
235
|
* @param {string} label User-visible label / title
|
|
238
236
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
239
237
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
238
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
240
239
|
*/
|
|
241
|
-
constructor(node, name, label, socket, control)
|
|
240
|
+
constructor(node, name, label, socket, control, multiple=false)
|
|
242
241
|
{
|
|
243
242
|
super(node, name, label, socket);
|
|
244
243
|
/**
|
|
@@ -246,6 +245,11 @@ export class NodeInput extends Connector
|
|
|
246
245
|
* @member {HTMLInputElement?}
|
|
247
246
|
*/
|
|
248
247
|
this.control = control;
|
|
248
|
+
/**
|
|
249
|
+
* Whether this input accepts multiple incoming connections
|
|
250
|
+
* @member {boolean}
|
|
251
|
+
*/
|
|
252
|
+
this.multiple = multiple;
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
build_dom()
|
|
@@ -269,7 +273,7 @@ export class NodeInput extends Connector
|
|
|
269
273
|
|
|
270
274
|
connect(connection)
|
|
271
275
|
{
|
|
272
|
-
if ( !this.
|
|
276
|
+
if ( !this.multiple )
|
|
273
277
|
{
|
|
274
278
|
for ( let conn of this.connections )
|
|
275
279
|
conn.disconnect();
|
|
@@ -300,7 +304,7 @@ export class NodeInput extends Connector
|
|
|
300
304
|
* If there's input connections, the value is based on those.
|
|
301
305
|
* Otherwise the value comes from the control.
|
|
302
306
|
*
|
|
303
|
-
* If there
|
|
307
|
+
* If there this connector accepts multiple inputs, the returned value
|
|
304
308
|
* is always an array.
|
|
305
309
|
*
|
|
306
310
|
* @return {any}
|
|
@@ -309,20 +313,20 @@ export class NodeInput extends Connector
|
|
|
309
313
|
{
|
|
310
314
|
if ( this.connections.length )
|
|
311
315
|
{
|
|
312
|
-
if ( this.
|
|
316
|
+
if ( this.multiple )
|
|
313
317
|
return this.connections.map(c => c.output.get_value());
|
|
314
318
|
return this.connections[0].output.get_value();
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
if ( !this.control )
|
|
318
322
|
{
|
|
319
|
-
if ( this.socket && this.
|
|
323
|
+
if ( this.socket && this.multiple )
|
|
320
324
|
return [];
|
|
321
325
|
return null;
|
|
322
326
|
}
|
|
323
327
|
|
|
324
328
|
let value = this.control.get_value();
|
|
325
|
-
if ( this.socket && this.
|
|
329
|
+
if ( this.socket && this.multiple )
|
|
326
330
|
return [value];
|
|
327
331
|
return value;
|
|
328
332
|
}
|
|
@@ -511,6 +515,26 @@ export class NodeControl
|
|
|
511
515
|
{
|
|
512
516
|
this.set_visibility("visible");
|
|
513
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
|
+
}
|
|
514
538
|
}
|
|
515
539
|
|
|
516
540
|
/**
|
|
@@ -546,6 +570,8 @@ export class InputControl extends NodeControl
|
|
|
546
570
|
{
|
|
547
571
|
if ( this.config.type == "checkbox" )
|
|
548
572
|
return this.input.checked;
|
|
573
|
+
if ( this.config.type == "number" || this.config.type == "range" )
|
|
574
|
+
return Number(this.input.value);
|
|
549
575
|
return this.input.value;
|
|
550
576
|
}
|
|
551
577
|
|
|
@@ -647,7 +673,7 @@ export class Node extends VisualElement
|
|
|
647
673
|
* Sets control values for inputs
|
|
648
674
|
* @param {object} data Object with input names as keys and associated values
|
|
649
675
|
*/
|
|
650
|
-
|
|
676
|
+
set_input_data(data)
|
|
651
677
|
{
|
|
652
678
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
653
679
|
{
|
|
@@ -660,7 +686,7 @@ export class Node extends VisualElement
|
|
|
660
686
|
* Gets values from the inputs
|
|
661
687
|
* @returns {object} Object with input names as keys and associated values
|
|
662
688
|
*/
|
|
663
|
-
|
|
689
|
+
get_input_data()
|
|
664
690
|
{
|
|
665
691
|
let data = {};
|
|
666
692
|
for ( let [k, v] of Object.entries(this.inputs) )
|
|
@@ -694,10 +720,11 @@ export class Node extends VisualElement
|
|
|
694
720
|
* @param {string} label User-visible label / title
|
|
695
721
|
* @param {Socket?} socket Optional socket for incoming connectors
|
|
696
722
|
* @param {HTMLInputElement?} control Optional control to set the value without a connection
|
|
723
|
+
* @param {boolean} multiple Whether this input accepts multiple incoming connections
|
|
697
724
|
*/
|
|
698
|
-
add_input(name, title, socket, control)
|
|
725
|
+
add_input(name, title, socket, control, multiple)
|
|
699
726
|
{
|
|
700
|
-
this.inputs[name] = new NodeInput(this, name, title, socket, control);
|
|
727
|
+
this.inputs[name] = new NodeInput(this, name, title, socket, control, multiple);
|
|
701
728
|
}
|
|
702
729
|
|
|
703
730
|
/**
|
|
@@ -764,7 +791,7 @@ export class Node extends VisualElement
|
|
|
764
791
|
* Returns a list of all output connections
|
|
765
792
|
* @returns {Connection[]}
|
|
766
793
|
*/
|
|
767
|
-
|
|
794
|
+
output_connections()
|
|
768
795
|
{
|
|
769
796
|
return Object.values(this.outputs)
|
|
770
797
|
.map(c => c.connections)
|
|
@@ -850,35 +877,37 @@ export class NodeType
|
|
|
850
877
|
export class ContextMenu extends VisualElement
|
|
851
878
|
{
|
|
852
879
|
/**
|
|
880
|
+
* @param {Editor} editor
|
|
853
881
|
* @param {object|NodeType[]} actions If an object,
|
|
854
882
|
* keys will be items in the menu while values are the performed actions.
|
|
855
883
|
* Possible values are callbacks, NodeType instances (which will create new nodes),
|
|
856
884
|
* Or other actions definitions for submenus
|
|
857
885
|
*/
|
|
858
|
-
constructor(actions)
|
|
886
|
+
constructor(editor, actions)
|
|
859
887
|
{
|
|
860
888
|
super();
|
|
889
|
+
this.editor = editor;
|
|
861
890
|
this.actions = actions;
|
|
862
891
|
}
|
|
863
892
|
|
|
864
|
-
to_dom(
|
|
893
|
+
to_dom()
|
|
865
894
|
{
|
|
866
|
-
this.dom = this.build(
|
|
895
|
+
this.dom = this.build();
|
|
867
896
|
return this.dom;
|
|
868
897
|
}
|
|
869
898
|
|
|
870
|
-
build(
|
|
899
|
+
build()
|
|
871
900
|
{
|
|
872
|
-
let dom = this.make_menu("dgl-menu", this.actions
|
|
901
|
+
let dom = this.make_menu("dgl-menu", this.actions);
|
|
873
902
|
dom.style.display = "none";
|
|
874
903
|
return dom;
|
|
875
904
|
}
|
|
876
905
|
|
|
877
|
-
make_action(title, action
|
|
906
|
+
make_action(title, action)
|
|
878
907
|
{
|
|
879
908
|
let onclick = action;
|
|
880
909
|
if ( action instanceof NodeType )
|
|
881
|
-
onclick = () => editor.create_node(action);
|
|
910
|
+
onclick = () => this.editor.create_node(action);
|
|
882
911
|
|
|
883
912
|
let item = document.createElement("li");
|
|
884
913
|
item.classList.add("dgl-menu-item");
|
|
@@ -893,7 +922,7 @@ export class ContextMenu extends VisualElement
|
|
|
893
922
|
return item;
|
|
894
923
|
}
|
|
895
924
|
|
|
896
|
-
make_menu(cls, actions
|
|
925
|
+
make_menu(cls, actions)
|
|
897
926
|
{
|
|
898
927
|
let dom = document.createElement("ul");
|
|
899
928
|
dom.classList.add(cls);
|
|
@@ -902,7 +931,7 @@ export class ContextMenu extends VisualElement
|
|
|
902
931
|
{
|
|
903
932
|
for ( let action of actions )
|
|
904
933
|
{
|
|
905
|
-
dom.appendChild(this.make_action(action.name, action
|
|
934
|
+
dom.appendChild(this.make_action(action.name, action));
|
|
906
935
|
}
|
|
907
936
|
}
|
|
908
937
|
else
|
|
@@ -910,7 +939,7 @@ export class ContextMenu extends VisualElement
|
|
|
910
939
|
for ( let [name, val] of Object.entries(actions) )
|
|
911
940
|
{
|
|
912
941
|
if ( Array.isArray(val) || (typeof val == "object" && !(val instanceof NodeType)) )
|
|
913
|
-
dom.appendChild(this.make_submenu(name, val
|
|
942
|
+
dom.appendChild(this.make_submenu(name, val));
|
|
914
943
|
else
|
|
915
944
|
dom.appendChild(this.make_action(name, val));
|
|
916
945
|
}
|
|
@@ -918,14 +947,14 @@ export class ContextMenu extends VisualElement
|
|
|
918
947
|
return dom;
|
|
919
948
|
}
|
|
920
949
|
|
|
921
|
-
make_submenu(name, actions
|
|
950
|
+
make_submenu(name, actions)
|
|
922
951
|
{
|
|
923
952
|
let item = document.createElement("li");
|
|
924
953
|
item.classList.add("dgl-menu-item");
|
|
925
954
|
let label = item.appendChild(document.createElement("span"));
|
|
926
955
|
label.textContent = name;
|
|
927
956
|
label.classList.add("dgl-submenu-label");
|
|
928
|
-
item.appendChild(this.make_menu("dgl-submenu", actions
|
|
957
|
+
item.appendChild(this.make_menu("dgl-submenu", actions));
|
|
929
958
|
return item;
|
|
930
959
|
}
|
|
931
960
|
|
|
@@ -975,6 +1004,7 @@ export class Editor
|
|
|
975
1004
|
constructor(container, context_menu={})
|
|
976
1005
|
{
|
|
977
1006
|
this.nodes = [];
|
|
1007
|
+
this.type_registry = {};
|
|
978
1008
|
|
|
979
1009
|
this.container = container;
|
|
980
1010
|
if ( !container.hasAttribute("tabindex") )
|
|
@@ -1013,23 +1043,141 @@ export class Editor
|
|
|
1013
1043
|
this.container.addEventListener("wheel", this._on_area_wheel.bind(this));
|
|
1014
1044
|
this.container.addEventListener("contextmenu", this._on_area_menu.bind(this));
|
|
1015
1045
|
|
|
1016
|
-
this.context_menu = new ContextMenu(context_menu);
|
|
1017
|
-
this.node_menu = new ContextMenu({
|
|
1046
|
+
this.context_menu = new ContextMenu(this, context_menu);
|
|
1047
|
+
this.node_menu = new ContextMenu(this, {
|
|
1018
1048
|
"Delete": () => {
|
|
1019
1049
|
if ( this.selected_node )
|
|
1020
1050
|
this.remove_node(this.selected_node);
|
|
1021
1051
|
},
|
|
1022
1052
|
"Duplicate": () => {
|
|
1023
1053
|
if ( this.selected_node ) {
|
|
1024
|
-
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());
|
|
1025
1055
|
this._select_node(node);
|
|
1026
1056
|
|
|
1027
1057
|
}
|
|
1028
1058
|
}
|
|
1029
1059
|
});
|
|
1030
1060
|
|
|
1031
|
-
this.container.appendChild(this.node_menu.to_dom(
|
|
1032
|
-
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);
|
|
1086
|
+
}
|
|
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();
|
|
1033
1181
|
}
|
|
1034
1182
|
|
|
1035
1183
|
/**
|
|
@@ -1038,8 +1186,6 @@ export class Editor
|
|
|
1038
1186
|
*/
|
|
1039
1187
|
add_node(node)
|
|
1040
1188
|
{
|
|
1041
|
-
node.x = this.cursor_x;
|
|
1042
|
-
node.y = this.cursor_y;
|
|
1043
1189
|
this.nodes.push(node);
|
|
1044
1190
|
let node_dom = node.to_dom();
|
|
1045
1191
|
this.area.appendChild(node_dom);
|
|
@@ -1059,6 +1205,22 @@ export class Editor
|
|
|
1059
1205
|
}
|
|
1060
1206
|
}
|
|
1061
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
|
+
|
|
1062
1224
|
/**
|
|
1063
1225
|
* Removes a node from the graph
|
|
1064
1226
|
* @param {Node} node
|
|
@@ -1085,8 +1247,10 @@ export class Editor
|
|
|
1085
1247
|
create_node(type, data={})
|
|
1086
1248
|
{
|
|
1087
1249
|
let node = type.create_node();
|
|
1250
|
+
node.x = this.cursor_x;
|
|
1251
|
+
node.y = this.cursor_y;
|
|
1088
1252
|
this.add_node(node);
|
|
1089
|
-
node.
|
|
1253
|
+
node.set_input_data(data);
|
|
1090
1254
|
this._refresh_graph();
|
|
1091
1255
|
return node;
|
|
1092
1256
|
}
|
|
@@ -1191,6 +1355,17 @@ export class Editor
|
|
|
1191
1355
|
this.fit_view();
|
|
1192
1356
|
}
|
|
1193
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
|
+
|
|
1194
1369
|
/**
|
|
1195
1370
|
* Pan and zoom to fit all the nodes
|
|
1196
1371
|
* @param {number} margin Margin to leave around the area
|
|
@@ -1198,28 +1373,38 @@ export class Editor
|
|
|
1198
1373
|
fit_view(margin=20)
|
|
1199
1374
|
{
|
|
1200
1375
|
if ( this.nodes.length == 0 )
|
|
1376
|
+
{
|
|
1377
|
+
this.reset_view();
|
|
1201
1378
|
return;
|
|
1379
|
+
}
|
|
1202
1380
|
|
|
1203
1381
|
let left = Infinity;
|
|
1204
1382
|
let right = -Infinity;
|
|
1205
1383
|
let top = Infinity;
|
|
1206
1384
|
let bottom = -Infinity;
|
|
1207
1385
|
|
|
1386
|
+
let crect = this.container.getBoundingClientRect();
|
|
1387
|
+
|
|
1208
1388
|
for ( let node of this.nodes )
|
|
1209
1389
|
{
|
|
1210
|
-
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
|
+
|
|
1211
1398
|
if ( rect.left < left ) left = rect.left;
|
|
1212
1399
|
if ( rect.right > right ) right = rect.right;
|
|
1213
1400
|
if ( rect.top < top ) top = rect.top;
|
|
1214
1401
|
if ( rect.bottom > bottom ) bottom = rect.bottom;
|
|
1215
1402
|
}
|
|
1216
1403
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
top += -margin - crect.top;
|
|
1222
|
-
bottom += margin - crect.top;
|
|
1404
|
+
left += -margin;
|
|
1405
|
+
right += margin;
|
|
1406
|
+
top += -margin;
|
|
1407
|
+
bottom += margin;
|
|
1223
1408
|
|
|
1224
1409
|
let width = right - left;
|
|
1225
1410
|
let height = bottom - top;
|
|
@@ -1228,12 +1413,25 @@ export class Editor
|
|
|
1228
1413
|
let scale = Math.min(x_scale, y_scale);
|
|
1229
1414
|
this.scale = scale;
|
|
1230
1415
|
|
|
1231
|
-
this.offset_x = (crect.width - width * scale) / 2 - left
|
|
1232
|
-
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;
|
|
1233
1418
|
this._apply_transform();
|
|
1234
1419
|
|
|
1235
1420
|
}
|
|
1236
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
|
+
|
|
1237
1435
|
/**
|
|
1238
1436
|
* Adds a connection to the graph
|
|
1239
1437
|
* @param {Node} output_node Node the connection is coming from
|
|
@@ -1244,13 +1442,9 @@ export class Editor
|
|
|
1244
1442
|
*/
|
|
1245
1443
|
connect(output_node, output_name, input_node, input_name)
|
|
1246
1444
|
{
|
|
1247
|
-
let conn =
|
|
1445
|
+
let conn = this._create_connection(output_node, output_name, input_node, input_name)
|
|
1248
1446
|
if ( conn )
|
|
1249
1447
|
{
|
|
1250
|
-
conn.dom = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1251
|
-
conn.dom.classList.add("connection-from-" + to_slug(output_node.outputs[output_name].socket.name));
|
|
1252
|
-
conn.dom.classList.add("connection-to-" + to_slug(input_node.inputs[input_name].socket.name));
|
|
1253
|
-
this.connection_parent.appendChild(conn.dom);
|
|
1254
1448
|
this._update_connection(conn);
|
|
1255
1449
|
this._refresh_graph();
|
|
1256
1450
|
}
|
|
@@ -1374,6 +1568,14 @@ export class Editor
|
|
|
1374
1568
|
this.area.style.transform = `translate(${this.offset_x}px, ${this.offset_y}px) scale(${this.scale})`;
|
|
1375
1569
|
}
|
|
1376
1570
|
|
|
1571
|
+
/**
|
|
1572
|
+
* Re-evaluates all the nodes
|
|
1573
|
+
*/
|
|
1574
|
+
update()
|
|
1575
|
+
{
|
|
1576
|
+
this._refresh_graph();
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1377
1579
|
_refresh_graph()
|
|
1378
1580
|
{
|
|
1379
1581
|
for ( let node of this.nodes )
|