diff --git a/libraries/springy.js b/libraries/springy.js index 65ad9fecf..5b0bca1a3 100644 --- a/libraries/springy.js +++ b/libraries/springy.js @@ -191,7 +191,7 @@ this.addNodes.apply(this, json['nodes']); this.addEdges.apply(this, json['edges']); } - } + }; // find the edges from node1 to node2 @@ -479,51 +479,35 @@ return energy; }; - var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-) - - Springy.requestAnimationFrame = __bind(this.requestAnimationFrame || - this.webkitRequestAnimationFrame || - this.mozRequestAnimationFrame || - this.oRequestAnimationFrame || - this.msRequestAnimationFrame || - (function(callback, element) { - this.setTimeout(callback, 10); - }), this); - - /** * Start simulation if it's not running already. * In case it's running then the call is ignored, and none of the callbacks passed is ever executed. */ - Layout.ForceDirected.prototype.start = function(render, onRenderStop, onRenderStart) { + Layout.ForceDirected.prototype.start = function(onRenderStop) { var t = this; if (this._started) return; this._started = true; this._stop = false; - if (onRenderStart !== undefined) { onRenderStart(); } - - Springy.requestAnimationFrame(function step() { + function step() { t.tick(0.03); - if (!t._stop && render !== undefined) { - render(); - } - // stop simulation when energy of the system goes below a threshold if (t._stop || t.totalEnergy() < t.minEnergyThreshold) { t._started = false; - if (onRenderStop !== undefined) { onRenderStop(); } + onRenderStop(); } else { - Springy.requestAnimationFrame(step); + setImmediate(step); } - }); + } + + step(); }; Layout.ForceDirected.prototype.stop = function() { this._stop = true; - } + }; Layout.ForceDirected.prototype.tick = function(timestep) { this.applyCoulombsLaw(); @@ -644,85 +628,35 @@ /** * Renderer handles the layout rendering loop - * @param onRenderStop optional callback function that gets executed whenever rendering stops. - * @param onRenderStart optional callback function that gets executed whenever rendering starts. - * @param onRenderFrame optional callback function that gets executed after each frame is rendered. */ - var Renderer = Springy.Renderer = function(layout, clear, drawEdge, drawNode, onRenderStop, onRenderStart, onRenderFrame) { + var Renderer = Springy.Renderer = function(layout, onRenderStop) { this.layout = layout; - this.clear = clear; - this.drawEdge = drawEdge; - this.drawNode = drawNode; this.onRenderStop = onRenderStop; - this.onRenderStart = onRenderStart; - this.onRenderFrame = onRenderFrame; this.layout.graph.addGraphListener(this); - } + }; - Renderer.prototype.graphChanged = function(e) { + Renderer.prototype.graphChanged = function() { this.start(); }; /** * Starts the simulation of the layout in use. - * - * Note that in case the algorithm is still or already running then the layout that's in use - * might silently ignore the call, and your optional done callback is never executed. - * At least the built-in ForceDirected layout behaves in this way. - * - * @param done An optional callback function that gets executed when the springy algorithm stops, - * either because it ended or because stop() was called. */ - Renderer.prototype.start = function(done) { - var t = this; - this.layout.start(function render() { - t.clear(); + Renderer.prototype.start = function(maxTime) { + if (maxTime) { + setTimeout(() => this.stop(), maxTime); + } - t.layout.eachEdge(function(edge, spring) { - t.drawEdge(edge, spring.point1.p, spring.point2.p); - }); - - t.layout.eachNode(function(node, point) { - t.drawNode(node, point.p); - }); - - if (t.onRenderFrame !== undefined) { t.onRenderFrame(); } - }, this.onRenderStop, this.onRenderStart); + return new Promise((res, rej) => { + this.layout.start(res); + }); }; Renderer.prototype.stop = function() { this.layout.stop(); }; - // Array.forEach implementation for IE support.. - //https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach - if ( !Array.prototype.forEach ) { - Array.prototype.forEach = function( callback, thisArg ) { - var T, k; - if ( this == null ) { - throw new TypeError( " this is null or not defined" ); - } - var O = Object(this); - var len = O.length >>> 0; // Hack to convert O.length to a UInt32 - if ( {}.toString.call(callback) != "[object Function]" ) { - throw new TypeError( callback + " is not a function" ); - } - if ( thisArg ) { - T = thisArg; - } - k = 0; - while( k < len ) { - var kValue; - if ( k in O ) { - kValue = O[ k ]; - callback.call( T, kValue, k, O ); - } - k++; - } - }; - } - var isEmpty = function(obj) { for (var k in obj) { if (obj.hasOwnProperty(k)) { diff --git a/src/public/javascripts/dialogs/link_map.js b/src/public/javascripts/dialogs/link_map.js index c62001fae..4a3937a53 100644 --- a/src/public/javascripts/dialogs/link_map.js +++ b/src/public/javascripts/dialogs/link_map.js @@ -23,19 +23,18 @@ export async function showDialog() { // set default settings $maxNotesInput.val(20); - const note = noteDetailService.getActiveTabNote(); - - if (!note) { - return; - } - $linkMapContainer.css("height", $("body").height() - 150); - linkMapService = new LinkMapService(note, $linkMapContainer, getOptions()); - - linkMapService.render(); + $linkMapContainer.empty(); $dialog.modal(); } +$dialog.on('shown.bs.modal', () => { + const note = noteDetailService.getActiveTabNote(); + + linkMapService = new LinkMapService(note, $linkMapContainer, getOptions()); + linkMapService.render(); +}); + $maxNotesInput.on("input", () => linkMapService.loadNotesAndRelations(getOptions())); diff --git a/src/public/javascripts/services/link_map.js b/src/public/javascripts/services/link_map.js index f5041b90f..5ecfcb58b 100644 --- a/src/public/javascripts/services/link_map.js +++ b/src/public/javascripts/services/link_map.js @@ -119,60 +119,54 @@ export default class LinkMap { stop: params => {} }); - return $noteBox; }; - this.renderer = new Springy.Renderer( - layout, - () => {}, - (edge, p1, p2) => { - const connectionId = this.linkMapContainerId + '-' + edge.source.id + '-' + edge.target.id; + this.renderer = new Springy.Renderer(layout); + await this.renderer.start(500); - if ($("#" + connectionId).length > 0) { - return; - } + layout.eachNode((node, point) => { + const $noteBox = getNoteBox(node.id); + const middleW = this.$linkMapContainer.width() / 2; + const middleH = this.$linkMapContainer.height() / 2; - getNoteBox(edge.source.id); - getNoteBox(edge.target.id); + $noteBox + .css("left", (middleW + point.p.x * 100) + "px") + .css("top", (middleH + point.p.y * 100) + "px"); - const connection = this.jsPlumbInstance.connect({ - source: this.noteIdToId(edge.source.id), - target: this.noteIdToId(edge.target.id), - type: 'link' - }); - - if (connection) { - $(connection.canvas) - .prop("id", connectionId) - .addClass('link-' + edge.source.id) - .addClass('link-' + edge.target.id); - } - else { - console.log(`connection not created for`, edge); - } - }, - (node, p) => { - const $noteBox = getNoteBox(node.id); - const middleW = this.$linkMapContainer.width() / 2; - const middleH = this.$linkMapContainer.height() / 2; - - $noteBox - .css("left", (middleW + p.x * 100) + "px") - .css("top", (middleH + p.y * 100) + "px"); - - if ($noteBox.hasClass("link-map-active-note")) { - this.moveToCenterOfElement($noteBox[0]); - } - }, - () => {}, - () => {}, - () => { - this.jsPlumbInstance.repaintEverything(); + if ($noteBox.hasClass("link-map-active-note")) { + this.moveToCenterOfElement($noteBox[0]); } - ); + }); - this.renderer.start(); + layout.eachEdge(edge => { + const connectionId = this.linkMapContainerId + '-' + edge.source.id + '-' + edge.target.id; + + if ($("#" + connectionId).length > 0) { + return; + } + + getNoteBox(edge.source.id); + getNoteBox(edge.target.id); + + const connection = this.jsPlumbInstance.connect({ + source: this.noteIdToId(edge.source.id), + target: this.noteIdToId(edge.target.id), + type: 'link' + }); + + if (connection) { + $(connection.canvas) + .prop("id", connectionId) + .addClass('link-' + edge.source.id) + .addClass('link-' + edge.target.id); + } + else { + console.log(`connection not created for`, edge); + } + }); + + this.jsPlumbInstance.repaintEverything(); } moveToCenterOfElement(element) { diff --git a/src/public/javascripts/widgets/link_map.js b/src/public/javascripts/widgets/link_map.js index 449f9c7e9..89d764042 100644 --- a/src/public/javascripts/widgets/link_map.js +++ b/src/public/javascripts/widgets/link_map.js @@ -29,6 +29,7 @@ class LinkMapWidget extends StandardWidget { } async doRenderBody() { + this.$body.css('opacity', 0); this.$body.html(TPL); const $linkMapContainer = this.$body.find('.link-map-container'); @@ -43,6 +44,8 @@ class LinkMapWidget extends StandardWidget { }); await this.linkMapService.render(); + + this.$body.animate({opacity: 1}, 300); } cleanup() { diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 3533ae9de..043b27b9f 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -64,7 +64,9 @@ async function update(name, value) { return false; } - log.info(`Updating option ${name} to ${value}`); + if (name !== 'openTabs') { + log.info(`Updating option ${name} to ${value}`); + } await optionService.setOption(name, value);