@dualbox/editor 1.0.74 → 1.0.76
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/js/dist/GraphEditor.js +232 -14
- package/js/dist/GraphEditor.min.js +231 -13
- package/js/src/c/GraphController.js +130 -2
- package/js/src/m/GraphModel.js +57 -6
- package/js/src/m/History.js +4 -0
- package/js/src/v/templates/graph.vue +22 -5
- package/package.json +1 -1
|
@@ -18063,6 +18063,10 @@ class History {
|
|
|
18063
18063
|
this._holdSaving = b;
|
|
18064
18064
|
}
|
|
18065
18065
|
|
|
18066
|
+
isHoldingSave() {
|
|
18067
|
+
return this._holdSaving;
|
|
18068
|
+
}
|
|
18069
|
+
|
|
18066
18070
|
// execute the callback and save the history as 1 change
|
|
18067
18071
|
batch(cb) {
|
|
18068
18072
|
let ret = this.ignore(cb);
|
|
@@ -43753,6 +43757,53 @@ class GraphModel {
|
|
|
43753
43757
|
return json;
|
|
43754
43758
|
}
|
|
43755
43759
|
|
|
43760
|
+
// ctrl+v
|
|
43761
|
+
paste(json) {
|
|
43762
|
+
var currentMetanode = this.getCurrentMetanode();
|
|
43763
|
+
|
|
43764
|
+
this.batch(() => {
|
|
43765
|
+
// paste metanodes
|
|
43766
|
+
lodash.each(json.metamodules, (mmDef, mmName) => {
|
|
43767
|
+
var metamodule = lodash.get(currentMetanode, ["metanodes", mmName]);
|
|
43768
|
+
if (metamodule === undefined) {
|
|
43769
|
+
lodash.set(currentMetanode, ["metanodes", mmName], mmDef);
|
|
43770
|
+
}
|
|
43771
|
+
});
|
|
43772
|
+
|
|
43773
|
+
// paste inputs
|
|
43774
|
+
lodash.each(json.input, (inputDef, inputName) => {
|
|
43775
|
+
var input = lodash.get(currentMetanode, ["input", inputName]);
|
|
43776
|
+
if (input === undefined) {
|
|
43777
|
+
lodash.set(currentMetanode, ["input", inputName], inputDef);
|
|
43778
|
+
}
|
|
43779
|
+
});
|
|
43780
|
+
|
|
43781
|
+
// paste outputs
|
|
43782
|
+
lodash.each(json.output, (outputDef, outputName) => {
|
|
43783
|
+
var output = lodash.get(currentMetanode, ["output", outputName]);
|
|
43784
|
+
if (output === undefined) {
|
|
43785
|
+
lodash.set(currentMetanode, ["output", outputName], outputDef);
|
|
43786
|
+
}
|
|
43787
|
+
});
|
|
43788
|
+
|
|
43789
|
+
// paste modules
|
|
43790
|
+
lodash.each(json.modules, (moduleDef, moduleName) => {
|
|
43791
|
+
var node = lodash.get(currentMetanode, ["modules", moduleName]);
|
|
43792
|
+
if (node === undefined) {
|
|
43793
|
+
lodash.set(currentMetanode, ["modules", moduleName], moduleDef);
|
|
43794
|
+
}
|
|
43795
|
+
});
|
|
43796
|
+
|
|
43797
|
+
// paste uis
|
|
43798
|
+
lodash.each(json.ui, (uiDef, uiName) => {
|
|
43799
|
+
var ui = lodash.get(currentMetanode, ["ui", uiName]);
|
|
43800
|
+
if (ui === undefined) {
|
|
43801
|
+
lodash.set(currentMetanode, ["ui", uiName], uiDef);
|
|
43802
|
+
}
|
|
43803
|
+
});
|
|
43804
|
+
});
|
|
43805
|
+
}
|
|
43806
|
+
|
|
43756
43807
|
/**
|
|
43757
43808
|
* Json security methods
|
|
43758
43809
|
*/
|
|
@@ -44138,12 +44189,12 @@ class GraphNode {
|
|
|
44138
44189
|
this.graphId = id.substring(4);
|
|
44139
44190
|
this.def = this.app.output[this.graphId];
|
|
44140
44191
|
} else if (this.app.modules && this.app.modules[id]) {
|
|
44141
|
-
this.graphId = id;
|
|
44142
44192
|
this.type = "module";
|
|
44193
|
+
this.graphId = id;
|
|
44143
44194
|
this.def = this.app.modules[this.graphId];
|
|
44144
44195
|
} else if (this.app.ui && this.app.ui[id]) {
|
|
44145
|
-
this.graphId = id;
|
|
44146
44196
|
this.type = "ui";
|
|
44197
|
+
this.graphId = id;
|
|
44147
44198
|
this.def = this.app.ui[this.graphId];
|
|
44148
44199
|
} else {
|
|
44149
44200
|
throw "Cant find node " + id;
|
|
@@ -45285,10 +45336,14 @@ class GraphNode {
|
|
|
45285
45336
|
|
|
45286
45337
|
// remove from current app
|
|
45287
45338
|
remove() {
|
|
45288
|
-
this.m.history.
|
|
45289
|
-
|
|
45290
|
-
|
|
45291
|
-
|
|
45339
|
+
if (this.m.history.isHoldingSave()) {
|
|
45340
|
+
this.removeFrom(this.app);
|
|
45341
|
+
} else {
|
|
45342
|
+
this.m.history.holdSaving(true);
|
|
45343
|
+
this.removeFrom(this.app);
|
|
45344
|
+
this.m.history.holdSaving(false);
|
|
45345
|
+
this.m.save();
|
|
45346
|
+
}
|
|
45292
45347
|
}
|
|
45293
45348
|
|
|
45294
45349
|
rename(newId) {
|
|
@@ -48499,8 +48554,17 @@ var script$1 = {
|
|
|
48499
48554
|
else if((e.which == 89 || e.keyCode == 89) && e.ctrlKey) { // ctrl + y
|
|
48500
48555
|
self.view.c.redo();
|
|
48501
48556
|
}
|
|
48502
|
-
else if((e.which == 46 || e.keyCode == 46)) {
|
|
48503
|
-
|
|
48557
|
+
else if((e.which == 46 || e.keyCode == 46)) { // suppr
|
|
48558
|
+
self.view.c.deleteSelection();
|
|
48559
|
+
}
|
|
48560
|
+
else if((e.which == 67 || e.keyCode == 67) && e.ctrlKey) { // ctrl + c
|
|
48561
|
+
self.view.c.copySelection();
|
|
48562
|
+
}
|
|
48563
|
+
else if((e.which == 86 || e.keyCode == 86) && e.ctrlKey) { // ctrl + v
|
|
48564
|
+
self.view.c.pasteSelection();
|
|
48565
|
+
}
|
|
48566
|
+
else if((e.which == 88 || e.keyCode == 88) && e.ctrlKey) { // ctrl + x
|
|
48567
|
+
self.view.c.copySelection();
|
|
48504
48568
|
self.view.c.deleteSelection();
|
|
48505
48569
|
}
|
|
48506
48570
|
}
|
|
@@ -48626,6 +48690,14 @@ var script$1 = {
|
|
|
48626
48690
|
this.view.c.deleteSelection();
|
|
48627
48691
|
},
|
|
48628
48692
|
|
|
48693
|
+
copySelection() {
|
|
48694
|
+
this.view.c.copySelection();
|
|
48695
|
+
},
|
|
48696
|
+
|
|
48697
|
+
pasteSelection() {
|
|
48698
|
+
this.view.c.pasteSelection();
|
|
48699
|
+
},
|
|
48700
|
+
|
|
48629
48701
|
getCurrentMousePosition(e) {
|
|
48630
48702
|
var x = e.pageX - $(this.$el).find('#dualbox-graph-canvas').offset().left;
|
|
48631
48703
|
var y = e.pageY - $(this.$el).find('#dualbox-graph-canvas').offset().top;
|
|
@@ -48668,7 +48740,7 @@ var script$1 = {
|
|
|
48668
48740
|
|
|
48669
48741
|
// show selected nodes
|
|
48670
48742
|
this.selector.onSelecting((divs) => {
|
|
48671
|
-
$(this.
|
|
48743
|
+
$(this.canvas).find('.card').removeClass('selected');
|
|
48672
48744
|
lodash.each(divs, (div) => {
|
|
48673
48745
|
$(div).addClass('selected');
|
|
48674
48746
|
});
|
|
@@ -48956,7 +49028,7 @@ var script$1 = {
|
|
|
48956
49028
|
id: id,
|
|
48957
49029
|
class: "connection-control capture-left-click capture-right-click"
|
|
48958
49030
|
});
|
|
48959
|
-
$(this.
|
|
49031
|
+
$(this.canvas).append(div);
|
|
48960
49032
|
|
|
48961
49033
|
// position it at pos (adjust for the height of the div)
|
|
48962
49034
|
var jsPlumbElement = this.jsPlumbInstance.getElement(id);
|
|
@@ -49241,6 +49313,24 @@ var __vue_render__$1 = function() {
|
|
|
49241
49313
|
return null
|
|
49242
49314
|
}
|
|
49243
49315
|
return _vm.deleteSelection()
|
|
49316
|
+
},
|
|
49317
|
+
function($event) {
|
|
49318
|
+
if (!$event.type.indexOf("key") && $event.keyCode !== 67) {
|
|
49319
|
+
return null
|
|
49320
|
+
}
|
|
49321
|
+
if (!$event.ctrlKey) {
|
|
49322
|
+
return null
|
|
49323
|
+
}
|
|
49324
|
+
return _vm.copySelection()
|
|
49325
|
+
},
|
|
49326
|
+
function($event) {
|
|
49327
|
+
if (!$event.type.indexOf("key") && $event.keyCode !== 86) {
|
|
49328
|
+
return null
|
|
49329
|
+
}
|
|
49330
|
+
if (!$event.ctrlKey) {
|
|
49331
|
+
return null
|
|
49332
|
+
}
|
|
49333
|
+
return _vm.pasteSelection()
|
|
49244
49334
|
}
|
|
49245
49335
|
]
|
|
49246
49336
|
}
|
|
@@ -49390,7 +49480,7 @@ __vue_render__$1._withStripped = true;
|
|
|
49390
49480
|
/* style */
|
|
49391
49481
|
const __vue_inject_styles__$1 = function (inject) {
|
|
49392
49482
|
if (!inject) return
|
|
49393
|
-
inject("data-v-82f3e0bc_0", { source: "\n.dualbox-graph-container {\n padding: 0px 0px 30px 0px;\n margin-left: -25px;\n margin-right: -25px;\n height: 100%;\n width: calc(100% + 50px);\n overflow: hidden;\n transform: translateZ(0); /* chrome hack to avoid glitches */\n}\n.dualbox-graph-container.translating {\n cursor: move;\n}\n.dualbox-graph-container span {\n pointer-events: none;\n}\n.dualbox-graph-container:focus {\n outline: none;\n}\n.dualbox-graph-canvas {\n height: 100%;\n width: 100%;\n background-color: #eee;\n position: relative;\n outline-color: transparent!important;\n min-height: 100%;\n min-width: 100%;\n border-radius: 5px;\n background-color: #ECF2F8;\n border: 1px solid #2e6da4;\n display: inline-block; /* fix padding pb: https://stackoverflow.com/questions/10054870/when-a-child-element-overflows-horizontally-why-is-the-right-padding-of-the-par */\n margin-bottom: -7px;\n}\n\n/* ex-plumb.css */\n.dualbox-graph-canvas .marker-color-tag {\n height: 8px;\n margin: 0 3px 3px 0;\n padding: 0;\n width: 42px;\n line-height: 75pt;\n border-radius: 3px;\n display: block;\n width: 100%;\n}\n.dualbox-graph-canvas .jtk-connector {\n z-index: 4;\n}\n.dualbox-graph-canvas .jtk-endpoint {\n z-index: 30;\n}\n.dualbox-graph-canvas .inoutEndpointLabel, .endpointSourceLabel {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n z-index: 2000;\n transform:translate(0%,0%) !important; /* this shit is just to prevent jsplumb of centering the box on the endpoint... */\n}\n.dualbox-graph-canvas .descSpan {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n position: fixed;\n display:none;\n z-index:2010;\n}\n.dualbox-graph-canvas .aLabel {\n background-color: white;\n padding: 0.4em;\n font: 12px sans-serif;\n color: #444;\n z-index: 21;\n border: 1px dotted gray;\n opacity: 0.8;\n cursor: pointer;\n}\n.dualbox-graph-canvas .aLabel.jtk-hover {\n background-color: #5C96BC;\n color: white;\n border: 1px solid white;\n}\n.card.jtk-connected {\n /*border: 1px solid #a1a1a1; change border when connected to spot isolated elements as disabled*/\n}\n.jtk-drag {\n opacity:0.7 !important;\n}\npath, .jtk-endpoint {\n cursor: pointer;\n}\n.jtk-overlay {\n background-color:transparent;\n}\n", map: {"version":3,"sources":["/home/seb/dev/dualbox/editor/js/src/v/templates/graph.vue"],"names":[],"mappings":";AACA;IACA,yBAAA;IACA,kBAAA;IACA,mBAAA;IACA,YAAA;IACA,wBAAA;IACA,gBAAA;IACA,wBAAA,EAAA,kCAAA;AACA;AAEA;IACA,YAAA;AACA;AAEA;IACA,oBAAA;AACA;AAEA;IACA,aAAA;AACA;AAEA;IACA,YAAA;IACA,WAAA;IACA,sBAAA;IACA,kBAAA;IACA,oCAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;IACA,yBAAA;IACA,yBAAA;IACA,qBAAA,EAAA,iJAAA;IACA,mBAAA;AACA;;AAEA,iBAAA;AACA;IACA,WAAA;IACA,mBAAA;IACA,UAAA;IACA,WAAA;IACA,iBAAA;IACA,kBAAA;IACA,cAAA;IACA,WAAA;AACA;AAEA;IACA,UAAA;AACA;AAEA;IACA,WAAA;AACA;AACA;IACA,+BAAA;IACA,kCAAA;IACA,uCAAA;IACA,oCAAA;IACA,aAAA;IACA,qCAAA,EAAA,iFAAA;AACA;AACA;IACA,+BAAA;IACA,kCAAA;IACA,uCAAA;IACA,oCAAA;IACA,eAAA;IACA,YAAA;IACA,YAAA;AACA;AAEA;IACA,uBAAA;IACA,cAAA;IACA,qBAAA;IACA,WAAA;IACA,WAAA;IACA,uBAAA;IACA,YAAA;IACA,eAAA;AACA;AAEA;IACA,yBAAA;IACA,YAAA;IACA,uBAAA;AACA;AAEA;IACA,gGAAA;AACA;AAEA;IACA,sBAAA;AACA;AAEA;IACA,eAAA;AACA;AAEA;IACA,4BAAA;AACA","file":"graph.vue","sourcesContent":["<style>\n .dualbox-graph-container {\n padding: 0px 0px 30px 0px;\n margin-left: -25px;\n margin-right: -25px;\n height: 100%;\n width: calc(100% + 50px);\n overflow: hidden;\n transform: translateZ(0); /* chrome hack to avoid glitches */\n }\n\n .dualbox-graph-container.translating {\n cursor: move;\n }\n\n .dualbox-graph-container span {\n pointer-events: none;\n }\n\n .dualbox-graph-container:focus {\n outline: none;\n }\n\n .dualbox-graph-canvas {\n height: 100%;\n width: 100%;\n background-color: #eee;\n position: relative;\n outline-color: transparent!important;\n min-height: 100%;\n min-width: 100%;\n border-radius: 5px;\n background-color: #ECF2F8;\n border: 1px solid #2e6da4;\n display: inline-block; /* fix padding pb: https://stackoverflow.com/questions/10054870/when-a-child-element-overflows-horizontally-why-is-the-right-padding-of-the-par */\n margin-bottom: -7px;\n }\n\n /* ex-plumb.css */\n .dualbox-graph-canvas .marker-color-tag {\n height: 8px;\n margin: 0 3px 3px 0;\n padding: 0;\n width: 42px;\n line-height: 75pt;\n border-radius: 3px;\n display: block;\n width: 100%;\n }\n\n .dualbox-graph-canvas .jtk-connector {\n z-index: 4;\n }\n\n .dualbox-graph-canvas .jtk-endpoint {\n z-index: 30;\n }\n .dualbox-graph-canvas .inoutEndpointLabel, .endpointSourceLabel {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n z-index: 2000;\n transform:translate(0%,0%) !important; /* this shit is just to prevent jsplumb of centering the box on the endpoint... */\n }\n .dualbox-graph-canvas .descSpan {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n position: fixed;\n display:none;\n z-index:2010;\n }\n\n .dualbox-graph-canvas .aLabel {\n background-color: white;\n padding: 0.4em;\n font: 12px sans-serif;\n color: #444;\n z-index: 21;\n border: 1px dotted gray;\n opacity: 0.8;\n cursor: pointer;\n }\n\n .dualbox-graph-canvas .aLabel.jtk-hover {\n background-color: #5C96BC;\n color: white;\n border: 1px solid white;\n }\n\n .card.jtk-connected {\n /*border: 1px solid #a1a1a1; change border when connected to spot isolated elements as disabled*/\n }\n\n .jtk-drag {\n opacity:0.7 !important;\n }\n\n path, .jtk-endpoint {\n cursor: pointer;\n }\n\n .jtk-overlay {\n background-color:transparent;\n }\n</style>\n\n<template>\n <div class=\"grid-bg dualbox-graph-container dark\" @keyup.ctrl.89=\"redo()\" @keyup.ctrl.90=\"undo()\" @keyup.ctrl.46=\"deleteSelection()\" tabindex=1>\n <div id=\"dualbox-graph-canvas\" class=\"canvas-wide jtk-surface jtk-surface-nopan contextmenu dualbox-graph-canvas\">\n <graph-node v-for=\"(def, id) in app.input\" :key=\"getNodeUniqId(id, 'input')\" :id=\"getNodeId(id, 'input')\" :pkg=\"getNode(id, 'input').getPackage()\" :n=\"getNode(id, 'input')\" :ref=\"getNodeId(id, 'input')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.output\" :key=\"getNodeUniqId(id, 'output')\" :id=\"getNodeId(id, 'output')\" :pkg=\"getNode(id, 'output').getPackage()\" :n=\"getNode(id, 'output')\" :ref=\"getNodeId(id, 'output')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.modules\" :key=\"getNodeUniqId(id, 'module')\" :id=\"getNodeId(id, 'module')\" :pkg=\"getNode(id, 'module').getPackage()\" :n=\"getNode(id, 'module')\" :ref=\"getNodeId(id, 'module')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.ui\" :key=\"getNodeUniqId(id, 'ui')\" :id=\"getNodeId(id, 'ui')\" :pkg=\"getNode(id, 'ui').getPackage()\" :n=\"getNode(id, 'ui')\" :ref=\"getNodeId(id, 'ui')\" :displayEvents=\"displayEvents\"></graph-node>\n </div>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-topleft\" style=\"position: absolute; top: 0; left: 0px; border-radius: 0px; border-top-left-radius: 5px; margin: 0;\" v-on:click=\"gotoTopLeft\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-topright\" style=\"position: absolute; top: 0; right: 0px; border-radius: 0px; border-top-right-radius: 5px; margin: 0;\" v-on:click=\"gotoTopRight\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-bottomleft\" style=\"position: absolute; bottom: 30px; left: 0px; border-radius: 0px; border-bottom-left-radius: 5px; margin: 0;\" v-on:click=\"gotoBottomLeft\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-bottomright\" style=\"position: absolute; bottom: 30px; right: 0px; border-radius: 0px; border-bottom-right-radius: 5px; margin: 0;\" v-on:click=\"gotoBottomRight\"><i class=\"fa fa-arrows-alt\"></i></button>\n </div>\n</template>\n\n<script>\n// Utils\nimport _ from 'lodash-es';\nimport dbutils from '../../m/DualboxUtils';\nimport Utils from '../Utils';\nimport swal from 'sweetalert2';\n\n// Sub-components\nimport graphNodeVue from './graphNode.vue';\n\n// Canvas control helpers\nimport Zoomer from '../Zoomer';\nimport Selector from '../Selector';\nimport Translater from '../Translater';\nimport CanvasSizeHandler from '../CanvasSizeHandler';\nimport ContextMenu from '../ContextMenu';\n\n// jsplumb style definitins\nimport PlumbStyle from '../PlumbStyle';\n\nexport default {\n props: [\n \"app\", // the app model\n \"displayEvents\" // display events or not\n ],\n components: {\n 'graph-node' : graphNodeVue,\n },\n data: function () {\n return {\n utils: Utils,\n loading: false,\n }\n },\n created: function() {\n this.view = window.dualboxEditor.v;\n this.jsPlumbInstance = null;\n\n // Controls helpers utilities\n this.selector = null;\n this.translater = null;\n this.zoomer = null;\n this.canvasSizeHandler = null;\n\n // encapsulate the JSPlumb styles\n this.style = new PlumbStyle(this);\n },\n mounted: async function() {\n this.jsPlumbInstance = jsPlumb.getInstance({\n DragOptions: { cursor: 'pointer', zIndex: 2500 }, // default drag options\n Container: \"dualbox-graph-canvas\"\n });\n this.jsPlumbInstance.rand = dbutils.randomString(8);\n this.style.setDefault();\n this.graphContainer = $(this.$el);\n this.canvas = $(this.$el).find('.dualbox-graph-canvas');\n\n // TODO: remove theses definitions from the graphview object (bugs when entering metanode)\n this.view.selector = this.selector = new Selector(this, this.graphContainer);\n this.view.translater = this.translater = new Translater(this, this.graphContainer, this.canvas);\n this.view.zoomer = this.zoomer = new Zoomer(this, this.graphContainer, this.canvas);\n this.view.canvasSizeHandler = this.canvasSizeHandler = new CanvasSizeHandler(this, this.canvas);\n this.assignContextMenu();\n\n // autofocus editor when the div is clicked (allow to use keydown)\n // We use this way because it's easier than to set a tabindex on the dualbox-graph-container\n // to capture keyboard events (will break modals)\n var self = this;\n this.keyDownHandler = function(e) {\n // Ensure event is not null\n e = e || window.event;\n\n if( $(self.$el).is(\":hover\") ) {\n if ((e.which == 90 || e.keyCode == 90) && e.ctrlKey) { // ctrl + z\n self.view.c.undo();\n }\n else if((e.which == 89 || e.keyCode == 89) && e.ctrlKey) { // ctrl + y\n self.view.c.redo();\n }\n else if((e.which == 46 || e.keyCode == 46)) {\n // suppr: delete selection\n self.view.c.deleteSelection();\n }\n }\n }\n\n $(document).on(\"keydown\", this.keyDownHandler);\n return await this.initializeListeners();\n },\n destroyed: function() {\n $(document).off(\"keydown\", this.keyDownHandler);\n },\n beforeUpdate: async function() {\n // Reset the JSPlumb instance\n this.resetJsPlumb();\n this.refreshBoxes();\n },\n updated: async function() {\n // don't do anything for out-of-date updates (hash is not matching)\n console.log('Updating graph !');\n\n //this.canvas.empty();\n ContextMenu.clean();\n this.assignContextMenu();\n\n // We need to add links and events to the nodes\n\n // Function to gather all links from a node into the \"links\" array\n var links = []; // array of links\n var gatherLinks = (targetId, sourceDef) => {\n // for nodes and uis\n _.each(sourceDef.links, (sources, targetInput) => {\n _.each(sources, (sourceOutput, sourceId) => {\n if( sourceId === \"graph\" ) {\n return;\n }\n\n if( sourceId === \"input\" || sourceId === \"output\" ) {\n sourceId = this.view.m.prefixId(sourceOutput, sourceId);\n sourceOutput = \"value\";\n }\n\n links.push({\n sourceId: sourceId,\n sourceOutput: sourceOutput,\n targetId: targetId,\n targetInput: targetInput\n });\n });\n });\n\n // for inputs and outputs\n _.each(sourceDef.link, (sourceOutput, sourceId) => {\n if( sourceId === \"graph\" ) return;\n if( sourceId === \"input\" || sourceId === \"output\" ) {\n sourceId = this.view.m.prefixId(sourceOutput, sourceId);\n sourceOutput = \"value\";\n }\n\n links.push({\n sourceId: sourceId,\n sourceOutput: sourceOutput,\n targetId: targetId,\n targetInput: \"value\"\n });\n });\n }\n\n // Function to gather all events from a node into the \"links\" array\n var events = [];\n var gatherEvents = (sourceId, def) => {\n _.each(def.events, (evt) => {\n if( evt.node ) {\n events.push({\n sourceId: sourceId,\n targetId: evt.node,\n name: evt.event\n });\n }\n });\n }\n\n _.each(this.app.input, (def, id) => {\n id = this.view.m.prefixId(id, \"input\");\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.output, (def, id) => {\n id = this.view.m.prefixId(id, \"output\");\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.modules, (def, id) =>{\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.ui, (def, id) =>{\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n\n // Now, we gathered all links and events. Add them into jsPlumb\n // add the links\n _.each(links, (link) => {\n this.addLink(link.sourceId, link.sourceOutput, link.targetId, link.targetInput);\n });\n\n // add the events\n if( this.displayEvents ) {\n _.each(events, (e) => {\n this.addEvent(e.sourceId, e.targetId, e.name);\n });\n }\n\n await this.canvasSizeHandler.resize();\n\n // Initialize all listeners again\n await this.initializeJsPlumbListeners();\n\n return new Promise((resolve) => this.jsPlumbInstance.ready(resolve));\n },\n methods: {\n deleteSelection() {\n this.view.c.deleteSelection();\n },\n\n getCurrentMousePosition(e) {\n var x = e.pageX - $(this.$el).find('#dualbox-graph-canvas').offset().left;\n var y = e.pageY - $(this.$el).find('#dualbox-graph-canvas').offset().top;\n x = x / this.zoomer.getZoom();\n y = y / this.zoomer.getZoom();\n return {\n \"top\": y,\n \"left\": x\n };\n },\n\n resetJsPlumb : function() {\n if( this.jsPlumbInstance ) {\n this.jsPlumbInstance.reset();\n delete this.jsPlumbInstance.initializedNodes;\n }\n\n window.jsPlumbInstance = this.jsPlumbInstance = jsPlumb.getInstance({\n DragOptions: { cursor: 'pointer', zIndex: 2500 }, // default drag options\n Container: \"dualbox-graph-canvas\"\n });\n\n this.jsPlumbInstance.rand = dbutils.randomString(8); // just change the node refs to avoid vue.js optimizations\n this.style.setDefault();\n },\n\n refreshBoxes: function() {\n _.each(this.$refs, (component, name) => {\n var c = component[0];\n if(c) c.$forceUpdate();\n })\n },\n\n // bind jsPlumb events\n initializeListeners: async function() {\n this.selector.initialize();\n this.zoomer.initialize();\n ContextMenu.initialize(); // important to make this after the translater (right-clic conflicts)\n this.translater.initialize();\n\n // show selected nodes\n this.selector.onSelecting((divs) => {\n $(this.view.canvas).find('.card').removeClass('selected');\n _.each(divs, (div) => {\n $(div).addClass('selected');\n });\n });\n\n // Add every node in the selected area to JsPlumb selection\n this.selector.onSelect((divs) => {\n _.each(divs, (div) => {\n $(div).addClass('selected');\n this.jsPlumbInstance.addToDragSelection($(div)[0]);\n });\n });\n\n // remove the selection\n this.selector.onDeselect((divs) => {\n _.each(divs, (div) => {\n $(div).removeClass('selected');\n this.jsPlumbInstance.removeFromDragSelection($(div)[0]);\n });\n });\n\n return await this.initializeJsPlumbListeners();\n },\n\n initializeJsPlumbListeners: function() {\n this.refreshGraphNode = function(id) {\n var nodeVue = this.$refs[id];\n if( nodeVue[0] ) nodeVue[0].$forceUpdate();\n }\n\n this.jsPlumbInstance.bind(\"connectionDrag\", (connection) => {\n // hide the type of the source (output endpoint)\n var endpoints = connection.getAttachedElements();\n if( endpoints && endpoints[0] ) this.style.hideType(endpoints[0]);\n\n // if the connection linked a source to a target, remove the connection\n var c = connection.getParameters();\n if( c.target ) {\n if( c.type === \"data\" ) {\n // remove the link\n this.view.m.removeDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else if( c.type === \"event\" ) {\n this.view.m.removeEventLink(c.source.id, c.target.id, c.event);\n this.view.setBoxMenu(c.source.id);\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connectionDragStop\", (connection) => {\n // if the connection has not been made, show back the type of the source\n var endpoints = connection.getAttachedElements();\n if( endpoints && endpoints.length == 2 ) {\n var sourceEP = endpoints[0];\n var targetEP = endpoints[1];\n if( targetEP && targetEP.elementId.startsWith('jsPlumb')) {\n if( targetEP.elementId.startsWith('jsPlumb') ) {\n // no connection has been made, show back our output type\n var sourceOutput = connection.getParameters().source.output;\n if( !this.view.m.getNode(sourceEP.elementId).isOutputConnected(sourceOutput) ) {\n this.style.showType(sourceEP);\n }\n }\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connection\", (info, event) => {\n // only do this for connection established by user, no programmatically\n if( event ) {\n var c = info.connection.getParameters();\n if( c.type === \"data\" ) {\n // check the types\n var sourceType = this.view.m.getNode(c.source.id).getOutputType(c.source.output);\n var targetType = this.view.m.getNode(c.target.id).getInputType(c.target.input);\n if( this.view.m.compareTypes(sourceType, targetType) ) {\n this.view.m.addDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n\n // the target need to be refreshed\n this.refreshGraphNode(c.target.id);\n\n // hide source and target types\n this.style.hideType(info.sourceEndpoint);\n this.style.hideType(info.targetEndpoint);\n }\n else {\n // Error\n this.jsPlumbInstance.deleteConnection(info.connection, {\n fireEvent: false, //fire a connection detached event?\n forceDetach: false //override any beforeDetach listeners\n });\n this.style.showType(info.targetEndpoint);\n if( !this.view.m.getNode(info.sourceId).isOutputConnected(c.source.output) ) {\n this.style.showType(info.sourceEndpoint);\n }\n\n swal({\n title: \"Nope.\",\n html: \"<div style='text-align: left;'>Output <b style='font-weight: bold;'>\" + c.source.output + \"</b> of <b style='font-weight: bold;'>\" + info.sourceId + \"</b> is of type <u>\" + Utils.htmlentities(sourceType) + \"</u><br/>Input <b style='font-weight: bold;'>\" + c.target.input + \"</b> of <b style='font-weight: bold;'>\" + info.targetId + \"</b> is of type <u>\" + Utils.htmlentities(targetType) + \"</u>.</div><br/>Type mismatch.\",\n type: \"error\",\n });\n }\n }\n else if( c.type === \"event\" ) {\n var target = this.view.m.getNode(c.target.id);\n var eventNames = target.getEventsNames();\n var options = {};\n _.each(eventNames, (name) => options[name] = name );\n\n swal({\n input: 'select',\n title: 'Select an event',\n inputOptions: options\n }).then( (result) => {\n if( result.value ) {\n info.connection.setParameter('event', result.value);\n info.connection.setLabel({ label: result.value, cssClass: \"connection-label noselect\", location: 0.5 });\n this.view.m.addEventLink(c.source.id, c.target.id, result.value);\n\n this.view.setBoxMenu(c.source.id);\n }\n });\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connectionDetached\", (info, event) => {\n var c = info.connection.getParameters();\n if( c.type === \"data\" ) {\n // remove the link\n this.view.m.removeDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n\n // show source and target type\n this.style.showType(info.targetEndpoint);\n if( !info.source.id.startsWith('connection-split') &&\n !this.view.m.getNode(info.source.id).isOutputConnected(c.source.output) ) {\n this.style.showType(info.sourceEndpoint);\n }\n\n // the target need to be refreshed\n this.refreshGraphNode(c.target.id);\n }\n else if( c.type === \"event\" ) {\n this.view.m.removeEventLink(c.source.id, c.target.id, c.event);\n //this.style.showEventName(info.targetEndpoint);\n }\n\n // if the connection is splitted, remove all other connections/endpoint\n if( c.splitted ) {\n var connections = this.jsPlumbInstance.getAllConnections();\n var connToDelete = [];\n var EPToDelete = {};\n _.each(connections, conn => {\n var p = conn.getParameters();\n if( p.source.id === c.source.id &&\n p.target.id === c.target.id &&\n ((c.type == \"event\" && p.event === c.event) ||\n (c.type == \"data\" && p.source.sourceOutput == c.source.sourceOutput &&\n p.target.targetInput === p.target.targetInput))) {\n // collect the connection and the endpoints to delete\n connToDelete.push(conn);\n\n let [sourceEP, targetEP] = conn.getAttachedElements();\n if( sourceEP.getUuid().startsWith('connection-split-') ) {\n EPToDelete[ sourceEP.getUuid() ] = sourceEP;\n }\n if( targetEP.getUuid().startsWith('connection-split-') ) {\n EPToDelete[ targetEP.getUuid() ] = targetEP;\n }\n }\n });\n\n // clean all theses connections and endpoints once this call is ended\n _.each(connToDelete, conn => {\n _.defer(()=> {\n this.jsPlumbInstance.deleteConnection(conn, {\n fireEvent: false, //fire a connection detached event?\n forceDetach: false //override any beforeDetach listeners\n });\n });\n });\n\n _.each(EPToDelete, (endpoint, uuid) => {\n _.defer(()=> {\n var div = endpoint.getElement();\n this.jsPlumbInstance.deleteEndpoint(endpoint);\n $(div).remove();\n });\n });\n }\n });\n\n this.jsPlumbInstance.bind(\"dblclick\", async (connection, e) => {\n let [sourceEP, targetEP] = connection.getAttachedElements();\n let c = connection.getParameters();\n\n // split this connection into 2 by creating an endpoint at the event coordinate\n // remove 7px to align it to the center\n var pos = {\n \"top\" : e.pageY - $(this.canvas).offset().top -7,\n \"left\": e.pageX - $(this.canvas).offset().left - 7\n };\n this.splitConnection(connection, pos); // return the endpoint exact position\n\n // save to the graph\n if( c.type == \"data\" ) {\n var link = this.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else {\n var link = this.m.getEventLink(c.source.id, c.target.id, c.event);\n }\n\n if( sourceEP.getUuid().startsWith('connection-split-') ) {\n var el = sourceEP.getElement();\n var prevPos = this.jsPlumbInstance.getPosition(el);\n link.addPathStep(prevPos, pos);\n }\n else {\n link.addPathStep( \"source\", pos );\n }\n });\n\n return new Promise(resolve => this.jsPlumbInstance.ready(resolve));\n },\n\n addLink: function(sourceId, sourceOutput, targetId, targetInput) {\n var sourceUuid = [sourceId, \"output\", sourceOutput].join('#');\n var targetUuid = [targetId, \"input\", targetInput].join('#');\n\n // hide source and target types\n this.style.hideType(sourceUuid); // works with endpoint or uuid\n this.style.hideType(targetUuid);\n\n var connection = this.jsPlumbInstance.connect({\n uuids: [sourceUuid, targetUuid],\n editable: true,\n paintStyle: this.style.connectorData.paintStyle,\n cssClass: 'data-connection',\n connectorClass: 'data-connection'\n });\n\n // split the connection if we need to\n var datalink = this.view.m.getDataLink(sourceId, sourceOutput, targetId, targetInput);\n var path = datalink.getPath();\n _.each( path, (pos) => {\n var r = this.splitConnection(connection, pos);\n connection = r.c2; // switch to the 2nd child connection\n });\n },\n\n addEvent: function(sourceId, targetId, name) {\n var sourceUuid = [sourceId, \"event-out\"].join('#');\n var sourceEP = this.jsPlumbInstance.getEndpoint(sourceUuid);\n\n var connection = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: targetId,\n editable: true,\n paintStyle: this.style.connectorData.paintStyle,\n cssClass: 'event-connection',\n connectorClass: 'event-connection',\n overlays: [\n [ \"PlainArrow\", { location: 1, width:10, length:10, gap: 12 }],\n [ \"Label\", { label: name, location:0.50, labelStyle: { cssClass: \"connection-label noselect\" } } ]\n ]\n });\n connection.setParameter(\"event\", name);\n\n // split the connection if we need to\n var eventlink = this.view.m.getEventLink(sourceId, targetId, name);\n var path = eventlink.getPath();\n _.each( path, (pos) => {\n var r = this.splitConnection(connection, pos);\n connection = r.c2; // switch to the 2nd child connection\n });\n },\n\n // split a connection by adding an endpoint at given pos\n splitConnection: function( connection, pos ) {\n var c = connection.getParameters();\n\n // create the endpoint div\n var id = \"connection-split-\" + dbutils.randomString(8);\n var div = $('<div/>', {\n id: id,\n class: \"connection-control capture-left-click capture-right-click\"\n });\n $(this.view.canvas).append(div);\n\n // position it at pos (adjust for the height of the div)\n var jsPlumbElement = this.jsPlumbInstance.getElement(id);\n this.jsPlumbInstance.setPosition(jsPlumbElement, pos);\n\n // make this div draggable\n var startPosition = pos;\n this.jsPlumbInstance.draggable(div, {\n start: (e) => {\n startPosition = this.jsPlumbInstance.getPosition(jsPlumbElement);\n },\n stop: (e) => {\n var stopPosition = this.jsPlumbInstance.getPosition(jsPlumbElement);\n if( c.type == \"data\" ) {\n var link = this.view.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else if( c.type == \"event\" ) {\n var link = this.view.m.getEventLink(c.source.id, c.target.id, c.event);\n }\n link.updatePathStep(startPosition, stopPosition);\n }\n });\n\n // determine the style of the 2 connections\n c.splitted = true; // indicates that this connection is splitted\n if( c.type == \"data\" ) {\n var connectionStyle = {\n paintStyle: this.style.splittedConnectorData.paintStyle,\n connector: this.style.splittedConnectorData.connector,\n connectorOverlays:this.style.splittedConnectorData.overlays,\n connectorStyle: this.style.splittedConnectorData.paintStyle,\n hoverPaintStyle: this.style.endpointData.hoverStyle,\n cssClass : 'data-connection'\n }\n var epStyle = this.style.dataLineSplitterEndpoint;\n }\n else if( c.type == \"event\" ) {\n var connectionStyle = {\n paintStyle: this.style.eventConnectorData.paintStyle,\n connector: this.style.eventConnectorData.connector,\n connectorOverlays:this.style.eventConnectorData.overlays,\n connectorStyle: this.style.eventConnectorData.paintStyle,\n hoverPaintStyle: this.style.eventEndpointData.hoverStyle,\n cssClass : 'event-connection',\n };\n var epStyle = this.style.eventLineSplitterEndpoint;\n }\n\n // create the div endpoint\n var ep = this.jsPlumbInstance.addEndpoint(id, {\n isSource : false,\n isTarget : false,\n uuid : id,\n anchor : \"Center\",\n maxConnections : 2,\n parameters : c\n }, epStyle);\n\n // make the ep transparent to pointer events, left click on it must drag the div\n $(ep.canvas).css('pointer-events', 'none');\n $(ep.canvas).css('cursor', 'move');\n $(ep.canvas).addClass('capture-left-click');\n $(ep.canvas).addClass('capture-right-click');\n $(ep.canvas).find('svg').attr('pointer-events', 'none');\n $(ep.canvas).addClass(id);\n\n\n // save the connection overlays\n var overlays = connection.getOverlays();\n var overlaysParams = [];\n _.each(overlays, (o)=> {\n overlaysParams.push([o.type, {\n \"location\" : o.loc,\n \"width\" : o.width,\n \"length\" : o.length,\n \"label\" : o.getLabel ? o.getLabel() : undefined,\n \"id\" : o.id,\n \"labelStyle\": o.labelStyle\n }]);\n });\n\n let [sourceEP, targetEP] = connection.getAttachedElements();\n let targetId = connection.targetId;\n\n // detach the original connection\n this.jsPlumbInstance.deleteConnection(connection, { fireEvent: false, forceDetach: false });\n\n // now reattach it into 2 connections\n var c1 = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: ep,\n editable: true,\n }, connectionStyle);\n\n var c2 = this.jsPlumbInstance.connect({\n source: ep,\n target: targetEP._jsPlumb ? targetEP : targetId /* EP has been destroyed, reattach to window */,\n editable: true,\n }, connectionStyle);\n\n sourceEP.setParameter(\"targetConnection\", c1);\n ep.setParameter(\"sourceConnection\", c1);\n ep.setParameter(\"targetConnection\", c2);\n targetEP.setParameter(\"sourceConnection\", c2);\n\n // add the overlays to c2\n _.each(overlaysParams, (p)=> {\n c2.addOverlay(p);\n });\n\n // add menu to remove this split\n $(ep.canvas).ready(() => {\n var menu = new ContextMenu(\"#\" + id, [\n {\n name: 'Remove the split',\n fn: () => {\n this.unsplitConnection(ep);\n }\n },\n ]);\n });\n\n return {\n pos: pos,\n c1 : c1,\n c2 : c2\n }\n },\n\n // Remove a split in a connection\n // ep: entrypoint\n // c1: connection 1\n // c2: connection 2\n unsplitConnection: function(ep) {\n var c = ep.getParameters();\n var c1 = c.sourceConnection;\n var c2 = c.targetConnection;\n var sourceEP = c1.getAttachedElements()[0];\n var targetEP = c2.getAttachedElements()[1];\n\n // detach and delete the endpoint\n this.jsPlumbInstance.deleteConnection(c1, { fireEvent: false, forceDetach: false });\n this.jsPlumbInstance.deleteConnection(c2, { fireEvent: false, forceDetach: false });\n this.jsPlumbInstance.deleteEndpoint(ep);\n\n\n // reconnect directly the source and the target\n var connection = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: targetEP,\n editable: true,\n });\n\n sourceEP.setParameter('targetConnection', connection);\n targetEP.setParameter('sourceConnection', connection);\n\n // find the position of the split endpoint to remove\n var id = c1.targetId;\n var element = this.jsPlumbInstance.getElement(id)\n var position = this.jsPlumbInstance.getPosition(element);\n\n // remove the element in the path\n // 1. find the link\n // 2. find the position of the split endpoint\n if( c.type == \"data\" ) {\n var link = this.view.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n link.removePathStep(position);\n }\n else if( c.type == \"event\" ) {\n var link = this.m.getEventLink();\n link.removePathStep(position);\n }\n else {\n throw \"unknown connection type\";\n }\n\n // remove the div\n $(\"#\" + id).remove(); // delete the div\n },\n\n getNodeId: function( id, nodeType ) {\n switch( nodeType ) {\n case \"input\": return this.view.m.prefixId(id, \"input\");\n case \"output\": return this.view.m.prefixId(id, \"output\");\n case \"module\":\n case \"ui\":\n default:\n return id;\n }\n },\n\n getNode( id, nodeType ) {\n var realId = this.getNodeId(id, nodeType);\n return this.view.m.getNode(realId);\n },\n\n getNodeUniqId(id, nodeType) {\n return this.getNode(id, nodeType).getUniqId() + this.jsPlumbInstance.rand;\n },\n\n setLoading(b) {\n this.loading = b;\n },\n\n gotoTopLeft: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoTopLeft();\n },\n\n gotoTopRight: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoTopRight();\n },\n\n gotoBottomLeft: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoBottomLeft();\n },\n\n gotoBottomRight: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoBottomRight();\n },\n\n assignContextMenu: function() {\n // Create a contextmenu for the div\n var contextOptions = [\n {\n name: 'Add node...',\n fn: (target, evt) => {\n this.$parent.addBox(evt);\n }\n }\n ];\n var cMenu = new ContextMenu(\"#dualbox-graph-canvas\", contextOptions);\n },\n }\n}\n</script>\n"]}, media: undefined });
|
|
49483
|
+
inject("data-v-6d2c9492_0", { source: "\n.dualbox-graph-container {\n padding: 0px 0px 30px 0px;\n margin-left: -25px;\n margin-right: -25px;\n height: 100%;\n width: calc(100% + 50px);\n overflow: hidden;\n transform: translateZ(0); /* chrome hack to avoid glitches */\n}\n.dualbox-graph-container.translating {\n cursor: move;\n}\n.dualbox-graph-container span {\n pointer-events: none;\n}\n.dualbox-graph-container:focus {\n outline: none;\n}\n.dualbox-graph-canvas {\n height: 100%;\n width: 100%;\n background-color: #eee;\n position: relative;\n outline-color: transparent!important;\n min-height: 100%;\n min-width: 100%;\n border-radius: 5px;\n background-color: #ECF2F8;\n border: 1px solid #2e6da4;\n display: inline-block; /* fix padding pb: https://stackoverflow.com/questions/10054870/when-a-child-element-overflows-horizontally-why-is-the-right-padding-of-the-par */\n margin-bottom: -7px;\n}\n\n/* ex-plumb.css */\n.dualbox-graph-canvas .marker-color-tag {\n height: 8px;\n margin: 0 3px 3px 0;\n padding: 0;\n width: 42px;\n line-height: 75pt;\n border-radius: 3px;\n display: block;\n width: 100%;\n}\n.dualbox-graph-canvas .jtk-connector {\n z-index: 4;\n}\n.dualbox-graph-canvas .jtk-endpoint {\n z-index: 30;\n}\n.dualbox-graph-canvas .inoutEndpointLabel, .endpointSourceLabel {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n z-index: 2000;\n transform:translate(0%,0%) !important; /* this shit is just to prevent jsplumb of centering the box on the endpoint... */\n}\n.dualbox-graph-canvas .descSpan {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n position: fixed;\n display:none;\n z-index:2010;\n}\n.dualbox-graph-canvas .aLabel {\n background-color: white;\n padding: 0.4em;\n font: 12px sans-serif;\n color: #444;\n z-index: 21;\n border: 1px dotted gray;\n opacity: 0.8;\n cursor: pointer;\n}\n.dualbox-graph-canvas .aLabel.jtk-hover {\n background-color: #5C96BC;\n color: white;\n border: 1px solid white;\n}\n.card.jtk-connected {\n /*border: 1px solid #a1a1a1; change border when connected to spot isolated elements as disabled*/\n}\n.jtk-drag {\n opacity:0.7 !important;\n}\npath, .jtk-endpoint {\n cursor: pointer;\n}\n.jtk-overlay {\n background-color:transparent;\n}\n", map: {"version":3,"sources":["/home/seb/dev/dualbox/editor/js/src/v/templates/graph.vue"],"names":[],"mappings":";AACA;IACA,yBAAA;IACA,kBAAA;IACA,mBAAA;IACA,YAAA;IACA,wBAAA;IACA,gBAAA;IACA,wBAAA,EAAA,kCAAA;AACA;AAEA;IACA,YAAA;AACA;AAEA;IACA,oBAAA;AACA;AAEA;IACA,aAAA;AACA;AAEA;IACA,YAAA;IACA,WAAA;IACA,sBAAA;IACA,kBAAA;IACA,oCAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;IACA,yBAAA;IACA,yBAAA;IACA,qBAAA,EAAA,iJAAA;IACA,mBAAA;AACA;;AAEA,iBAAA;AACA;IACA,WAAA;IACA,mBAAA;IACA,UAAA;IACA,WAAA;IACA,iBAAA;IACA,kBAAA;IACA,cAAA;IACA,WAAA;AACA;AAEA;IACA,UAAA;AACA;AAEA;IACA,WAAA;AACA;AACA;IACA,+BAAA;IACA,kCAAA;IACA,uCAAA;IACA,oCAAA;IACA,aAAA;IACA,qCAAA,EAAA,iFAAA;AACA;AACA;IACA,+BAAA;IACA,kCAAA;IACA,uCAAA;IACA,oCAAA;IACA,eAAA;IACA,YAAA;IACA,YAAA;AACA;AAEA;IACA,uBAAA;IACA,cAAA;IACA,qBAAA;IACA,WAAA;IACA,WAAA;IACA,uBAAA;IACA,YAAA;IACA,eAAA;AACA;AAEA;IACA,yBAAA;IACA,YAAA;IACA,uBAAA;AACA;AAEA;IACA,gGAAA;AACA;AAEA;IACA,sBAAA;AACA;AAEA;IACA,eAAA;AACA;AAEA;IACA,4BAAA;AACA","file":"graph.vue","sourcesContent":["<style>\n .dualbox-graph-container {\n padding: 0px 0px 30px 0px;\n margin-left: -25px;\n margin-right: -25px;\n height: 100%;\n width: calc(100% + 50px);\n overflow: hidden;\n transform: translateZ(0); /* chrome hack to avoid glitches */\n }\n\n .dualbox-graph-container.translating {\n cursor: move;\n }\n\n .dualbox-graph-container span {\n pointer-events: none;\n }\n\n .dualbox-graph-container:focus {\n outline: none;\n }\n\n .dualbox-graph-canvas {\n height: 100%;\n width: 100%;\n background-color: #eee;\n position: relative;\n outline-color: transparent!important;\n min-height: 100%;\n min-width: 100%;\n border-radius: 5px;\n background-color: #ECF2F8;\n border: 1px solid #2e6da4;\n display: inline-block; /* fix padding pb: https://stackoverflow.com/questions/10054870/when-a-child-element-overflows-horizontally-why-is-the-right-padding-of-the-par */\n margin-bottom: -7px;\n }\n\n /* ex-plumb.css */\n .dualbox-graph-canvas .marker-color-tag {\n height: 8px;\n margin: 0 3px 3px 0;\n padding: 0;\n width: 42px;\n line-height: 75pt;\n border-radius: 3px;\n display: block;\n width: 100%;\n }\n\n .dualbox-graph-canvas .jtk-connector {\n z-index: 4;\n }\n\n .dualbox-graph-canvas .jtk-endpoint {\n z-index: 30;\n }\n .dualbox-graph-canvas .inoutEndpointLabel, .endpointSourceLabel {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n z-index: 2000;\n transform:translate(0%,0%) !important; /* this shit is just to prevent jsplumb of centering the box on the endpoint... */\n }\n .dualbox-graph-canvas .descSpan {\n box-shadow: 1px 1px 1px #716f6f;\n -o-box-shadow: 1px 1px 1px #716f6f;\n -webkit-box-shadow: 0px 1px 1px #716f6f;\n -moz-box-shadow: 1px 1px 1px #716f6f;\n position: fixed;\n display:none;\n z-index:2010;\n }\n\n .dualbox-graph-canvas .aLabel {\n background-color: white;\n padding: 0.4em;\n font: 12px sans-serif;\n color: #444;\n z-index: 21;\n border: 1px dotted gray;\n opacity: 0.8;\n cursor: pointer;\n }\n\n .dualbox-graph-canvas .aLabel.jtk-hover {\n background-color: #5C96BC;\n color: white;\n border: 1px solid white;\n }\n\n .card.jtk-connected {\n /*border: 1px solid #a1a1a1; change border when connected to spot isolated elements as disabled*/\n }\n\n .jtk-drag {\n opacity:0.7 !important;\n }\n\n path, .jtk-endpoint {\n cursor: pointer;\n }\n\n .jtk-overlay {\n background-color:transparent;\n }\n</style>\n\n<template>\n <div class=\"grid-bg dualbox-graph-container dark\" @keyup.ctrl.89=\"redo()\" @keyup.ctrl.90=\"undo()\" @keyup.ctrl.46=\"deleteSelection()\" @keyup.ctrl.67=\"copySelection()\" @keyup.ctrl.86=\"pasteSelection()\" tabindex=1>\n <div id=\"dualbox-graph-canvas\" class=\"canvas-wide jtk-surface jtk-surface-nopan contextmenu dualbox-graph-canvas\">\n <graph-node v-for=\"(def, id) in app.input\" :key=\"getNodeUniqId(id, 'input')\" :id=\"getNodeId(id, 'input')\" :pkg=\"getNode(id, 'input').getPackage()\" :n=\"getNode(id, 'input')\" :ref=\"getNodeId(id, 'input')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.output\" :key=\"getNodeUniqId(id, 'output')\" :id=\"getNodeId(id, 'output')\" :pkg=\"getNode(id, 'output').getPackage()\" :n=\"getNode(id, 'output')\" :ref=\"getNodeId(id, 'output')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.modules\" :key=\"getNodeUniqId(id, 'module')\" :id=\"getNodeId(id, 'module')\" :pkg=\"getNode(id, 'module').getPackage()\" :n=\"getNode(id, 'module')\" :ref=\"getNodeId(id, 'module')\"></graph-node>\n <graph-node v-for=\"(def, id) in app.ui\" :key=\"getNodeUniqId(id, 'ui')\" :id=\"getNodeId(id, 'ui')\" :pkg=\"getNode(id, 'ui').getPackage()\" :n=\"getNode(id, 'ui')\" :ref=\"getNodeId(id, 'ui')\" :displayEvents=\"displayEvents\"></graph-node>\n </div>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-topleft\" style=\"position: absolute; top: 0; left: 0px; border-radius: 0px; border-top-left-radius: 5px; margin: 0;\" v-on:click=\"gotoTopLeft\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-topright\" style=\"position: absolute; top: 0; right: 0px; border-radius: 0px; border-top-right-radius: 5px; margin: 0;\" v-on:click=\"gotoTopRight\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-bottomleft\" style=\"position: absolute; bottom: 30px; left: 0px; border-radius: 0px; border-bottom-left-radius: 5px; margin: 0;\" v-on:click=\"gotoBottomLeft\"><i class=\"fa fa-arrows-alt\"></i></button>\n <button class=\"btn btn-editor-xs btn-outline-dark btn-graph-goto btn-goto-bottomright\" style=\"position: absolute; bottom: 30px; right: 0px; border-radius: 0px; border-bottom-right-radius: 5px; margin: 0;\" v-on:click=\"gotoBottomRight\"><i class=\"fa fa-arrows-alt\"></i></button>\n </div>\n</template>\n\n<script>\n// Utils\nimport _ from 'lodash-es';\nimport dbutils from '../../m/DualboxUtils';\nimport Utils from '../Utils';\nimport swal from 'sweetalert2';\n\n// Sub-components\nimport graphNodeVue from './graphNode.vue';\n\n// Canvas control helpers\nimport Zoomer from '../Zoomer';\nimport Selector from '../Selector';\nimport Translater from '../Translater';\nimport CanvasSizeHandler from '../CanvasSizeHandler';\nimport ContextMenu from '../ContextMenu';\n\n// jsplumb style definitins\nimport PlumbStyle from '../PlumbStyle';\n\nexport default {\n props: [\n \"app\", // the app model\n \"displayEvents\" // display events or not\n ],\n components: {\n 'graph-node' : graphNodeVue,\n },\n data: function () {\n return {\n utils: Utils,\n loading: false,\n }\n },\n created: function() {\n this.view = window.dualboxEditor.v;\n this.jsPlumbInstance = null;\n\n // Controls helpers utilities\n this.selector = null;\n this.translater = null;\n this.zoomer = null;\n this.canvasSizeHandler = null;\n\n // encapsulate the JSPlumb styles\n this.style = new PlumbStyle(this);\n },\n mounted: async function() {\n this.jsPlumbInstance = jsPlumb.getInstance({\n DragOptions: { cursor: 'pointer', zIndex: 2500 }, // default drag options\n Container: \"dualbox-graph-canvas\"\n });\n this.jsPlumbInstance.rand = dbutils.randomString(8);\n this.style.setDefault();\n this.graphContainer = $(this.$el);\n this.canvas = $(this.$el).find('.dualbox-graph-canvas');\n\n // TODO: remove theses definitions from the graphview object (bugs when entering metanode)\n this.view.selector = this.selector = new Selector(this, this.graphContainer);\n this.view.translater = this.translater = new Translater(this, this.graphContainer, this.canvas);\n this.view.zoomer = this.zoomer = new Zoomer(this, this.graphContainer, this.canvas);\n this.view.canvasSizeHandler = this.canvasSizeHandler = new CanvasSizeHandler(this, this.canvas);\n this.assignContextMenu();\n\n // autofocus editor when the div is clicked (allow to use keydown)\n // We use this way because it's easier than to set a tabindex on the dualbox-graph-container\n // to capture keyboard events (will break modals)\n var self = this;\n this.keyDownHandler = function(e) {\n // Ensure event is not null\n e = e || window.event;\n\n if( $(self.$el).is(\":hover\") ) {\n if ((e.which == 90 || e.keyCode == 90) && e.ctrlKey) { // ctrl + z\n self.view.c.undo();\n }\n else if((e.which == 89 || e.keyCode == 89) && e.ctrlKey) { // ctrl + y\n self.view.c.redo();\n }\n else if((e.which == 46 || e.keyCode == 46)) { // suppr\n self.view.c.deleteSelection();\n }\n else if((e.which == 67 || e.keyCode == 67) && e.ctrlKey) { // ctrl + c\n self.view.c.copySelection();\n }\n else if((e.which == 86 || e.keyCode == 86) && e.ctrlKey) { // ctrl + v\n self.view.c.pasteSelection();\n }\n else if((e.which == 88 || e.keyCode == 88) && e.ctrlKey) { // ctrl + x\n self.view.c.copySelection();\n self.view.c.deleteSelection();\n }\n }\n }\n\n $(document).on(\"keydown\", this.keyDownHandler);\n return await this.initializeListeners();\n },\n destroyed: function() {\n $(document).off(\"keydown\", this.keyDownHandler);\n },\n beforeUpdate: async function() {\n // Reset the JSPlumb instance\n this.resetJsPlumb();\n this.refreshBoxes();\n },\n updated: async function() {\n // don't do anything for out-of-date updates (hash is not matching)\n console.log('Updating graph !');\n\n //this.canvas.empty();\n ContextMenu.clean();\n this.assignContextMenu();\n\n // We need to add links and events to the nodes\n\n // Function to gather all links from a node into the \"links\" array\n var links = []; // array of links\n var gatherLinks = (targetId, sourceDef) => {\n // for nodes and uis\n _.each(sourceDef.links, (sources, targetInput) => {\n _.each(sources, (sourceOutput, sourceId) => {\n if( sourceId === \"graph\" ) {\n return;\n }\n\n if( sourceId === \"input\" || sourceId === \"output\" ) {\n sourceId = this.view.m.prefixId(sourceOutput, sourceId);\n sourceOutput = \"value\";\n }\n\n links.push({\n sourceId: sourceId,\n sourceOutput: sourceOutput,\n targetId: targetId,\n targetInput: targetInput\n });\n });\n });\n\n // for inputs and outputs\n _.each(sourceDef.link, (sourceOutput, sourceId) => {\n if( sourceId === \"graph\" ) return;\n if( sourceId === \"input\" || sourceId === \"output\" ) {\n sourceId = this.view.m.prefixId(sourceOutput, sourceId);\n sourceOutput = \"value\";\n }\n\n links.push({\n sourceId: sourceId,\n sourceOutput: sourceOutput,\n targetId: targetId,\n targetInput: \"value\"\n });\n });\n }\n\n // Function to gather all events from a node into the \"links\" array\n var events = [];\n var gatherEvents = (sourceId, def) => {\n _.each(def.events, (evt) => {\n if( evt.node ) {\n events.push({\n sourceId: sourceId,\n targetId: evt.node,\n name: evt.event\n });\n }\n });\n }\n\n _.each(this.app.input, (def, id) => {\n id = this.view.m.prefixId(id, \"input\");\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.output, (def, id) => {\n id = this.view.m.prefixId(id, \"output\");\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.modules, (def, id) =>{\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n _.each(this.app.ui, (def, id) =>{\n gatherLinks(id, def); // add all data links in our array\n gatherEvents(id, def); // add all event links in our array\n });\n\n // Now, we gathered all links and events. Add them into jsPlumb\n // add the links\n _.each(links, (link) => {\n this.addLink(link.sourceId, link.sourceOutput, link.targetId, link.targetInput);\n });\n\n // add the events\n if( this.displayEvents ) {\n _.each(events, (e) => {\n this.addEvent(e.sourceId, e.targetId, e.name);\n });\n }\n\n await this.canvasSizeHandler.resize();\n\n // Initialize all listeners again\n await this.initializeJsPlumbListeners();\n\n return new Promise((resolve) => this.jsPlumbInstance.ready(resolve));\n },\n methods: {\n deleteSelection() {\n this.view.c.deleteSelection();\n },\n\n copySelection() {\n this.view.c.copySelection();\n },\n\n pasteSelection() {\n this.view.c.pasteSelection();\n },\n\n getCurrentMousePosition(e) {\n var x = e.pageX - $(this.$el).find('#dualbox-graph-canvas').offset().left;\n var y = e.pageY - $(this.$el).find('#dualbox-graph-canvas').offset().top;\n x = x / this.zoomer.getZoom();\n y = y / this.zoomer.getZoom();\n return {\n \"top\": y,\n \"left\": x\n };\n },\n\n resetJsPlumb : function() {\n if( this.jsPlumbInstance ) {\n this.jsPlumbInstance.reset();\n delete this.jsPlumbInstance.initializedNodes;\n }\n\n window.jsPlumbInstance = this.jsPlumbInstance = jsPlumb.getInstance({\n DragOptions: { cursor: 'pointer', zIndex: 2500 }, // default drag options\n Container: \"dualbox-graph-canvas\"\n });\n\n this.jsPlumbInstance.rand = dbutils.randomString(8); // just change the node refs to avoid vue.js optimizations\n this.style.setDefault();\n },\n\n refreshBoxes: function() {\n _.each(this.$refs, (component, name) => {\n var c = component[0];\n if(c) c.$forceUpdate();\n })\n },\n\n // bind jsPlumb events\n initializeListeners: async function() {\n this.selector.initialize();\n this.zoomer.initialize();\n ContextMenu.initialize(); // important to make this after the translater (right-clic conflicts)\n this.translater.initialize();\n\n // show selected nodes\n this.selector.onSelecting((divs) => {\n $(this.canvas).find('.card').removeClass('selected');\n _.each(divs, (div) => {\n $(div).addClass('selected');\n });\n });\n\n // Add every node in the selected area to JsPlumb selection\n this.selector.onSelect((divs) => {\n _.each(divs, (div) => {\n $(div).addClass('selected');\n this.jsPlumbInstance.addToDragSelection($(div)[0]);\n });\n });\n\n // remove the selection\n this.selector.onDeselect((divs) => {\n _.each(divs, (div) => {\n $(div).removeClass('selected');\n this.jsPlumbInstance.removeFromDragSelection($(div)[0]);\n });\n });\n\n return await this.initializeJsPlumbListeners();\n },\n\n initializeJsPlumbListeners: function() {\n this.refreshGraphNode = function(id) {\n var nodeVue = this.$refs[id];\n if( nodeVue[0] ) nodeVue[0].$forceUpdate();\n }\n\n this.jsPlumbInstance.bind(\"connectionDrag\", (connection) => {\n // hide the type of the source (output endpoint)\n var endpoints = connection.getAttachedElements();\n if( endpoints && endpoints[0] ) this.style.hideType(endpoints[0]);\n\n // if the connection linked a source to a target, remove the connection\n var c = connection.getParameters();\n if( c.target ) {\n if( c.type === \"data\" ) {\n // remove the link\n this.view.m.removeDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else if( c.type === \"event\" ) {\n this.view.m.removeEventLink(c.source.id, c.target.id, c.event);\n this.view.setBoxMenu(c.source.id);\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connectionDragStop\", (connection) => {\n // if the connection has not been made, show back the type of the source\n var endpoints = connection.getAttachedElements();\n if( endpoints && endpoints.length == 2 ) {\n var sourceEP = endpoints[0];\n var targetEP = endpoints[1];\n if( targetEP && targetEP.elementId.startsWith('jsPlumb')) {\n if( targetEP.elementId.startsWith('jsPlumb') ) {\n // no connection has been made, show back our output type\n var sourceOutput = connection.getParameters().source.output;\n if( !this.view.m.getNode(sourceEP.elementId).isOutputConnected(sourceOutput) ) {\n this.style.showType(sourceEP);\n }\n }\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connection\", (info, event) => {\n // only do this for connection established by user, no programmatically\n if( event ) {\n var c = info.connection.getParameters();\n if( c.type === \"data\" ) {\n // check the types\n var sourceType = this.view.m.getNode(c.source.id).getOutputType(c.source.output);\n var targetType = this.view.m.getNode(c.target.id).getInputType(c.target.input);\n if( this.view.m.compareTypes(sourceType, targetType) ) {\n this.view.m.addDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n\n // the target need to be refreshed\n this.refreshGraphNode(c.target.id);\n\n // hide source and target types\n this.style.hideType(info.sourceEndpoint);\n this.style.hideType(info.targetEndpoint);\n }\n else {\n // Error\n this.jsPlumbInstance.deleteConnection(info.connection, {\n fireEvent: false, //fire a connection detached event?\n forceDetach: false //override any beforeDetach listeners\n });\n this.style.showType(info.targetEndpoint);\n if( !this.view.m.getNode(info.sourceId).isOutputConnected(c.source.output) ) {\n this.style.showType(info.sourceEndpoint);\n }\n\n swal({\n title: \"Nope.\",\n html: \"<div style='text-align: left;'>Output <b style='font-weight: bold;'>\" + c.source.output + \"</b> of <b style='font-weight: bold;'>\" + info.sourceId + \"</b> is of type <u>\" + Utils.htmlentities(sourceType) + \"</u><br/>Input <b style='font-weight: bold;'>\" + c.target.input + \"</b> of <b style='font-weight: bold;'>\" + info.targetId + \"</b> is of type <u>\" + Utils.htmlentities(targetType) + \"</u>.</div><br/>Type mismatch.\",\n type: \"error\",\n });\n }\n }\n else if( c.type === \"event\" ) {\n var target = this.view.m.getNode(c.target.id);\n var eventNames = target.getEventsNames();\n var options = {};\n _.each(eventNames, (name) => options[name] = name );\n\n swal({\n input: 'select',\n title: 'Select an event',\n inputOptions: options\n }).then( (result) => {\n if( result.value ) {\n info.connection.setParameter('event', result.value);\n info.connection.setLabel({ label: result.value, cssClass: \"connection-label noselect\", location: 0.5 });\n this.view.m.addEventLink(c.source.id, c.target.id, result.value);\n\n this.view.setBoxMenu(c.source.id);\n }\n });\n }\n }\n });\n\n this.jsPlumbInstance.bind(\"connectionDetached\", (info, event) => {\n var c = info.connection.getParameters();\n if( c.type === \"data\" ) {\n // remove the link\n this.view.m.removeDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n\n // show source and target type\n this.style.showType(info.targetEndpoint);\n if( !info.source.id.startsWith('connection-split') &&\n !this.view.m.getNode(info.source.id).isOutputConnected(c.source.output) ) {\n this.style.showType(info.sourceEndpoint);\n }\n\n // the target need to be refreshed\n this.refreshGraphNode(c.target.id);\n }\n else if( c.type === \"event\" ) {\n this.view.m.removeEventLink(c.source.id, c.target.id, c.event);\n //this.style.showEventName(info.targetEndpoint);\n }\n\n // if the connection is splitted, remove all other connections/endpoint\n if( c.splitted ) {\n var connections = this.jsPlumbInstance.getAllConnections();\n var connToDelete = [];\n var EPToDelete = {};\n _.each(connections, conn => {\n var p = conn.getParameters();\n if( p.source.id === c.source.id &&\n p.target.id === c.target.id &&\n ((c.type == \"event\" && p.event === c.event) ||\n (c.type == \"data\" && p.source.sourceOutput == c.source.sourceOutput &&\n p.target.targetInput === p.target.targetInput))) {\n // collect the connection and the endpoints to delete\n connToDelete.push(conn);\n\n let [sourceEP, targetEP] = conn.getAttachedElements();\n if( sourceEP.getUuid().startsWith('connection-split-') ) {\n EPToDelete[ sourceEP.getUuid() ] = sourceEP;\n }\n if( targetEP.getUuid().startsWith('connection-split-') ) {\n EPToDelete[ targetEP.getUuid() ] = targetEP;\n }\n }\n });\n\n // clean all theses connections and endpoints once this call is ended\n _.each(connToDelete, conn => {\n _.defer(()=> {\n this.jsPlumbInstance.deleteConnection(conn, {\n fireEvent: false, //fire a connection detached event?\n forceDetach: false //override any beforeDetach listeners\n });\n });\n });\n\n _.each(EPToDelete, (endpoint, uuid) => {\n _.defer(()=> {\n var div = endpoint.getElement();\n this.jsPlumbInstance.deleteEndpoint(endpoint);\n $(div).remove();\n });\n });\n }\n });\n\n this.jsPlumbInstance.bind(\"dblclick\", async (connection, e) => {\n let [sourceEP, targetEP] = connection.getAttachedElements();\n let c = connection.getParameters();\n\n // split this connection into 2 by creating an endpoint at the event coordinate\n // remove 7px to align it to the center\n var pos = {\n \"top\" : e.pageY - $(this.canvas).offset().top -7,\n \"left\": e.pageX - $(this.canvas).offset().left - 7\n };\n this.splitConnection(connection, pos); // return the endpoint exact position\n\n // save to the graph\n if( c.type == \"data\" ) {\n var link = this.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else {\n var link = this.m.getEventLink(c.source.id, c.target.id, c.event);\n }\n\n if( sourceEP.getUuid().startsWith('connection-split-') ) {\n var el = sourceEP.getElement();\n var prevPos = this.jsPlumbInstance.getPosition(el);\n link.addPathStep(prevPos, pos);\n }\n else {\n link.addPathStep( \"source\", pos );\n }\n });\n\n return new Promise(resolve => this.jsPlumbInstance.ready(resolve));\n },\n\n addLink: function(sourceId, sourceOutput, targetId, targetInput) {\n var sourceUuid = [sourceId, \"output\", sourceOutput].join('#');\n var targetUuid = [targetId, \"input\", targetInput].join('#');\n\n // hide source and target types\n this.style.hideType(sourceUuid); // works with endpoint or uuid\n this.style.hideType(targetUuid);\n\n var connection = this.jsPlumbInstance.connect({\n uuids: [sourceUuid, targetUuid],\n editable: true,\n paintStyle: this.style.connectorData.paintStyle,\n cssClass: 'data-connection',\n connectorClass: 'data-connection'\n });\n\n // split the connection if we need to\n var datalink = this.view.m.getDataLink(sourceId, sourceOutput, targetId, targetInput);\n var path = datalink.getPath();\n _.each( path, (pos) => {\n var r = this.splitConnection(connection, pos);\n connection = r.c2; // switch to the 2nd child connection\n });\n },\n\n addEvent: function(sourceId, targetId, name) {\n var sourceUuid = [sourceId, \"event-out\"].join('#');\n var sourceEP = this.jsPlumbInstance.getEndpoint(sourceUuid);\n\n var connection = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: targetId,\n editable: true,\n paintStyle: this.style.connectorData.paintStyle,\n cssClass: 'event-connection',\n connectorClass: 'event-connection',\n overlays: [\n [ \"PlainArrow\", { location: 1, width:10, length:10, gap: 12 }],\n [ \"Label\", { label: name, location:0.50, labelStyle: { cssClass: \"connection-label noselect\" } } ]\n ]\n });\n connection.setParameter(\"event\", name);\n\n // split the connection if we need to\n var eventlink = this.view.m.getEventLink(sourceId, targetId, name);\n var path = eventlink.getPath();\n _.each( path, (pos) => {\n var r = this.splitConnection(connection, pos);\n connection = r.c2; // switch to the 2nd child connection\n });\n },\n\n // split a connection by adding an endpoint at given pos\n splitConnection: function( connection, pos ) {\n var c = connection.getParameters();\n\n // create the endpoint div\n var id = \"connection-split-\" + dbutils.randomString(8);\n var div = $('<div/>', {\n id: id,\n class: \"connection-control capture-left-click capture-right-click\"\n });\n $(this.canvas).append(div);\n\n // position it at pos (adjust for the height of the div)\n var jsPlumbElement = this.jsPlumbInstance.getElement(id);\n this.jsPlumbInstance.setPosition(jsPlumbElement, pos);\n\n // make this div draggable\n var startPosition = pos;\n this.jsPlumbInstance.draggable(div, {\n start: (e) => {\n startPosition = this.jsPlumbInstance.getPosition(jsPlumbElement);\n },\n stop: (e) => {\n var stopPosition = this.jsPlumbInstance.getPosition(jsPlumbElement);\n if( c.type == \"data\" ) {\n var link = this.view.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n }\n else if( c.type == \"event\" ) {\n var link = this.view.m.getEventLink(c.source.id, c.target.id, c.event);\n }\n link.updatePathStep(startPosition, stopPosition);\n }\n });\n\n // determine the style of the 2 connections\n c.splitted = true; // indicates that this connection is splitted\n if( c.type == \"data\" ) {\n var connectionStyle = {\n paintStyle: this.style.splittedConnectorData.paintStyle,\n connector: this.style.splittedConnectorData.connector,\n connectorOverlays:this.style.splittedConnectorData.overlays,\n connectorStyle: this.style.splittedConnectorData.paintStyle,\n hoverPaintStyle: this.style.endpointData.hoverStyle,\n cssClass : 'data-connection'\n }\n var epStyle = this.style.dataLineSplitterEndpoint;\n }\n else if( c.type == \"event\" ) {\n var connectionStyle = {\n paintStyle: this.style.eventConnectorData.paintStyle,\n connector: this.style.eventConnectorData.connector,\n connectorOverlays:this.style.eventConnectorData.overlays,\n connectorStyle: this.style.eventConnectorData.paintStyle,\n hoverPaintStyle: this.style.eventEndpointData.hoverStyle,\n cssClass : 'event-connection',\n };\n var epStyle = this.style.eventLineSplitterEndpoint;\n }\n\n // create the div endpoint\n var ep = this.jsPlumbInstance.addEndpoint(id, {\n isSource : false,\n isTarget : false,\n uuid : id,\n anchor : \"Center\",\n maxConnections : 2,\n parameters : c\n }, epStyle);\n\n // make the ep transparent to pointer events, left click on it must drag the div\n $(ep.canvas).css('pointer-events', 'none');\n $(ep.canvas).css('cursor', 'move');\n $(ep.canvas).addClass('capture-left-click');\n $(ep.canvas).addClass('capture-right-click');\n $(ep.canvas).find('svg').attr('pointer-events', 'none');\n $(ep.canvas).addClass(id);\n\n\n // save the connection overlays\n var overlays = connection.getOverlays();\n var overlaysParams = [];\n _.each(overlays, (o)=> {\n overlaysParams.push([o.type, {\n \"location\" : o.loc,\n \"width\" : o.width,\n \"length\" : o.length,\n \"label\" : o.getLabel ? o.getLabel() : undefined,\n \"id\" : o.id,\n \"labelStyle\": o.labelStyle\n }]);\n });\n\n let [sourceEP, targetEP] = connection.getAttachedElements();\n let targetId = connection.targetId;\n\n // detach the original connection\n this.jsPlumbInstance.deleteConnection(connection, { fireEvent: false, forceDetach: false });\n\n // now reattach it into 2 connections\n var c1 = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: ep,\n editable: true,\n }, connectionStyle);\n\n var c2 = this.jsPlumbInstance.connect({\n source: ep,\n target: targetEP._jsPlumb ? targetEP : targetId /* EP has been destroyed, reattach to window */,\n editable: true,\n }, connectionStyle);\n\n sourceEP.setParameter(\"targetConnection\", c1);\n ep.setParameter(\"sourceConnection\", c1);\n ep.setParameter(\"targetConnection\", c2);\n targetEP.setParameter(\"sourceConnection\", c2);\n\n // add the overlays to c2\n _.each(overlaysParams, (p)=> {\n c2.addOverlay(p);\n });\n\n // add menu to remove this split\n $(ep.canvas).ready(() => {\n var menu = new ContextMenu(\"#\" + id, [\n {\n name: 'Remove the split',\n fn: () => {\n this.unsplitConnection(ep);\n }\n },\n ]);\n });\n\n return {\n pos: pos,\n c1 : c1,\n c2 : c2\n }\n },\n\n // Remove a split in a connection\n // ep: entrypoint\n // c1: connection 1\n // c2: connection 2\n unsplitConnection: function(ep) {\n var c = ep.getParameters();\n var c1 = c.sourceConnection;\n var c2 = c.targetConnection;\n var sourceEP = c1.getAttachedElements()[0];\n var targetEP = c2.getAttachedElements()[1];\n\n // detach and delete the endpoint\n this.jsPlumbInstance.deleteConnection(c1, { fireEvent: false, forceDetach: false });\n this.jsPlumbInstance.deleteConnection(c2, { fireEvent: false, forceDetach: false });\n this.jsPlumbInstance.deleteEndpoint(ep);\n\n\n // reconnect directly the source and the target\n var connection = this.jsPlumbInstance.connect({\n source: sourceEP,\n target: targetEP,\n editable: true,\n });\n\n sourceEP.setParameter('targetConnection', connection);\n targetEP.setParameter('sourceConnection', connection);\n\n // find the position of the split endpoint to remove\n var id = c1.targetId;\n var element = this.jsPlumbInstance.getElement(id)\n var position = this.jsPlumbInstance.getPosition(element);\n\n // remove the element in the path\n // 1. find the link\n // 2. find the position of the split endpoint\n if( c.type == \"data\" ) {\n var link = this.view.m.getDataLink(c.source.id, c.source.output, c.target.id, c.target.input);\n link.removePathStep(position);\n }\n else if( c.type == \"event\" ) {\n var link = this.m.getEventLink();\n link.removePathStep(position);\n }\n else {\n throw \"unknown connection type\";\n }\n\n // remove the div\n $(\"#\" + id).remove(); // delete the div\n },\n\n getNodeId: function( id, nodeType ) {\n switch( nodeType ) {\n case \"input\": return this.view.m.prefixId(id, \"input\");\n case \"output\": return this.view.m.prefixId(id, \"output\");\n case \"module\":\n case \"ui\":\n default:\n return id;\n }\n },\n\n getNode( id, nodeType ) {\n var realId = this.getNodeId(id, nodeType);\n return this.view.m.getNode(realId);\n },\n\n getNodeUniqId(id, nodeType) {\n return this.getNode(id, nodeType).getUniqId() + this.jsPlumbInstance.rand;\n },\n\n setLoading(b) {\n this.loading = b;\n },\n\n gotoTopLeft: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoTopLeft();\n },\n\n gotoTopRight: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoTopRight();\n },\n\n gotoBottomLeft: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoBottomLeft();\n },\n\n gotoBottomRight: function(e) {\n e.preventDefault();\n e.stopPropagation();\n this.translater.gotoBottomRight();\n },\n\n assignContextMenu: function() {\n // Create a contextmenu for the div\n var contextOptions = [\n {\n name: 'Add node...',\n fn: (target, evt) => {\n this.$parent.addBox(evt);\n }\n }\n ];\n var cMenu = new ContextMenu(\"#dualbox-graph-canvas\", contextOptions);\n },\n }\n}\n</script>\n"]}, media: undefined });
|
|
49394
49484
|
|
|
49395
49485
|
};
|
|
49396
49486
|
/* scoped */
|
|
@@ -89862,12 +89952,140 @@ class GraphController {
|
|
|
89862
89952
|
this.v.repaint();
|
|
89863
89953
|
}
|
|
89864
89954
|
|
|
89955
|
+
setClipboard(str) {
|
|
89956
|
+
const el = document.createElement('textarea');
|
|
89957
|
+
el.value = str;
|
|
89958
|
+
document.body.appendChild(el);
|
|
89959
|
+
el.select();
|
|
89960
|
+
document.execCommand('copy');
|
|
89961
|
+
document.body.removeChild(el);
|
|
89962
|
+
}
|
|
89963
|
+
|
|
89964
|
+
async getClipboard() {
|
|
89965
|
+
return await navigator.clipboard.readText();
|
|
89966
|
+
}
|
|
89967
|
+
|
|
89865
89968
|
deleteSelection() {
|
|
89969
|
+
this.m.batch(() => {
|
|
89970
|
+
var selectedDivs = this.v.selector.getSelection();
|
|
89971
|
+
lodash.each(selectedDivs, (div) => {
|
|
89972
|
+
this.m.getNode($(div).attr('id')).remove();
|
|
89973
|
+
});
|
|
89974
|
+
});
|
|
89975
|
+
this.v.repaint();
|
|
89976
|
+
}
|
|
89977
|
+
|
|
89978
|
+
copySelection() {
|
|
89979
|
+
var nodes = [];
|
|
89980
|
+
var json = {
|
|
89981
|
+
object: "dualbox c/c",
|
|
89982
|
+
modules: {},
|
|
89983
|
+
ui: {},
|
|
89984
|
+
input: {},
|
|
89985
|
+
output: {},
|
|
89986
|
+
metanodes: {},
|
|
89987
|
+
};
|
|
89866
89988
|
var selectedDivs = this.v.selector.getSelection();
|
|
89989
|
+
|
|
89990
|
+
var isInSelection = nodeId => {
|
|
89991
|
+
var r = false;
|
|
89992
|
+
lodash.each(nodes, n => {
|
|
89993
|
+
if (n.graphId === nodeId) {
|
|
89994
|
+
r = true;
|
|
89995
|
+
return false; // eol
|
|
89996
|
+
}
|
|
89997
|
+
});
|
|
89998
|
+
return r;
|
|
89999
|
+
};
|
|
90000
|
+
|
|
90001
|
+
// strip a node definition from it's content that is outside
|
|
90002
|
+
// of the scope of selection
|
|
90003
|
+
var stripContentOutsideOfSelection = (n) => {
|
|
90004
|
+
var def = lodash.clone(n.def);
|
|
90005
|
+
|
|
90006
|
+
// delete links from outside
|
|
90007
|
+
var links = n.getInboundLinks();
|
|
90008
|
+
lodash.each(links, (l) => {
|
|
90009
|
+
var sourceNode = l.getSourceNode();
|
|
90010
|
+
if (!isInSelection(sourceNode.graphId)) {
|
|
90011
|
+
if (n.isInput() || n.isOutput()) {
|
|
90012
|
+
delete def.link;
|
|
90013
|
+
} else {
|
|
90014
|
+
delete def.links[l.targetInput];
|
|
90015
|
+
}
|
|
90016
|
+
}
|
|
90017
|
+
});
|
|
90018
|
+
|
|
90019
|
+
// delete events and UI stuff
|
|
90020
|
+
delete def.events;
|
|
90021
|
+
delete def.registerTo;
|
|
90022
|
+
|
|
90023
|
+
return def;
|
|
90024
|
+
};
|
|
90025
|
+
|
|
90026
|
+
// first get nodes
|
|
89867
90027
|
lodash.each(selectedDivs, (div) => {
|
|
89868
|
-
this.m.getNode($(div).attr('id'))
|
|
90028
|
+
var n = this.m.getNode($(div).attr('id'));
|
|
90029
|
+
nodes.push(n);
|
|
89869
90030
|
});
|
|
89870
|
-
|
|
90031
|
+
|
|
90032
|
+
|
|
90033
|
+
// add node, but strip content
|
|
90034
|
+
lodash.each(nodes, n => {
|
|
90035
|
+
if (n.isInput()) {
|
|
90036
|
+
json.input[n.graphId] = stripContentOutsideOfSelection(n);
|
|
90037
|
+
} else if (n.isOutput()) {
|
|
90038
|
+
json.output[n.graphId] = stripContentOutsideOfSelection(n);
|
|
90039
|
+
} else if (n.isUI()) {
|
|
90040
|
+
json.ui[n.graphId] = stripContentOutsideOfSelection(n);
|
|
90041
|
+
} else if (n.isModule()) {
|
|
90042
|
+
json.modules[n.graphId] = stripContentOutsideOfSelection(n);
|
|
90043
|
+
if (n.isMetanode()) {
|
|
90044
|
+
json.metanodes[n.def.module] = n.getMetanodeDefinition();
|
|
90045
|
+
}
|
|
90046
|
+
}
|
|
90047
|
+
});
|
|
90048
|
+
|
|
90049
|
+
this.setClipboard(JSON.stringify(json));
|
|
90050
|
+
|
|
90051
|
+
// indicates to the user that the selection is saved
|
|
90052
|
+
const Toast = sweetalert2_all.mixin({
|
|
90053
|
+
toast: true,
|
|
90054
|
+
position: 'bottom-end',
|
|
90055
|
+
showConfirmButton: false,
|
|
90056
|
+
timer: 2000,
|
|
90057
|
+
timerProgressBar: true,
|
|
90058
|
+
onOpen: (toast) => {
|
|
90059
|
+
toast.addEventListener('mouseenter', sweetalert2_all.stopTimer);
|
|
90060
|
+
toast.addEventListener('mouseleave', sweetalert2_all.resumeTimer);
|
|
90061
|
+
}
|
|
90062
|
+
});
|
|
90063
|
+
|
|
90064
|
+
Toast.fire({
|
|
90065
|
+
icon: 'success',
|
|
90066
|
+
title: 'Selection copied to clipboard'
|
|
90067
|
+
});
|
|
90068
|
+
}
|
|
90069
|
+
|
|
90070
|
+
async pasteSelection() {
|
|
90071
|
+
var clipboard = await this.getClipboard();
|
|
90072
|
+
try {
|
|
90073
|
+
var json = JSON.parse(clipboard);
|
|
90074
|
+
} catch (e) {
|
|
90075
|
+
sweetalert2_all.fire("Error", "You must first copy boxes from Dualbox");
|
|
90076
|
+
}
|
|
90077
|
+
|
|
90078
|
+
sweetalert2_all.queue([{
|
|
90079
|
+
title: 'Dependencies required',
|
|
90080
|
+
text: 'Dualbox will load additional required dependencies to paste your selection',
|
|
90081
|
+
showLoaderOnConfirm: true,
|
|
90082
|
+
preConfirm: async () => {
|
|
90083
|
+
await this.e.loadPackages(json);
|
|
90084
|
+
this.m.paste(json);
|
|
90085
|
+
await this.v.repaint();
|
|
90086
|
+
}
|
|
90087
|
+
}]);
|
|
90088
|
+
|
|
89871
90089
|
}
|
|
89872
90090
|
|
|
89873
90091
|
mergeSelection() {
|