let util = require('../../util'); let Hammer = require('../../module/hammer'); let hammerUtil = require('../../hammerUtil'); let locales = require('../locales'); /** * clears the toolbar div element of children * * @private */ class ManipulationSystem { constructor(body, canvas, selectionHandler) { this.body = body; this.canvas = canvas; this.selectionHandler = selectionHandler; this.editMode = false; this.manipulationDiv = undefined; this.editModeDiv = undefined; this.closeDiv = undefined; this.boundFunction = undefined; this.manipulationHammers = []; this.cachedFunctions = {}; this.touchTime = 0; this.temporaryIds = {nodes: [], edges:[]}; this.guiEnabled = false; this.selectedControlNode = undefined; this.options = {}; this.defaultOptions = { enabled: false, initiallyVisible: false, locale: 'en', locales: locales, functionality:{ addNode: true, addEdge: true, editNode: true, editEdge: true, deleteNode: true, deleteEdge: true }, handlerFunctions: { addNode: undefined, addEdge: undefined, editNode: undefined, editEdge: undefined, deleteNode: undefined, deleteEdge: undefined } } util.extend(this.options, this.defaultOptions); } setOptions(options) { if (options !== undefined) { if (typeof options == 'boolean') { this.options.enabled = options; } else { this.options.enabled = true; for (let prop in options) { if (options.hasOwnProperty(prop)) { this.options[prop] = options[prop]; } } } if (this.options.initiallyVisible === true) { this.editMode = true; } this.init(); } } init() { if (this.options.enabled === true) { // Enable the GUI this.guiEnabled = true; // remove override this.selectionHandler.forceSelectEdges = true; this.createWrappers(); if (this.editMode === false) { this.createEditButton(); } else { this.createManipulatorBar(); } } else { this.removeManipulationDOM(); // disable the gui this.guiEnabled = false; } } createWrappers() { // load the manipulator HTML elements. All styling done in css. if (this.manipulationDiv === undefined) { this.manipulationDiv = document.createElement('div'); this.manipulationDiv.className = 'network-manipulationDiv'; if (this.editMode === true) { this.manipulationDiv.style.display = "block"; } else { this.manipulationDiv.style.display = "none"; } this.canvas.frame.appendChild(this.manipulationDiv); } if (this.editModeDiv === undefined) { this.editModeDiv = document.createElement('div'); this.editModeDiv.className = 'network-manipulation-editMode'; if (this.editMode === true) { this.editModeDiv.style.display = "none"; } else { this.editModeDiv.style.display = "block"; } this.canvas.frame.appendChild(this.editModeDiv); } if (this.closeDiv === undefined) { this.closeDiv = document.createElement('div'); this.closeDiv.className = 'network-manipulation-closeDiv'; this.closeDiv.style.display = this.manipulationDiv.style.display; this.canvas.frame.appendChild(this.closeDiv); } } /** * Create the edit button */ createEditButton() { // restore everything to it's original state (if applicable) this._clean(); // reset the manipulationDOM this.manipulationDOM = {}; // empty the editModeDiv util.recursiveDOMDelete(this.editModeDiv); // create the contents for the editMode button let locale = this.options.locales[this.options.locale]; let button = this.createButton('editMode', 'network-manipulationUI edit editmode', locale['edit']); this.editModeDiv.appendChild(button); // bind a hammer listener to the button, calling the function toggleEditMode. this.bindHammerToDiv(button, 'toggleEditMode'); } removeManipulationDOM() { // removes all the bindings and overloads this._clean(); // empty the manipulation divs util.recursiveDOMDelete(this.manipulationDiv); util.recursiveDOMDelete(this.editModeDiv); util.recursiveDOMDelete(this.closeDiv); // remove the manipulation divs this.canvas.frame.removeChild(this.manipulationDiv); this.canvas.frame.removeChild(this.editModeDiv); this.canvas.frame.removeChild(this.closeDiv); // set the references to undefined this.manipulationDiv = undefined; this.editModeDiv = undefined; this.closeDiv = undefined; // remove override this.selectionHandler.forceSelectEdges = false; } //clearManipulatorBar() { // util._recursiveDOMDelete(this.manipulationDiv); // this.manipulationDOM = {}; // this._cleanManipulatorHammers(); // this._manipulationReleaseOverload(); //} _cleanManipulatorHammers() { // _clean hammer bindings if (this.manipulationHammers.length != 0) { for (let i = 0; i < this.manipulationHammers.length; i++) { this.manipulationHammers[i].destroy(); } this.manipulationHammers = []; } } /** * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore * these functions to their original functionality, we saved them in this.cachedFunctions. * This function restores these functions to their original function. * * @private */ _restoreOverloadedFunctions() { for (let functionName in this.cachedFunctions) { if (this.cachedFunctions.hasOwnProperty(functionName)) { this.body.eventListeners[functionName] = this.cachedFunctions[functionName]; delete this.cachedFunctions[functionName]; } } this.cachedFunctions = {}; } /** * Enable or disable edit-mode. * * @private */ toggleEditMode() { this.editMode = !this.editMode; let toolbar = this.manipulationDiv; let closeDiv = this.closeDiv; let editModeDiv = this.editModeDiv; if (this.editMode === true) { toolbar.style.display = "block"; closeDiv.style.display = "block"; editModeDiv.style.display = "none"; this.bindHammerToDiv(closeDiv, 'toggleEditMode'); this.createManipulatorBar(); } else { toolbar.style.display = "none"; closeDiv.style.display = "none"; editModeDiv.style.display = "block"; this.createEditButton(); } } _clean() { // _clean the divs if (this.guiEnabled === true) { util.recursiveDOMDelete(this.editModeDiv); util.recursiveDOMDelete(this.manipulationDiv); // removes all the bindings and overloads this._cleanManipulatorHammers(); } // remove temporary nodes and edges this._cleanupTemporaryNodesAndEdges(); // restore overloaded UI functions this._restoreOverloadedFunctions(); // remove the boundFunction if (this.boundFunction !== undefined) { this.body.emitter.off(this.boundFunction.event, this.boundFunction.fn); } this.boundFunction = undefined; } createSeperator(index = 1) { this.manipulationDOM['seperatorLineDiv' + index] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv' + index].className = 'network-seperatorLine'; this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv' + index]); } createAddNodeButton(locale) { let button = this.createButton('addNode', 'network-manipulationUI add', locale['addNode']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, 'addNodeMode'); } createAddEdgeButton(locale) { let button = this.createButton('addEdge', 'network-manipulationUI connect', locale['addEdge']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, 'addEdgeMode'); } createEditNodeButton(locale) { let button = this.createButton('editNode', 'network-manipulationUI edit', locale['editNode']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, '_editNode'); } createEditEdgeButton(locale) { let button = this.createButton('editEdge', 'network-manipulationUI edit', locale['editEdge']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, 'editEdgeMode'); } createDeleteButton(locale) { let button = this.createButton('delete', 'network-manipulationUI delete', locale['del']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, 'deleteSelected'); } createBackButton(locale) { let button = this.createButton('back', 'network-manipulationUI back', locale['back']); this.manipulationDiv.appendChild(button); this.bindHammerToDiv(button, 'createManipulatorBar'); } createDescription(label) { this.manipulationDiv.appendChild( this.createButton('description', 'network-manipulationUI none', label) ); } createButton(id, className, label, labelClassName = 'network-manipulationLabel') { this.manipulationDOM[id+"Div"] = document.createElement('div'); this.manipulationDOM[id+"Div"].className = className; this.manipulationDOM[id+"Label"] = document.createElement('div'); this.manipulationDOM[id+"Label"].className = labelClassName; this.manipulationDOM[id+"Label"].innerHTML = label; this.manipulationDOM[id+"Div"].appendChild(this.manipulationDOM[id+'Label']); return this.manipulationDOM[id+"Div"]; } temporaryBind(fn, event) { this.boundFunction = {fn:fn.bind(this), event}; this.body.emitter.on(event, this.boundFunction.fn); } /** * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. * * @private */ createManipulatorBar() { this._clean(); // resume calculation this.body.emitter.emit("restorePhysics"); // reset global letiables this.manipulationDOM = {}; let selectedNodeCount = this.selectionHandler._getSelectedNodeCount(); let selectedEdgeCount = this.selectionHandler._getSelectedEdgeCount(); let selectedTotalCount = selectedNodeCount + selectedEdgeCount; let locale = this.options.locales[this.options.locale]; let needSeperator = false; if (this.options.functionality.addNode === true) { this.createAddNodeButton(locale); needSeperator = true; } if (this.options.functionality.addEdge === true) { if (needSeperator === true) {this.createSeperator(1);} else {needSeperator = true;} this.createAddEdgeButton(locale); } if (selectedNodeCount === 1 && typeof this.options.handlerFunctions.editNode === 'function' && this.options.functionality.editNode === true) { if (needSeperator === true) {this.createSeperator(2);} else {needSeperator = true;} this.createEditNodeButton(locale); } else if (selectedEdgeCount === 1 && selectedNodeCount === 0 && this.options.functionality.editEdge === true) { if (needSeperator === true) {this.createSeperator(3);} else {needSeperator = true;} this.createEditEdgeButton(locale); } // remove buttons if (selectedTotalCount !== 0) { if (selectedNodeCount === 1 && this.options.functionality.deleteNode === true) { if (needSeperator === true) {this.createSeperator(4);} this.createDeleteButton(locale); } else if (selectedNodeCount === 0 && this.options.functionality.deleteEdge === true) { if (needSeperator === true) {this.createSeperator(4);} this.createDeleteButton(locale); } } // bind the close button this.bindHammerToDiv(this.closeDiv, 'toggleEditMode'); // refresh this bar based on what has been selected this.temporaryBind(this.createManipulatorBar,'select'); } /** * Bind an hammer instance to a DOM element. TODO: remove the double check. * @param domElement * @param funct */ bindHammerToDiv(domElement, funct) { let hammer = new Hammer(domElement, {}); hammerUtil.onTouch(hammer, this[funct].bind(this)); this.manipulationHammers.push(hammer); } /** * Create the toolbar for adding Nodes * * @private */ addNodeMode() { // clear the toolbar this._clean(); if (this.guiEnabled === true) { let locale = this.options.locales[this.options.locale]; this.manipulationDOM = {}; this.createBackButton(locale); this.createSeperator(); this.createDescription(locale['addDescription']) // bind the close button this.bindHammerToDiv(this.closeDiv, 'toggleEditMode'); } this.temporaryBind(this._addNode,'click'); } /** * create the toolbar to connect nodes * * @private */ addEdgeMode() { // _clean the system this._clean(); if (this.guiEnabled === true) { let locale = this.options.locales[this.options.locale]; this.manipulationDOM = {}; this.createBackButton(locale); this.createSeperator(); this.createDescription(locale['edgeDescription']); // bind the close button this.bindHammerToDiv(this.closeDiv, 'toggleEditMode'); } // temporarily overload functions this.cachedFunctions["onTouch"] = this.body.eventListeners.onTouch; this.cachedFunctions["onDragEnd"] = this.body.eventListeners.onDragEnd; this.cachedFunctions["onHold"] = this.body.eventListeners.onHold; this.body.eventListeners.onTouch = this._handleConnect.bind(this); this.body.eventListeners.onDragEnd = this._finishConnect.bind(this); this.body.eventListeners.onHold = function () {}; } /** * create the toolbar to edit edges * * @private */ editEdgeMode() { // clear the system this._clean(); if (this.guiEnabled === true) { let locale = this.options.locales[this.options.locale]; this.manipulationDOM = {}; this.createBackButton(locale); this.createSeperator(); this.createDescription(locale['editEdgeDescription']); // bind the close button this.bindHammerToDiv(this.closeDiv, 'toggleEditMode'); } this.edgeBeingEditedId = this.selectionHandler.getSelectedEdges()[0]; let edge = this.body.edges[this.edgeBeingEditedId]; // create control nodes let controlNodeFrom = this.body.functions.createNode(this.getTargetNodeProperties(edge.from.x, edge.from.y)); let controlNodeTo = this.body.functions.createNode(this.getTargetNodeProperties(edge.to.x, edge.to.y)); this.temporaryIds.nodes.push(controlNodeFrom.id); this.temporaryIds.nodes.push(controlNodeTo.id); this.body.nodes[controlNodeFrom.id] = controlNodeFrom; this.body.nodeIndices.push(controlNodeFrom.id); this.body.nodes[controlNodeTo.id] = controlNodeTo; this.body.nodeIndices.push(controlNodeTo.id); // temporarily overload functions this.cachedFunctions['onTouch'] = this.body.eventListeners.onTouch; this.cachedFunctions['onTap'] = this.body.eventListeners.onTap; this.cachedFunctions['onHold'] = this.body.eventListeners.onHold; this.cachedFunctions['onDragStart'] = this.body.eventListeners.onDragStart; this.cachedFunctions['onDrag'] = this.body.eventListeners.onDrag; this.cachedFunctions['onDragEnd'] = this.body.eventListeners.onDragEnd; this.cachedFunctions['onMouseOver'] = this.body.eventListeners.onMouseOver; this.body.eventListeners.onTouch = this._controlNodeTouch.bind(this); this.body.eventListeners.onTap = function() {}; this.body.eventListeners.onHold = function() {}; this.body.eventListeners.onDragStart= this._controlNodeDragStart.bind(this); this.body.eventListeners.onDrag = this._controlNodeDrag.bind(this); this.body.eventListeners.onDragEnd = this._controlNodeDragEnd.bind(this); this.body.eventListeners.onMouseOver= function() {} // create function to position control nodes correctly on movement let positionControlNodes = (ctx) => { let positions = edge.edgeType.findBorderPositions(ctx); if (controlNodeFrom.selected === false) { controlNodeFrom.x = positions.from.x; controlNodeFrom.y = positions.from.y; } if (controlNodeTo.selected === false) { controlNodeTo.x = positions.to.x; controlNodeTo.y = positions.to.y; } } this.temporaryBind(positionControlNodes, "beforeDrawing"); this.body.emitter.emit("_redraw"); } _controlNodeTouch(event) { this.lastTouch = this.body.functions.getPointer(event.center); this.lastTouch.translation = util.extend({},this.body.view.translation); // copy the object } _controlNodeDragStart(event) { let pointer = this.lastTouch; let pointerObj = this.selectionHandler._pointerToPositionObject(pointer); let from = this.body.nodes[this.temporaryIds.nodes[0]]; let to = this.body.nodes[this.temporaryIds.nodes[1]]; let edge = this.body.edges[this.edgeBeingEditedId]; this.selectedControlNode = undefined; let fromSelect = from.isOverlappingWith(pointerObj); let toSelect = to.isOverlappingWith(pointerObj); if (fromSelect === true) { this.selectedControlNode = from; edge.edgeType.from = from; } else if (toSelect === true) { this.selectedControlNode = to; edge.edgeType.to = to; } this.body.emitter.emit("_redraw"); } _controlNodeDrag(event) { this.body.emitter.emit("disablePhysics"); let pointer = this.body.functions.getPointer(event.center); let pos = this.canvas.DOMtoCanvas(pointer); if (this.selectedControlNode !== undefined) { this.selectedControlNode.x = pos.x; this.selectedControlNode.y = pos.y; } else { // if the drag was not started properly because the click started outside the network div, start it now. let diffX = pointer.x - this.lastTouch.x; let diffY = pointer.y - this.lastTouch.y; this.body.view.translation = {x:this.lastTouch.translation.x + diffX, y:this.lastTouch.translation.y + diffY}; } this.body.emitter.emit("_redraw"); } _controlNodeDragEnd(event) { let pointer = this.body.functions.getPointer(event.center); let pointerObj = this.selectionHandler._pointerToPositionObject(pointer); let edge = this.body.edges[this.edgeBeingEditedId]; let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); let node = undefined; for (let i = overlappingNodeIds.length-1; i >= 0; i--) { if (overlappingNodeIds[i] !== this.selectedControlNode.id) { node = this.body.nodes[overlappingNodeIds[i]]; break; } } // perform the connection if (node !== undefined && this.selectedControlNode !== undefined) { if (node.isCluster === true) { alert(this.options.locales[this.options.locale]["createEdgeError"]) } else { let from = this.body.nodes[this.temporaryIds.nodes[0]]; if (this.selectedControlNode.id == from.id) { this._editEdge(node.id, edge.to.id); } else { this._editEdge(edge.from.id, node.id); } } } else { edge.updateEdgeType(); this.body.emitter.emit("restorePhysics"); } this.body.emitter.emit("_redraw"); } /** * the function bound to the selection event. It checks if you want to connect a cluster and changes the description * to walk the user through the process. * * @private */ _selectControlNode(event) { } /** * * @param pointer * @private */ _releaseControlNode(pointer) { if (new Date().valueOf() - this.touchTime > 100) { console.log("release") // perform the connection let node = this.selectionHandler.getNodeAt(pointer); if (node !== undefined) { if (node.isCluster === true) { alert(this.options.locales[this.options.locale]["createEdgeError"]) } else { let edge = this.body.edges[this.edgeBeingEditedId]; let targetNodeId = undefined; if (edge.to.selected === true) { targetNodeId = edge.toId; } else if (edge.from.selected === true) { targetNodeId = edge.fromId; } //this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"]; //this.body.eventListeners.onRelease = this.cachedFunctions["onRelease"]; //delete this.cachedFunctions["onRelease"]; //delete this.cachedFunctions["onDrag"]; //// // // // // // // //if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { // this._createEdge(connectFromId, node.id); //} } } this.body.emitter.emit("_redraw"); //this.body.emitter.emit("_redraw"); //let newNode = this.getNodeAt(pointer); //if (newNode !== undefined) { // if (this.edgeBeingEditedId.controlNodes.from.selected == true) { // this.edgeBeingEditedId._restoreControlNodes(); // this._editEdge(newNode.id, this.edgeBeingEditedId.to.id); // this.edgeBeingEditedId.controlNodes.from.unselect(); // } // if (this.edgeBeingEditedId.controlNodes.to.selected == true) { // this.edgeBeingEditedId._restoreControlNodes(); // this._editEdge(this.edgeBeingEditedId.from.id, newNode.id); // this.edgeBeingEditedId.controlNodes.to.unselect(); // } //} //else { // this.edgeBeingEditedId._restoreControlNodes(); //} this.touchTime = new Date().valueOf(); } } /** * the function bound to the selection event. It checks if you want to connect a cluster and changes the description * to walk the user through the process. * * @private */ _handleConnect(event) { // check to avoid double fireing of this function. if (new Date().valueOf() - this.touchTime > 100) { let pointer = this.body.functions.getPointer(event.center); let node = this.selectionHandler.getNodeAt(pointer); if (node !== undefined) { if (node.isCluster === true) { alert(this.options.locales[this.options.locale]['createEdgeError']) } else { // create a node the temporary line can look at let targetNode = this.body.functions.createNode(this.getTargetNodeProperties(node.x,node.y)); let targetNodeId = targetNode.id; this.body.nodes[targetNode.id] = targetNode; this.body.nodeIndices.push(targetNode.id); // create a temporary edge let connectionEdge = this.body.functions.createEdge({ id: "connectionEdge" + util.randomUUID(), from: node.id, to: targetNode.id, physics:false, smooth: { enabled: true, dynamic: false, type: "continuous", roundness: 0.5 } }); this.body.edges[connectionEdge.id] = connectionEdge; this.body.edgeIndices.push(connectionEdge.id); this.temporaryIds.nodes.push(targetNode.id); this.temporaryIds.edges.push(connectionEdge.id); this.cachedFunctions["onDrag"] = this.body.eventListeners.onDrag; this.body.eventListeners.onDrag = (event) => { let pointer = this.body.functions.getPointer(event.center); let targetNode = this.body.nodes[targetNodeId]; targetNode.x = this.canvas._XconvertDOMtoCanvas(pointer.x); targetNode.y = this.canvas._YconvertDOMtoCanvas(pointer.y); this.body.emitter.emit("_redraw"); } } } this.touchTime = new Date().valueOf(); // do the original touch events this.cachedFunctions["onTouch"](event); } } _finishConnect(event) { let pointer = this.body.functions.getPointer(event.center); let pointerObj = this.selectionHandler._pointerToPositionObject(pointer); // remember the edge id let connectFromId = undefined; if (this.temporaryIds.edges[0] !== undefined) { connectFromId = this.body.edges[this.temporaryIds.edges[0]].fromId; } //restore the drag function if (this.cachedFunctions["onDrag"] !== undefined) { this.body.eventListeners.onDrag = this.cachedFunctions["onDrag"]; delete this.cachedFunctions["onDrag"]; } // get the overlapping node but NOT the temporary node; let overlappingNodeIds = this.selectionHandler._getAllNodesOverlappingWith(pointerObj); let node = undefined; for (let i = overlappingNodeIds.length-1; i >= 0; i--) { if (this.temporaryIds.nodes.indexOf(overlappingNodeIds[i]) !== -1) { node = this.body.nodes[overlappingNodeIds[i]]; break; } } // clean temporary nodes and edges. this._cleanupTemporaryNodesAndEdges(); // perform the connection if (node !== undefined) { if (node.isCluster === true) { alert(this.options.locales[this.options.locale]["createEdgeError"]) } else { if (this.body.nodes[connectFromId] !== undefined && this.body.nodes[node.id] !== undefined) { this._createEdge(connectFromId, node.id); } } } this.body.emitter.emit("_redraw"); } _cleanupTemporaryNodesAndEdges() { // _clean temporary edges for (let i = 0; i < this.temporaryIds.edges.length; i++) { this.body.edges[this.temporaryIds.edges[i]].disconnect(); delete this.body.edges[this.temporaryIds.edges[i]]; let indexTempEdge = this.body.edgeIndices.indexOf(this.temporaryIds.edges[i]); if (indexTempEdge !== -1) {this.body.edgeIndices.splice(indexTempEdge,1);} } // _clean temporary nodes for (let i = 0; i < this.temporaryIds.nodes.length; i++) { delete this.body.nodes[this.temporaryIds.nodes[i]]; let indexTempNode = this.body.nodeIndices.indexOf(this.temporaryIds.nodes[i]); if (indexTempNode !== -1) {this.body.nodeIndices.splice(indexTempNode,1);} } this.temporaryIds = {nodes: [], edges: []}; } /** * Adds a node on the specified location */ _addNode(clickData) { let defaultData = { id: util.randomUUID(), x: clickData.pointer.canvas.x, y: clickData.pointer.canvas.y, label: "new" }; if (typeof this.options.handlerFunctions.addNode === 'function') { if (this.options.handlerFunctions.addNode.length == 2) { this.options.handlerFunctions.addNode(defaultData, (finalizedData) => { this.body.data.nodes.add(finalizedData); this.createManipulatorBar(); }); } else { throw new Error('The function for add does not support two arguments (data,callback)'); this.createManipulatorBar(); } } else { this.body.data.nodes.add(defaultData); this.createManipulatorBar(); } } /** * connect two nodes with a new edge. * * @private */ _createEdge(sourceNodeId, targetNodeId) { let defaultData = {from: sourceNodeId, to: targetNodeId}; if (this.options.handlerFunctions.addEdge) { if (this.options.handlerFunctions.addEdge.length == 2) { this.options.handlerFunctions.addEdge(defaultData, (finalizedData) => { this.body.data.edges.add(finalizedData); this.selectionHandler.unselectAll(); this.createManipulatorBar(); }); } else { throw new Error('The function for connect does not support two arguments (data,callback)'); } } else { this.body.data.edges.add(defaultData); this.selectionHandler.unselectAll(); this.createManipulatorBar(); } } /** * connect two nodes with a new edge. * * @private */ _editEdge(sourceNodeId, targetNodeId) { let defaultData = {id: this.edgeBeingEditedId, from: sourceNodeId, to: targetNodeId}; console.log(defaultData) if (this.options.handlerFunctions.editEdge) { if (this.options.handlerFunctions.editEdge.length == 2) { this.options.handlerFunctions.editEdge(defaultData, (finalizedData) => { this.body.data.edges.update(finalizedData); this.selectionHandler.unselectAll(); this.createManipulatorBar(); }); } else { throw new Error('The function for edit does not support two arguments (data, callback)'); } } else { this.body.data.edges.update(defaultData); this.selectionHandler.unselectAll(); this.createManipulatorBar(); } } /** * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color. * * @private */ _editNode() { if (this.options.handlerFunctions.edit && this.editMode == true) { let node = this._getSelectedNode(); let data = { id: node.id, label: node.label, group: node.options.group, shape: node.options.shape, color: { background: node.options.color.background, border: node.options.color.border, highlight: { background: node.options.color.highlight.background, border: node.options.color.highlight.border } } }; if (this.options.handlerFunctions.edit.length == 2) { let me = this; this.options.handlerFunctions.edit(data, function (finalizedData) { me.body.data.nodes.update(finalizedData); me.createManipulatorBar(); me.moving = true; me.start(); }); } else { throw new Error('The function for edit does not support two arguments (data, callback)'); } } else { throw new Error('No edit function has been bound to this button'); } } /** * delete everything in the selection * * @private */ deleteSelected() { let selectedNodes = this.selectionHandler.getSelectedNodes(); let selectedEdges = this.selectionHandler.getSelectedEdges(); let deleteFunction = undefined; if (selectedNodes.length > 0) { for (let i = 0; i < selectedNodes.length; i++) { if (this.body.nodes[selectedNodes[i]].isCluster === true) { alert("You cannot delete a cluster."); return; } } if (typeof this.options.handlerFunctions.deleteNode === 'function') { deleteFunction = this.options.handlerFunctions.deleteNode; } } else if (selectedEdges.length > 0) { if (typeof this.options.handlerFunctions.deleteEdge === 'function') { deleteFunction = this.options.handlerFunctions.deleteEdge; } } if (typeof deleteFunction === 'function') { let data = {nodes: selectedNodes, edges: selectedEdges}; if (deleteFunction.length == 2) { deleteFunction(data, (finalizedData) => { this.body.data.edges.remove(finalizedData.edges); this.body.data.nodes.remove(finalizedData.nodes); this.body.emitter.emit("startSimulation"); }); } else { throw new Error('The function for delete does not support two arguments (data, callback)') } } else { this.body.data.edges.remove(selectedEdges); this.body.data.nodes.remove(selectedNodes); this.body.emitter.emit("startSimulation"); } } getTargetNodeProperties(x,y) { return { id: 'targetNode' + util.randomUUID(), hidden: false, physics: false, shape:'dot', size:6, x:x, y:y, color: {background: '#ff0000', border: '#3c3c3c', highlight: {background: '#07f968'}}, borderWidth: 2, borderWidthSelected: 2 } } } export default ManipulationSystem;