diff --git a/lib/Minimap.js b/lib/Minimap.js index 7ca61b8..a8edcee 100644 --- a/lib/Minimap.js +++ b/lib/Minimap.js @@ -56,6 +56,9 @@ export default function Minimap( this._elementRegistry = elementRegistry; this._eventBus = eventBus; this._injector = injector; + this._config = config || {}; + this._updateTimeout = null; + this._firstTimeoutStart = null; this._state = { isOpen: undefined, @@ -73,7 +76,7 @@ export default function Minimap( this._init(); - this.toggle((config && config.open) || false); + this.toggle(this._config.open || false); function centerViewbox(point) { @@ -298,7 +301,7 @@ export default function Minimap( self._addElement(element); - self._update(); + self._debouncedUpdate(); }); // remove shape on shape/connection removed @@ -307,7 +310,7 @@ export default function Minimap( self._removeElement(element); - self._update(); + self._debouncedUpdate(); }); // update on elements changed @@ -318,7 +321,7 @@ export default function Minimap( self._updateElement(element); }); - self._update(); + self._debouncedUpdate(); }); // update on element ID update @@ -435,7 +438,52 @@ Minimap.prototype._init = function() { this._parent.appendChild(overlay); }; +Minimap.prototype._debouncedUpdate = function() { + + // ignore updates if closed + if (!this.isOpen()) { + return; + } + + const { + debounceDelay = 100, + debounceSkipDelay = 2000 + } = this._config; + + const now = Date.now(); + + // if we've been debouncing for a while already, force update + if (this._firstTimeoutStart && now - this._firstTimeoutStart >= debounceSkipDelay) { + return this._update(); + } + + // clear previous timeout + if (this._updateTimeout) { + clearTimeout(this._updateTimeout); + } + + // fire debounced update + this._firstTimeoutStart = this._firstTimeoutStart || now; + this._updateTimeout = setTimeout(() => { + this._update(); + }, debounceDelay); +}; + Minimap.prototype._update = function() { + + // ignore updates if closed + if (!this.isOpen()) { + return; + } + + // if update was forced, clear any timeouts + if (this._updateTimeout) { + clearTimeout(this._updateTimeout); + this._updateTimeout = null; + } + + this._firstTimeoutStart = null; + var viewbox = this._canvas.viewbox(), innerViewbox = viewbox.inner, outerViewbox = viewbox.outer; @@ -536,6 +584,7 @@ Minimap.prototype.open = function() { domAttr(this._toggle, 'title', translate('Close minimap')); + // open forces an update to ensure a viewbox is set and current this._update(); this._eventBus.fire('minimap.toggle', { open: true }); diff --git a/test/spec/MinimapSpec.js b/test/spec/MinimapSpec.js index e476858..fe97fc0 100644 --- a/test/spec/MinimapSpec.js +++ b/test/spec/MinimapSpec.js @@ -258,6 +258,97 @@ describe('minimap', function() { expectMinimapShapeToExist('child'); })); + + // only use in isolation performance testing, this isn't a proper test and shouldn't run on CI + it.skip('performance test', inject(function(canvas, modeling, elementFactory, minimap) { + + const GRID_MATRIX = [ + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,0,0,0,0,2,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,1,1,1,1,1,0,3,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,1,1,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,1,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,1,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,3,2,1,1,3,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,0,0,0,0,2,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,1,1,0,1,3,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,1,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,2,0,1,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,2,0,1,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,1,4,4,2,1,1,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,1,1,2,2,1,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,0,0,0,0,1,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,3,2,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,0,0,0,0,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,1,1,1,1,1,1,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,2,0,0,0,0,0,0,2,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,3,2,2,2,2,2,2,3,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ], + [ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 ] + ]; + + const root = canvas.getRootElement(); + const CELL = 40; + const GAP = 4; + const CHUNK = 150; + + const tasks = []; + + GRID_MATRIX.forEach((row, rIdx) => { + row.forEach((layers, cIdx) => { + for (let l = 0; l < layers; l++) { + const shrink = l * GAP; + const size = CELL - shrink * 2; + if (size <= 4) continue; + tasks.push({ + id: `grid_${rIdx}_${cIdx}_${l}`, + x: cIdx * CELL + shrink, + y: rIdx * CELL + shrink, + width: size, + height: size + }); + } + }); + }); + + let index = 0; + + function processChunk() { + const end = Math.min(index + CHUNK, tasks.length); + + for (; index < end; index++) { + const t = tasks[index]; + const shape = elementFactory.createShape({ + id: t.id, + x: t.x, + y: t.y, + width: t.width, + height: t.height + }); + modeling.createShape(shape, { x: shape.x, y: shape.y }, root); + } + + if (index < tasks.length) { + requestAnimationFrame(processChunk); + } else { + + setTimeout(() => { + minimap._update(); + }, 100); + } + } + + requestAnimationFrame(processChunk); + })); + });