diff --git a/libraries/jsplumb.js b/libraries/jsplumb.js index 0e62651ac..21e2fa406 100644 --- a/libraries/jsplumb.js +++ b/libraries/jsplumb.js @@ -2104,14 +2104,9 @@ sel = k.getSelection(), dPos = this.params.getPosition(dragEl); - if (sel.length > 0) { - for (var i = 0; i < sel.length; i++) { - var p = this.params.getPosition(sel[i].el); - positions.push([ sel[i].el, { left: p[0], top: p[1] }, sel[i] ]); - } - } - else { - positions.push([ dragEl, {left:dPos[0], top:dPos[1]}, this ]); + for (var i = 0; i < sel.length; i++) { + var p = this.params.getPosition(sel[i].el); + positions.push([ sel[i].el, { left: p[0], top: p[1] }, sel[i] ]); } _dispatch("stop", { @@ -3540,6 +3535,231 @@ }).call(typeof window !== 'undefined' ? window : this); +;(function() { + + var DEFAULT_OPTIONS = { + deriveAnchor:function(edge, index, ep, conn) { + return { + top:["TopRight", "TopLeft"], + bottom:["BottomRight", "BottomLeft"] + }[edge][index]; + } + }; + + var root = this; + + var ListManager = function(jsPlumbInstance) { + + this.count = 0; + this.instance = jsPlumbInstance; + this.lists = {}; + + this.instance.addList = function(el, options) { + return this.listManager.addList(el, options); + }; + + this.instance.removeList = function(el) { + this.listManager.removeList(el); + }; + + this.instance.bind("manageElement", function(p) { + + //look for [jtk-scrollable-list] elements and attach scroll listeners if necessary + var scrollableLists = this.instance.getSelector(p.el, "[jtk-scrollable-list]"); + for (var i = 0; i < scrollableLists.length; i++) { + this.addList(scrollableLists[i]); + } + + }.bind(this)); + + this.instance.bind("unmanageElement", function(p) { + this.removeList(p.el); + }); + + + this.instance.bind("connection", function(c, evt) { + if (evt == null) { + // not added by mouse. look for an ancestor of the source and/or target element that is a scrollable list, and run + // its scroll method. + this._maybeUpdateParentList(c.source); + this._maybeUpdateParentList(c.target); + } + }.bind(this)); + }; + + root.jsPlumbListManager = ListManager; + + ListManager.prototype = { + + addList : function(el, options) { + var dp = this.instance.extend({}, DEFAULT_OPTIONS); + options = this.instance.extend(dp, options || {}); + var id = [this.instance.getInstanceIndex(), this.count++].join("_"); + this.lists[id] = new List(this.instance, el, options, id); + }, + + removeList:function(el) { + var list = this.lists[el._jsPlumbList]; + if (list) { + list.destroy(); + delete this.lists[el._jsPlumbList]; + } + }, + + _maybeUpdateParentList:function (el) { + var parent = el.parentNode, container = this.instance.getContainer(); + while(parent != null && parent !== container) { + if (parent._jsPlumbList != null && this.lists[parent._jsPlumbList] != null) { + parent._jsPlumbScrollHandler(); + return + } + parent = parent.parentNode; + } + } + + + }; + + var List = function(instance, el, options, id) { + + el["_jsPlumbList"] = id; + + // + // Derive an anchor to use for the current situation. In contrast to the way we derive an endpoint, here we use `anchor` from the options, if present, as + // our first choice, and then `deriveAnchor` as our next choice. There is a default `deriveAnchor` implementation that uses TopRight/TopLeft for top and + // BottomRight/BottomLeft for bottom. + // + // edge - "top" or "bottom" + // index - 0 when endpoint is connection source, 1 when endpoint is connection target + // ep - the endpoint that is being proxied + // conn - the connection that is being proxied + // + function deriveAnchor(edge, index, ep, conn) { + return options.anchor ? options.anchor : options.deriveAnchor(edge, index, ep, conn); + } + + // + // Derive an endpoint to use for the current situation. We'll use a `deriveEndpoint` function passed in to the options as our first choice, + // followed by `endpoint` (an endpoint spec) from the options, and failing either of those we just use the `type` of the endpoint that is being proxied. + // + // edge - "top" or "bottom" + // index - 0 when endpoint is connection source, 1 when endpoint is connection target + // endpoint - the endpoint that is being proxied + // connection - the connection that is being proxied + // + function deriveEndpoint(edge, index, ep, conn) { + return options.deriveEndpoint ? options.deriveEndpoint(edge, index, ep, conn) : options.endpoint ? options.endpoint : ep.type; + } + + // + // look for a parent of the given scrollable list that is draggable, and then update the child offsets for it. this should not + // be necessary in the delegated drag stuff from the upcoming 3.0.0 release. + // + function _maybeUpdateDraggable(el) { + var parent = el.parentNode, container = instance.getContainer(); + while(parent != null && parent !== container) { + if (instance.hasClass(parent, "jtk-managed")) { + instance.recalculateOffsets(parent); + return + } + parent = parent.parentNode; + } + } + + var scrollHandler = function(e) { + + var children = instance.getSelector(el, ".jtk-managed"); + var elId = instance.getId(el); + + for (var i = 0; i < children.length; i++) { + + if (children[i].offsetTop < el.scrollTop) { + if (!children[i]._jsPlumbProxies) { + children[i]._jsPlumbProxies = children[i]._jsPlumbProxies || []; + instance.select({source: children[i]}).each(function (c) { + + + instance.proxyConnection(c, 0, el, elId, function () { + return deriveEndpoint("top", 0, c.endpoints[0], c); + }, function () { + return deriveAnchor("top", 0, c.endpoints[0], c); + }); + children[i]._jsPlumbProxies.push([c, 0]); + }); + + instance.select({target: children[i]}).each(function (c) { + instance.proxyConnection(c, 1, el, elId, function () { + return deriveEndpoint("top", 1, c.endpoints[1], c); + }, function () { + return deriveAnchor("top", 1, c.endpoints[1], c); + }); + children[i]._jsPlumbProxies.push([c, 1]); + }); + } + } + // + else if (children[i].offsetTop > el.scrollTop + el.offsetHeight) { + if (!children[i]._jsPlumbProxies) { + children[i]._jsPlumbProxies = children[i]._jsPlumbProxies || []; + + instance.select({source: children[i]}).each(function (c) { + instance.proxyConnection(c, 0, el, elId, function () { + return deriveEndpoint("bottom", 0, c.endpoints[0], c); + }, function () { + return deriveAnchor("bottom", 0, c.endpoints[0], c); + }); + children[i]._jsPlumbProxies.push([c, 0]); + }); + + instance.select({target: children[i]}).each(function (c) { + instance.proxyConnection(c, 1, el, elId, function () { + return deriveEndpoint("bottom", 1, c.endpoints[1], c); + }, function () { + return deriveAnchor("bottom", 1, c.endpoints[1], c); + }); + children[i]._jsPlumbProxies.push([c, 1]); + }); + } + } else if (children[i]._jsPlumbProxies) { + for (var j = 0; j < children[i]._jsPlumbProxies.length; j++) { + instance.unproxyConnection(children[i]._jsPlumbProxies[j][0], children[i]._jsPlumbProxies[j][1], elId); + } + + delete children[i]._jsPlumbProxies; + } + + instance.revalidate(children[i]); + } + + _maybeUpdateDraggable(el); + }; + + instance.setAttribute(el, "jtk-scrollable-list", "true"); + el._jsPlumbScrollHandler = scrollHandler; + instance.on(el, "scroll", scrollHandler); + scrollHandler(); // run it once; there may be connections already. + + this.destroy = function() { + instance.off(el, "scroll", scrollHandler); + delete el._jsPlumbScrollHandler; + + var children = instance.getSelector(el, ".jtk-managed"); + var elId = instance.getId(el); + + for (var i = 0; i < children.length; i++) { + if (children[i]._jsPlumbProxies) { + for (var j = 0; j < children[i]._jsPlumbProxies.length; j++) { + instance.unproxyConnection(children[i]._jsPlumbProxies[j][0], children[i]._jsPlumbProxies[j][1], elId); + } + + delete children[i]._jsPlumbProxies; + } + } + }; + }; + + +}).call(typeof window !== 'undefined' ? window : this); /* * This file contains the core code. * @@ -4006,7 +4226,7 @@ var jsPlumbInstance = root.jsPlumbInstance = function (_defaults) { - this.version = "2.9.3"; + this.version = "2.10.0"; this.Defaults = { Anchor: "Bottom", @@ -5470,6 +5690,7 @@ managedElements[id].info = _updateOffset({ elId: id, timestamp: _suspendedAt }); _currentInstance.addClass(element, "jtk-managed"); + if (!_transient) { _currentInstance.fire("manageElement", { id:id, info:managedElements[id].info, el:element }); } @@ -5480,9 +5701,10 @@ var _unmanage = _currentInstance.unmanage = function(id) { if (managedElements[id]) { - _currentInstance.removeClass(managedElements[id].el, "jtk-managed"); + var el = managedElements[id].el; + _currentInstance.removeClass(el, "jtk-managed"); delete managedElements[id]; - _currentInstance.fire("unmanageElement", id); + _currentInstance.fire("unmanageElement", {id:id, el:el}); } }; @@ -6619,6 +6841,8 @@ this.getFloatingConnectionFor = function(id) { return floatingConnections[id]; }; + + this.listManager = new root.jsPlumbListManager(this); }; _ju.extend(root.jsPlumbInstance, _ju.EventGenerator, { @@ -6709,6 +6933,86 @@ floatingConnections: {}, getFloatingAnchorIndex: function (jpc) { return jpc.endpoints[0].isFloating() ? 0 : jpc.endpoints[1].isFloating() ? 1 : -1; + }, + proxyConnection :function(connection, index, proxyEl, proxyElId, endpointGenerator, anchorGenerator) { + var proxyEp, + originalElementId = connection.endpoints[index].elementId, + originalEndpoint = connection.endpoints[index]; + + connection.proxies = connection.proxies || []; + if(connection.proxies[index]) { + proxyEp = connection.proxies[index].ep; + }else { + proxyEp = this.addEndpoint(proxyEl, { + endpoint:endpointGenerator(connection, index), + anchor:anchorGenerator(connection, index), + parameters:{ + isProxyEndpoint:true + } + }); + } + proxyEp.setDeleteOnEmpty(true); + + // for this index, stash proxy info: the new EP, the original EP. + connection.proxies[index] = { ep:proxyEp, originalEp: originalEndpoint }; + + // and advise the anchor manager + if (index === 0) { + // TODO why are there two differently named methods? Why is there not one method that says "some end of this + // connection changed (you give the index), and here's the new element and element id." + this.anchorManager.sourceChanged(originalElementId, proxyElId, connection, proxyEl); + } + else { + this.anchorManager.updateOtherEndpoint(connection.endpoints[0].elementId, originalElementId, proxyElId, connection); + connection.target = proxyEl; + connection.targetId = proxyElId; + } + + // detach the original EP from the connection. + originalEndpoint.detachFromConnection(connection, null, true); + + // set the proxy as the new ep + proxyEp.connections = [ connection ]; + connection.endpoints[index] = proxyEp; + + originalEndpoint.setVisible(false); + + connection.setVisible(true); + + this.revalidate(proxyEl); + }, + unproxyConnection : function(connection, index, proxyElId) { + // if connection cleaned up, no proxies, or none for this end of the connection, abort. + if (connection._jsPlumb == null || connection.proxies == null || connection.proxies[index] == null) { + return; + } + + var originalElement = connection.proxies[index].originalEp.element, + originalElementId = connection.proxies[index].originalEp.elementId; + + connection.endpoints[index] = connection.proxies[index].originalEp; + // and advise the anchor manager + if (index === 0) { + // TODO why are there two differently named methods? Why is there not one method that says "some end of this + // connection changed (you give the index), and here's the new element and element id." + this.anchorManager.sourceChanged(proxyElId, originalElementId, connection, originalElement); + } + else { + this.anchorManager.updateOtherEndpoint(connection.endpoints[0].elementId, proxyElId, originalElementId, connection); + connection.target = originalElement; + connection.targetId = originalElementId; + } + + // detach the proxy EP from the connection (which will cause it to be removed as we no longer need it) + connection.proxies[index].ep.detachFromConnection(connection, null); + + connection.proxies[index].originalEp.addConnection(connection); + if(connection.isVisible()) { + connection.proxies[index].originalEp.setVisible(true); + } + + // cleanup + delete connection.proxies[index]; } }); @@ -10183,15 +10487,15 @@ }, _path = function (segments) { var anchorsPerFace = anchorCount / segments.length, a = [], - _computeFace = function (x1, y1, x2, y2, fractionalLength) { + _computeFace = function (x1, y1, x2, y2, fractionalLength, ox, oy) { anchorsPerFace = anchorCount * fractionalLength; var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; for (var i = 0; i < anchorsPerFace; i++) { a.push([ x1 + (dx * i), y1 + (dy * i), - 0, - 0 + ox == null ? 0 : ox, + oy == null ? 0 : oy ]); } }; @@ -10205,16 +10509,16 @@ _shape = function (faces) { var s = []; for (var i = 0; i < faces.length; i++) { - s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]); + s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length, faces[i][4], faces[i][5]]); } return _path(s); }, _rectangle = function () { return _shape([ - [ 0, 0, 1, 0 ], - [ 1, 0, 1, 1 ], - [ 1, 1, 0, 1 ], - [ 0, 1, 0, 0 ] + [ 0, 0, 1, 0, 0, -1 ], + [ 1, 0, 1, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, -1, 0 ] ]); }; @@ -12115,11 +12419,11 @@ c.setVisible(false); if (c.endpoints[oidx].element._jsPlumbGroup === group) { c.endpoints[oidx].setVisible(false); - self.expandConnection(c, oidx, group); + _expandConnection(c, oidx, group); } else { c.endpoints[index].setVisible(false); - self.collapseConnection(c, index, group); + _collapseConnection(c, index, group); } }); }; @@ -12203,54 +12507,16 @@ } } - var _collapseConnection = this.collapseConnection = function(c, index, group) { - - var proxyEp, groupEl = group.getEl(), groupElId = _jsPlumb.getId(groupEl), - originalElementId = c.endpoints[index].elementId; + var _collapseConnection = function(c, index, group) { var otherEl = c.endpoints[index === 0 ? 1 : 0].element; if (otherEl[GROUP] && (!otherEl[GROUP].shouldProxy() && otherEl[GROUP].collapsed)) { return; } - c.proxies = c.proxies || []; - if(c.proxies[index]) { - proxyEp = c.proxies[index].ep; - }else { - proxyEp = _jsPlumb.addEndpoint(groupEl, { - endpoint:group.getEndpoint(c, index), - anchor:group.getAnchor(c, index), - parameters:{ - isProxyEndpoint:true - } - }); - } - proxyEp.setDeleteOnEmpty(true); + var groupEl = group.getEl(), groupElId = _jsPlumb.getId(groupEl); - // for this index, stash proxy info: the new EP, the original EP. - c.proxies[index] = { ep:proxyEp, originalEp: c.endpoints[index] }; - - // and advise the anchor manager - if (index === 0) { - // TODO why are there two differently named methods? Why is there not one method that says "some end of this - // connection changed (you give the index), and here's the new element and element id." - _jsPlumb.anchorManager.sourceChanged(originalElementId, groupElId, c, groupEl); - } - else { - _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, originalElementId, groupElId, c); - c.target = groupEl; - c.targetId = groupElId; - } - - - // detach the original EP from the connection. - c.proxies[index].originalEp.detachFromConnection(c, null, true); - - // set the proxy as the new ep - proxyEp.connections = [ c ]; - c.endpoints[index] = proxyEp; - - c.setVisible(true); + _jsPlumb.proxyConnection(c, index, groupEl, groupElId, function(c, index) { return group.getEndpoint(c, index); }, function(c, index) { return group.getAnchor(c, index); }); }; this.collapseGroup = function(group) { @@ -12287,37 +12553,8 @@ _jsPlumb.fire(EVT_COLLAPSE, { group:group }); }; - var _expandConnection = this.expandConnection = function(c, index, group) { - - // if no proxies or none for this end of the connection, abort. - if (c.proxies == null || c.proxies[index] == null) { - return; - } - - var groupElId = _jsPlumb.getId(group.getEl()), - originalElement = c.proxies[index].originalEp.element, - originalElementId = c.proxies[index].originalEp.elementId; - - c.endpoints[index] = c.proxies[index].originalEp; - // and advise the anchor manager - if (index === 0) { - // TODO why are there two differently named methods? Why is there not one method that says "some end of this - // connection changed (you give the index), and here's the new element and element id." - _jsPlumb.anchorManager.sourceChanged(groupElId, originalElementId, c, originalElement); - } - else { - _jsPlumb.anchorManager.updateOtherEndpoint(c.endpoints[0].elementId, groupElId, originalElementId, c); - c.target = originalElement; - c.targetId = originalElementId; - } - - // detach the proxy EP from the connection (which will cause it to be removed as we no longer need it) - c.proxies[index].ep.detachFromConnection(c, null); - - c.proxies[index].originalEp.addConnection(c); - - // cleanup - delete c.proxies[index]; + var _expandConnection = function(c, index, group) { + _jsPlumb.unproxyConnection(c, index, _jsPlumb.getId(group.getEl())); }; this.expandGroup = function(group, doNotFireEvent) { diff --git a/src/public/javascripts/dialogs/link_map.js b/src/public/javascripts/dialogs/link_map.js index fae79d6eb..9c6ed4062 100644 --- a/src/public/javascripts/dialogs/link_map.js +++ b/src/public/javascripts/dialogs/link_map.js @@ -101,34 +101,58 @@ async function loadNotesAndRelations() { 0.5 // Damping ); + function getNoteBox(noteId) { + const noteBoxId = noteIdToId(noteId); + const $existingNoteBox = $("#" + noteBoxId); + + if ($existingNoteBox.length > 0) { + return $existingNoteBox; + } + + const note = notes.find(n => n.noteId === noteId); + + const $noteBox = $("