From f06d8c7c542590ff4cf13b20f0012fe7fb560459 Mon Sep 17 00:00:00 2001 From: azivner Date: Wed, 31 Oct 2018 23:55:14 +0100 Subject: [PATCH] multiselection with keyboard doesn't trigger activation which helps with performance --- .../javascripts/services/note_detail.js | 4 +- .../services/note_detail_relation_map.js | 67 ++++++++++--------- src/public/javascripts/services/tree.js | 9 +++ .../javascripts/services/tree_keybindings.js | 16 +++-- src/public/libraries/jsplumb.js | 41 +++++++----- 5 files changed, 83 insertions(+), 54 deletions(-) diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 2f568e6c2..3fc78dadc 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -175,9 +175,11 @@ async function loadNoteDetail(noteId) { return; } + // only now that we're in sync with tree active node we will switch currentNote currentNote = loadedNote; - refreshAttributes(); // needs to happend after loading the note itself because it references current noteId + // needs to happend after loading the note itself because it references current noteId + refreshAttributes(); if (isNewNoteCreated) { isNewNoteCreated = false; diff --git a/src/public/javascripts/services/note_detail_relation_map.js b/src/public/javascripts/services/note_detail_relation_map.js index fc29e26d9..3673705cb 100644 --- a/src/public/javascripts/services/note_detail_relation_map.js +++ b/src/public/javascripts/services/note_detail_relation_map.js @@ -12,9 +12,10 @@ const $zoomInButton = $("#relation-map-zoom-in"); const $zoomOutButton = $("#relation-map-zoom-out"); let mapData; -let instance; +let jsPlumbInstance; // outside of mapData because they are not persisted in the note content let relations; +let pzInstance; const uniDirectionalOverlays = [ [ "Arrow", { @@ -95,7 +96,7 @@ async function loadNotesAndRelations() { mapData.notes = mapData.notes.filter(note => note.id in data.noteTitles); - instance.batch(async function () { + jsPlumbInstance.batch(async function () { for (const note of mapData.notes) { const title = data.noteTitles[note.id]; @@ -107,7 +108,7 @@ async function loadNotesAndRelations() { continue; } - const connection = instance.connect({ + const connection = jsPlumbInstance.connect({ source: relation.sourceNoteId, target: relation.targetNoteId, type: relation.type @@ -121,37 +122,37 @@ async function loadNotesAndRelations() { } function initPanZoom() { - const pz = panzoom($relationMapCanvas[0], { + pzInstance = panzoom($relationMapCanvas[0], { maxZoom: 2, minZoom: 0.1, smoothScroll: false }); if (mapData.transform) { - pz.zoomTo(0, 0, mapData.transform.scale); - pz.moveTo(mapData.transform.x, mapData.transform.y); + pzInstance.zoomTo(0, 0, mapData.transform.scale); + pzInstance.moveTo(mapData.transform.x, mapData.transform.y); } - pz.on('zoom', function (e) { - mapData.transform = pz.getTransform(); + pzInstance.on('zoom', function (e) { + mapData.transform = pzInstance.getTransform(); saveData(); }); - pz.on('panend', function (e) { - mapData.transform = pz.getTransform(); + pzInstance.on('panend', function (e) { + mapData.transform = pzInstance.getTransform(); saveData(); }, true); - $zoomInButton.click(() => pz.zoomTo(0, 0, 1.2)); - $zoomOutButton.click(() => pz.zoomTo(0, 0, 0.8)); + $zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2)); + $zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8)); } function cleanup() { - if (instance) { + if (jsPlumbInstance) { // delete all endpoints and connections - instance.deleteEveryEndpoint(); + jsPlumbInstance.deleteEveryEndpoint(); // without this we still end up with note boxes remaining in the canvas $relationMapCanvas.empty(); @@ -159,13 +160,13 @@ function cleanup() { } function initJsPlumbInstance () { - if (instance) { + if (jsPlumbInstance) { cleanup(); return; } - instance = jsPlumb.getInstance({ + jsPlumbInstance = jsPlumb.getInstance({ Endpoint: ["Dot", {radius: 2}], Connector: "StateMachine", ConnectionOverlays: uniDirectionalOverlays, @@ -173,11 +174,11 @@ function initJsPlumbInstance () { Container: "relation-map-canvas" }); - instance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); + jsPlumbInstance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); - instance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); + jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); - instance.bind("connection", connectionCreatedHandler); + jsPlumbInstance.bind("connection", connectionCreatedHandler); $relationMapCanvas.contextmenu({ delegate: ".note-box", @@ -199,7 +200,7 @@ function initJsPlumbInstance () { select: relationContextMenuHandler }); - instance.bind("contextmenu", function (c, e) { + jsPlumbInstance.bind("contextmenu", function (c, e) { e.preventDefault(); $relationMapCanvas.contextmenuRelation("open", e, { connection: c }); @@ -221,7 +222,7 @@ async function connectionCreatedHandler(info, originalEvent) { const name = prompt("Specify new relation name:"); if (!name || !name.trim()) { - instance.deleteConnection(connection); + jsPlumbInstance.deleteConnection(connection); return; } @@ -237,7 +238,7 @@ async function connectionCreatedHandler(info, originalEvent) { if (relationExists) { alert("Connection '" + name + "' between these notes already exists."); - instance.deleteConnection(connection); + jsPlumbInstance.deleteConnection(connection); return; } @@ -262,7 +263,7 @@ async function relationContextMenuHandler(event, ui) { await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`); - instance.deleteConnection(connection); + jsPlumbInstance.deleteConnection(connection); relations = relations.filter(relation => relation.attributeId !== connection.id); } @@ -277,7 +278,7 @@ async function noteContextMenuHandler(event, ui) { return; } - instance.remove(noteId); + jsPlumbInstance.remove(noteId); mapData.notes = mapData.notes.filter(note => note.id !== noteId); @@ -314,9 +315,9 @@ async function createNoteBox(id, title, x, y) { .css("left", x + "px") .css("top", y + "px"); - instance.getContainer().appendChild($noteBox[0]); + jsPlumbInstance.getContainer().appendChild($noteBox[0]); - instance.draggable($noteBox[0], { + jsPlumbInstance.draggable($noteBox[0], { start:function(params) {}, drag:function(params) {}, stop:function(params) { @@ -333,7 +334,7 @@ async function createNoteBox(id, title, x, y) { } }); - instance.makeSource($noteBox[0], { + jsPlumbInstance.makeSource($noteBox[0], { filter: ".endpoint", anchor: "Continuous", connectorStyle: { stroke: "#000", strokeWidth: 1 }, @@ -343,7 +344,7 @@ async function createNoteBox(id, title, x, y) { } }); - instance.makeTarget($noteBox[0], { + jsPlumbInstance.makeTarget($noteBox[0], { dropOptions: { hoverClass: "dragHover" }, anchor: "Continuous", allowLoopback: true @@ -385,11 +386,13 @@ $addChildNotesButton.click(async () => { saveData(); // delete all endpoints and connections - instance.deleteEveryEndpoint(); + jsPlumbInstance.deleteEveryEndpoint(); await loadNotesAndRelations(); }); +let clipboard = null; + $createChildNote.click(async () => { const title = prompt("Enter title of new note", "new note"); @@ -410,9 +413,13 @@ $createChildNote.click(async () => { mapData.notes.push({ id: note.noteId, x, y }); - await createNoteBox(note.noteId, title, x, y); + clipboard = { id: note.noteId, title }; + +// await createNoteBox(note.noteId, title, x, y); }); + + export default { show, getContent: () => JSON.stringify(mapData), diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 3fd9be332..dd38b5baa 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -24,6 +24,14 @@ const $notePathCount = $("#note-path-count"); let startNotePath = null; +// focused & not active node can happen during multiselection where the node is selected but not activated +// (its content is not displayed in the detail) +function getFocusedNode() { + const tree = $tree.fancytree("getTree"); + + return tree.getFocusNode(); +} + // note that if you want to access data like noteId or isProtected, you need to go into "data" property function getCurrentNode() { return $tree.fancytree("getActiveNode"); @@ -615,6 +623,7 @@ export default { setProtected, expandToNote, activateNote, + getFocusedNode, getCurrentNode, getCurrentNotePath, setCurrentNotePathToHash, diff --git a/src/public/javascripts/services/tree_keybindings.js b/src/public/javascripts/services/tree_keybindings.js index ec3d6b87a..03d0138d4 100644 --- a/src/public/javascripts/services/tree_keybindings.js +++ b/src/public/javascripts/services/tree_keybindings.js @@ -40,9 +40,11 @@ const keyBindings = { return false; }, - "shift+up": node => { - node.navigate($.ui.keyCode.UP, true).then(() => { - const currentNode = treeService.getCurrentNode(); + "shift+up": () => { + const node = treeService.getFocusedNode(); + + node.navigate($.ui.keyCode.UP, false).then(() => { + const currentNode = treeService.getFocusedNode(); if (currentNode.isSelected()) { node.setSelected(false); @@ -53,9 +55,11 @@ const keyBindings = { return false; }, - "shift+down": node => { - node.navigate($.ui.keyCode.DOWN, true).then(() => { - const currentNode = treeService.getCurrentNode(); + "shift+down": () => { + const node = treeService.getFocusedNode(); + + node.navigate($.ui.keyCode.DOWN, false).then(() => { + const currentNode = treeService.getFocusedNode(); if (currentNode.isSelected()) { node.setSelected(false); diff --git a/src/public/libraries/jsplumb.js b/src/public/libraries/jsplumb.js index 374a01e74..e34e8c334 100644 --- a/src/public/libraries/jsplumb.js +++ b/src/public/libraries/jsplumb.js @@ -1537,7 +1537,7 @@ this._class = css.draggable; var k = Super.apply(this, arguments); this.rightButtonCanDrag = this.params.rightButtonCanDrag; - var downAt = [0,0], posAtDown = null, pagePosAtDown = null, pageDelta = [0,0], moving = false, + var downAt = [0,0], posAtDown = null, pagePosAtDown = null, pageDelta = [0,0], moving = false, initialScroll = [0,0], consumeStartEvent = this.params.consumeStartEvent !== false, dragEl = this.el, clone = this.params.clone, @@ -1729,6 +1729,10 @@ consumeStartEvent && _consume(e); downAt = _pl(e); + if (dragEl && dragEl.parentNode) + { + initialScroll = [dragEl.parentNode.scrollLeft, dragEl.parentNode.scrollTop]; + } // this.params.bind(document, "mousemove", this.moveListener); this.params.bind(document, "mouseup", this.upListener); @@ -1764,6 +1768,11 @@ intersectingDroppables.length = 0; var pos = _pl(e), dx = pos[0] - downAt[0], dy = pos[1] - downAt[1], z = this.params.ignoreZoom ? 1 : k.getZoom(); + if (dragEl && dragEl.parentNode) + { + dx += dragEl.parentNode.scrollLeft - initialScroll[0]; + dy += dragEl.parentNode.scrollTop - initialScroll[1]; + } dx /= z; dy /= z; this.moveBy(dx, dy, e); @@ -1783,7 +1792,11 @@ k.unmarkSelection(this, e); k.unmarkPosses(this, e); this.stop(e); - k.notifySelectionDragStop(this, e); + + //k.notifySelectionDragStop(this, e); removed in 1.1.0 under the "leave it for one release in case it breaks" rule. + // it isnt necessary to fire this as the normal stop event now includes a `selection` member that has every dragged element. + // firing this event causes consumers who use the `selection` array to process a lot more drag stop events than is necessary + k.notifyPosseDragStop(this, e); moving = false; if (clone) { @@ -3732,7 +3745,7 @@ var jsPlumbInstance = root.jsPlumbInstance = function (_defaults) { - this.version = "2.8.0"; + this.version = "2.8.3"; this.Defaults = { Anchor: "Bottom", @@ -8727,6 +8740,9 @@ _jsPlumb.finaliseConnection(jpc, null, originalEvent, false); jpc.setHover(false); + // SP continuous anchor flush + _jsPlumb.revalidate(jpc.endpoints[0].element); + }.bind(this); var dontContinueFunction = function () { @@ -9443,7 +9459,6 @@ _jp.AnchorManager = function (params) { var _amEndpoints = {}, continuousAnchorLocations = {}, - userDefinedContinuousAnchorLocations = {}, continuousAnchorOrientations = {}, connectionsByElementId = {}, self = this, @@ -9949,8 +9964,6 @@ } // now that continuous anchors have been placed, paint all the endpoints for this element - // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next - // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. for (i = 0; i < ep.length; i++) { ep[i].paint({ timestamp: timestamp, offset: myOffset, dimensions: myOffset.s, recalc: doNotRecalcEndpoint !== true }); } @@ -9958,7 +9971,8 @@ // ... and any other endpoints we came across as a result of the continuous anchors. for (i = 0; i < endpointsToPaint.length; i++) { var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId); - endpointsToPaint[i].paint({ timestamp: timestamp, offset: cd, dimensions: cd.s }); + //endpointsToPaint[i].paint({ timestamp: timestamp, offset: cd, dimensions: cd.s }); + endpointsToPaint[i].paint({ timestamp: null, offset: cd, dimensions: cd.s }); } // paint all the standard and "dynamic connections", which are connections whose other anchor is @@ -9995,7 +10009,7 @@ // paint all the connections for (i = 0; i < connectionsToPaint.length; i++) { - connectionsToPaint[i].paint({elId: elementId, timestamp: timestamp, recalc: false, clearEdits: clearEdits}); + connectionsToPaint[i].paint({elId: elementId, timestamp: null, recalc: false, clearEdits: clearEdits}); } } }; @@ -10094,20 +10108,14 @@ }; this.compute = function (params) { - return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0]; + return continuousAnchorLocations[params.element.id] || [0, 0]; }; this.getCurrentLocation = function (params) { - return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0, 0]; + return continuousAnchorLocations[params.element.id] || [0, 0]; }; this.getOrientation = function (endpoint) { return continuousAnchorOrientations[endpoint.id] || [0, 0]; }; - this.clearUserDefinedLocation = function () { - delete userDefinedContinuousAnchorLocations[anchorParams.elementId]; - }; - this.setUserDefinedLocation = function (loc) { - userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc; - }; this.getCssClass = function () { return cssClass; }; @@ -10119,7 +10127,6 @@ return new ContinuousAnchor(params); }, clear: function (elementId) { - delete userDefinedContinuousAnchorLocations[elementId]; delete continuousAnchorLocations[elementId]; } };