@joint/core 4.1.3 → 4.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/geometry.js +129 -124
- package/dist/geometry.min.js +4 -3
- package/dist/joint.d.ts +352 -160
- package/dist/joint.js +3654 -2191
- package/dist/joint.min.js +4 -3
- package/dist/joint.nowrap.js +3653 -2188
- package/dist/joint.nowrap.min.js +4 -3
- package/dist/vectorizer.js +489 -279
- package/dist/vectorizer.min.js +4 -3
- package/dist/version.mjs +1 -1
- package/package.json +33 -27
- package/src/V/create.mjs +51 -0
- package/src/V/index.mjs +89 -159
- package/src/V/namespace.mjs +9 -0
- package/src/V/transform.mjs +183 -0
- package/src/V/traverse.mjs +16 -0
- package/src/alg/Deque.mjs +126 -0
- package/src/anchors/index.mjs +140 -33
- package/src/cellTools/Boundary.mjs +15 -13
- package/src/cellTools/Button.mjs +7 -5
- package/src/cellTools/Control.mjs +38 -15
- package/src/cellTools/HoverConnect.mjs +5 -1
- package/src/cellTools/helpers.mjs +44 -3
- package/src/config/index.mjs +8 -0
- package/src/connectionPoints/index.mjs +24 -9
- package/src/connectionStrategies/index.mjs +1 -1
- package/src/connectors/jumpover.mjs +1 -1
- package/src/dia/Cell.mjs +32 -12
- package/src/dia/CellView.mjs +53 -38
- package/src/dia/Element.mjs +81 -35
- package/src/dia/ElementView.mjs +2 -1
- package/src/dia/HighlighterView.mjs +54 -11
- package/src/dia/LinkView.mjs +118 -98
- package/src/dia/Paper.mjs +831 -231
- package/src/dia/PaperLayer.mjs +9 -2
- package/src/dia/ToolView.mjs +4 -0
- package/src/dia/ToolsView.mjs +12 -3
- package/src/dia/attributes/text.mjs +16 -5
- package/src/dia/layers/GridLayer.mjs +5 -0
- package/src/dia/ports.mjs +344 -111
- package/src/elementTools/HoverConnect.mjs +14 -8
- package/src/env/index.mjs +7 -4
- package/src/g/rect.mjs +7 -0
- package/src/highlighters/stroke.mjs +1 -1
- package/src/layout/ports/port.mjs +30 -15
- package/src/layout/ports/portLabel.mjs +1 -1
- package/src/linkAnchors/index.mjs +2 -2
- package/src/linkTools/Anchor.mjs +2 -2
- package/src/linkTools/Vertices.mjs +4 -6
- package/src/mvc/View.mjs +4 -0
- package/src/mvc/ViewBase.mjs +1 -1
- package/src/util/util.mjs +1 -1
- package/src/util/utilHelpers.mjs +2 -0
- package/types/geometry.d.ts +65 -59
- package/types/joint.d.ts +278 -102
- package/types/vectorizer.d.ts +11 -1
- package/src/V/annotation.mjs +0 -0
package/src/dia/Paper.mjs
CHANGED
|
@@ -28,17 +28,20 @@ import {
|
|
|
28
28
|
filter as _filter,
|
|
29
29
|
parseDOMJSON,
|
|
30
30
|
toArray,
|
|
31
|
-
has
|
|
31
|
+
has,
|
|
32
|
+
uniqueId,
|
|
32
33
|
} from '../util/index.mjs';
|
|
33
34
|
import { ViewBase } from '../mvc/ViewBase.mjs';
|
|
34
35
|
import { Rect, Point, toRad } from '../g/index.mjs';
|
|
35
|
-
import { View, views } from '../mvc/index.mjs';
|
|
36
|
-
import { CellView } from './CellView.mjs';
|
|
36
|
+
import { View, views as viewsRegistry } from '../mvc/index.mjs';
|
|
37
|
+
import { CellView, CELL_VIEW_MARKER } from './CellView.mjs';
|
|
37
38
|
import { ElementView } from './ElementView.mjs';
|
|
38
39
|
import { LinkView } from './LinkView.mjs';
|
|
39
40
|
import { Cell } from './Cell.mjs';
|
|
40
41
|
import { Graph } from './Graph.mjs';
|
|
41
42
|
import { LayersNames, PaperLayer } from './PaperLayer.mjs';
|
|
43
|
+
import { HighlighterView } from './HighlighterView.mjs';
|
|
44
|
+
import { Deque } from '../alg/Deque.mjs';
|
|
42
45
|
import * as highlighters from '../highlighters/index.mjs';
|
|
43
46
|
import * as linkAnchors from '../linkAnchors/index.mjs';
|
|
44
47
|
import * as connectionPoints from '../connectionPoints/index.mjs';
|
|
@@ -96,6 +99,8 @@ const defaultLayers = [{
|
|
|
96
99
|
name: LayersNames.TOOLS
|
|
97
100
|
}];
|
|
98
101
|
|
|
102
|
+
const CELL_VIEW_PLACEHOLDER_MARKER = Symbol('joint.cellViewPlaceholderMarker');
|
|
103
|
+
|
|
99
104
|
export const Paper = View.extend({
|
|
100
105
|
|
|
101
106
|
className: 'paper',
|
|
@@ -267,6 +272,8 @@ export const Paper = View.extend({
|
|
|
267
272
|
|
|
268
273
|
autoFreeze: false,
|
|
269
274
|
|
|
275
|
+
viewManagement: false,
|
|
276
|
+
|
|
270
277
|
// no docs yet
|
|
271
278
|
onViewUpdate: function(view, flag, priority, opt, paper) {
|
|
272
279
|
// Do not update connected links when:
|
|
@@ -274,7 +281,7 @@ export const Paper = View.extend({
|
|
|
274
281
|
// 2. the view was just mounted (added back to the paper by viewport function)
|
|
275
282
|
// 3. the change was marked as `isolate`.
|
|
276
283
|
// 4. the view model was just removed from the graph
|
|
277
|
-
if ((flag & (
|
|
284
|
+
if ((flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE)) || opt.mounting || opt.isolate) return;
|
|
278
285
|
paper.requestConnectedLinksUpdate(view, priority, opt);
|
|
279
286
|
},
|
|
280
287
|
|
|
@@ -387,6 +394,14 @@ export const Paper = View.extend({
|
|
|
387
394
|
// to mitigate the differences between the model and view geometry.
|
|
388
395
|
DEFAULT_FIND_BUFFER: 200,
|
|
389
396
|
|
|
397
|
+
// Default layer to insert the cell views into.
|
|
398
|
+
DEFAULT_CELL_LAYER: LayersNames.CELLS,
|
|
399
|
+
|
|
400
|
+
// Update flags
|
|
401
|
+
FLAG_INSERT: 1<<30,
|
|
402
|
+
FLAG_REMOVE: 1<<29,
|
|
403
|
+
FLAG_INIT: 1<<28,
|
|
404
|
+
|
|
390
405
|
init: function() {
|
|
391
406
|
|
|
392
407
|
const { options } = this;
|
|
@@ -398,8 +413,16 @@ export const Paper = View.extend({
|
|
|
398
413
|
|
|
399
414
|
const model = this.model = options.model || new Graph;
|
|
400
415
|
|
|
416
|
+
// This property tells us if we need to keep the compatibility
|
|
417
|
+
// with the v4 API and behavior.
|
|
418
|
+
this.legacyMode = !options.viewManagement;
|
|
419
|
+
|
|
401
420
|
// Layers (SVGGroups)
|
|
402
|
-
this._layers = {
|
|
421
|
+
this._layers = {
|
|
422
|
+
viewsMap: {},
|
|
423
|
+
namesMap: {},
|
|
424
|
+
order: [],
|
|
425
|
+
};
|
|
403
426
|
|
|
404
427
|
this.cloneOptions();
|
|
405
428
|
this.render();
|
|
@@ -408,6 +431,8 @@ export const Paper = View.extend({
|
|
|
408
431
|
|
|
409
432
|
// Hash of all cell views.
|
|
410
433
|
this._views = {};
|
|
434
|
+
this._viewPlaceholders = {};
|
|
435
|
+
this._idToCid = {};
|
|
411
436
|
|
|
412
437
|
// Mouse wheel events buffer
|
|
413
438
|
this._mw_evt_buffer = {
|
|
@@ -417,8 +442,6 @@ export const Paper = View.extend({
|
|
|
417
442
|
|
|
418
443
|
// Render existing cells in the graph
|
|
419
444
|
this.resetViews(model.attributes.cells.models);
|
|
420
|
-
// Start the Rendering Loop
|
|
421
|
-
if (!this.isFrozen() && this.isAsync()) this.updateViewsAsync();
|
|
422
445
|
},
|
|
423
446
|
|
|
424
447
|
_resetUpdates: function() {
|
|
@@ -427,16 +450,15 @@ export const Paper = View.extend({
|
|
|
427
450
|
return this._updates = {
|
|
428
451
|
id: null,
|
|
429
452
|
priorities: [{}, {}, {}],
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
unmounted: {},
|
|
433
|
-
mounted: {},
|
|
453
|
+
unmountedList: new Deque(),
|
|
454
|
+
mountedList: new Deque(),
|
|
434
455
|
count: 0,
|
|
435
456
|
keyFrozen: false,
|
|
436
457
|
freezeKey: null,
|
|
437
458
|
sort: false,
|
|
438
459
|
disabled: false,
|
|
439
|
-
idle: false
|
|
460
|
+
idle: false,
|
|
461
|
+
freshAfterReset: true,
|
|
440
462
|
};
|
|
441
463
|
},
|
|
442
464
|
|
|
@@ -465,15 +487,25 @@ export const Paper = View.extend({
|
|
|
465
487
|
},
|
|
466
488
|
|
|
467
489
|
onCellRemoved: function(cell, _, opt) {
|
|
468
|
-
const
|
|
469
|
-
if (
|
|
490
|
+
const viewLike = this._getCellViewLike(cell);
|
|
491
|
+
if (!viewLike) return;
|
|
492
|
+
if (viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
493
|
+
this._unregisterCellViewPlaceholder(viewLike);
|
|
494
|
+
} else {
|
|
495
|
+
this.requestViewUpdate(viewLike, this.FLAG_REMOVE, viewLike.UPDATE_PRIORITY, opt);
|
|
496
|
+
}
|
|
470
497
|
},
|
|
471
498
|
|
|
472
499
|
onCellChange: function(cell, opt) {
|
|
473
500
|
if (cell === this.model.attributes.cells) return;
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
501
|
+
if (
|
|
502
|
+
cell.hasChanged('layer') ||
|
|
503
|
+
(cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX)
|
|
504
|
+
) {
|
|
505
|
+
const viewLike = this._getCellViewLike(cell);
|
|
506
|
+
if (viewLike) {
|
|
507
|
+
this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
|
|
508
|
+
}
|
|
477
509
|
}
|
|
478
510
|
},
|
|
479
511
|
|
|
@@ -488,7 +520,7 @@ export const Paper = View.extend({
|
|
|
488
520
|
},
|
|
489
521
|
|
|
490
522
|
onGraphBatchStop: function(data) {
|
|
491
|
-
if (this.isFrozen()) return;
|
|
523
|
+
if (this.isFrozen() || this.isIdle()) return;
|
|
492
524
|
var name = data && data.batchName;
|
|
493
525
|
var graph = this.model;
|
|
494
526
|
if (!this.isAsync()) {
|
|
@@ -548,6 +580,15 @@ export const Paper = View.extend({
|
|
|
548
580
|
// Return the default highlighting options into the user specified options.
|
|
549
581
|
options.highlighting = defaultsDeep({}, highlighting, defaultHighlighting);
|
|
550
582
|
}
|
|
583
|
+
// Copy and set defaults for the view management options.
|
|
584
|
+
options.viewManagement = defaults({}, options.viewManagement, {
|
|
585
|
+
// Whether to lazy initialize the cell views.
|
|
586
|
+
lazyInitialize: !!options.viewManagement, // default `true` if options.viewManagement provided
|
|
587
|
+
// Whether to add initialized cell views into the unmounted queue.
|
|
588
|
+
initializeUnmounted: false,
|
|
589
|
+
// Whether to dispose the cell views that are not visible.
|
|
590
|
+
disposeHidden: false,
|
|
591
|
+
});
|
|
551
592
|
},
|
|
552
593
|
|
|
553
594
|
children: function() {
|
|
@@ -588,19 +629,122 @@ export const Paper = View.extend({
|
|
|
588
629
|
},
|
|
589
630
|
|
|
590
631
|
hasLayerView(layerName) {
|
|
591
|
-
return (layerName in this._layers);
|
|
632
|
+
return (layerName in this._layers.viewsMap);
|
|
592
633
|
},
|
|
593
634
|
|
|
594
635
|
getLayerView(layerName) {
|
|
595
|
-
const { _layers } = this;
|
|
596
|
-
if (layerName in
|
|
597
|
-
throw new Error(`dia.Paper: Unknown layer "${layerName}"
|
|
636
|
+
const { _layers: { viewsMap }} = this;
|
|
637
|
+
if (layerName in viewsMap) return viewsMap[layerName];
|
|
638
|
+
throw new Error(`dia.Paper: Unknown layer "${layerName}".`);
|
|
598
639
|
},
|
|
599
640
|
|
|
600
641
|
getLayerNode(layerName) {
|
|
601
642
|
return this.getLayerView(layerName).el;
|
|
602
643
|
},
|
|
603
644
|
|
|
645
|
+
_removeLayer(layerView) {
|
|
646
|
+
this._unregisterLayer(layerView);
|
|
647
|
+
layerView.remove();
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
_unregisterLayer(layerView) {
|
|
651
|
+
const { _layers: { viewsMap, namesMap, order }} = this;
|
|
652
|
+
const layerName = this._getLayerName(layerView);
|
|
653
|
+
order.splice(order.indexOf(layerName), 1);
|
|
654
|
+
delete namesMap[layerView.cid];
|
|
655
|
+
delete viewsMap[layerName];
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
_registerLayer(layerName, layerView, beforeLayerView) {
|
|
659
|
+
const { _layers: { viewsMap, namesMap, order }} = this;
|
|
660
|
+
if (beforeLayerView) {
|
|
661
|
+
const beforeLayerName = this._getLayerName(beforeLayerView);
|
|
662
|
+
order.splice(order.indexOf(beforeLayerName), 0, layerName);
|
|
663
|
+
} else {
|
|
664
|
+
order.push(layerName);
|
|
665
|
+
}
|
|
666
|
+
viewsMap[layerName] = layerView;
|
|
667
|
+
namesMap[layerView.cid] = layerName;
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
_getLayerView(layer) {
|
|
671
|
+
const { _layers: { namesMap, viewsMap }} = this;
|
|
672
|
+
if (layer instanceof PaperLayer) {
|
|
673
|
+
if (layer.cid in namesMap) return layer;
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
if (layer in viewsMap) return viewsMap[layer];
|
|
677
|
+
return null;
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
_getLayerName(layerView) {
|
|
681
|
+
const { _layers: { namesMap }} = this;
|
|
682
|
+
return namesMap[layerView.cid];
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
_requireLayerView(layer) {
|
|
686
|
+
const layerView = this._getLayerView(layer);
|
|
687
|
+
if (!layerView) {
|
|
688
|
+
if (layer instanceof PaperLayer) {
|
|
689
|
+
throw new Error('dia.Paper: The layer is not registered.');
|
|
690
|
+
} else {
|
|
691
|
+
throw new Error(`dia.Paper: Unknown layer "${layer}".`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return layerView;
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
hasLayer(layer) {
|
|
698
|
+
return this._getLayerView(layer) !== null;
|
|
699
|
+
},
|
|
700
|
+
|
|
701
|
+
removeLayer(layer) {
|
|
702
|
+
const layerView = this._requireLayerView(layer);
|
|
703
|
+
if (!layerView.isEmpty()) {
|
|
704
|
+
throw new Error('dia.Paper: The layer is not empty.');
|
|
705
|
+
}
|
|
706
|
+
this._removeLayer(layerView);
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
addLayer(layerName, layerView, options = {}) {
|
|
710
|
+
if (!layerName || typeof layerName !== 'string') {
|
|
711
|
+
throw new Error('dia.Paper: The layer name must be provided.');
|
|
712
|
+
}
|
|
713
|
+
if (this._getLayerView(layerName)) {
|
|
714
|
+
throw new Error(`dia.Paper: The layer "${layerName}" already exists.`);
|
|
715
|
+
}
|
|
716
|
+
if (!(layerView instanceof PaperLayer)) {
|
|
717
|
+
throw new Error('dia.Paper: The layer view is not an instance of dia.PaperLayer.');
|
|
718
|
+
}
|
|
719
|
+
const { insertBefore } = options;
|
|
720
|
+
if (!insertBefore) {
|
|
721
|
+
this._registerLayer(layerName, layerView, null);
|
|
722
|
+
this.layers.appendChild(layerView.el);
|
|
723
|
+
} else {
|
|
724
|
+
const beforeLayerView = this._requireLayerView(insertBefore);
|
|
725
|
+
this._registerLayer(layerName, layerView, beforeLayerView);
|
|
726
|
+
this.layers.insertBefore(layerView.el, beforeLayerView.el);
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
moveLayer(layer, insertBefore) {
|
|
731
|
+
const layerView = this._requireLayerView(layer);
|
|
732
|
+
if (layerView === this._getLayerView(insertBefore)) return;
|
|
733
|
+
const layerName = this._getLayerName(layerView);
|
|
734
|
+
this._unregisterLayer(layerView);
|
|
735
|
+
this.addLayer(layerName, layerView, { insertBefore });
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
getLayerNames() {
|
|
739
|
+
// Returns a sorted array of layer names.
|
|
740
|
+
return this._layers.order.slice();
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
getLayers() {
|
|
744
|
+
// Returns a sorted array of layer views.
|
|
745
|
+
return this.getLayerNames().map(name => this.getLayerView(name));
|
|
746
|
+
},
|
|
747
|
+
|
|
604
748
|
render: function() {
|
|
605
749
|
|
|
606
750
|
this.renderChildren();
|
|
@@ -645,14 +789,15 @@ export const Paper = View.extend({
|
|
|
645
789
|
}
|
|
646
790
|
},
|
|
647
791
|
|
|
792
|
+
renderLayer: function(name) {
|
|
793
|
+
const layerView = this.createLayer(name);
|
|
794
|
+
this.addLayer(name, layerView);
|
|
795
|
+
return layerView;
|
|
796
|
+
},
|
|
797
|
+
|
|
648
798
|
renderLayers: function(layers = defaultLayers) {
|
|
649
799
|
this.removeLayers();
|
|
650
|
-
|
|
651
|
-
layers.forEach(({ name, sorted }) => {
|
|
652
|
-
const layerView = this.createLayer(name);
|
|
653
|
-
this.layers.appendChild(layerView.el);
|
|
654
|
-
this._layers[name] = layerView;
|
|
655
|
-
});
|
|
800
|
+
layers.forEach(({ name }) => this.renderLayer(name));
|
|
656
801
|
// Throws an exception if doesn't exist
|
|
657
802
|
const cellsLayerView = this.getLayerView(LayersNames.CELLS);
|
|
658
803
|
const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
|
|
@@ -670,18 +815,13 @@ export const Paper = View.extend({
|
|
|
670
815
|
},
|
|
671
816
|
|
|
672
817
|
removeLayers: function() {
|
|
673
|
-
const { _layers } = this;
|
|
674
|
-
Object.
|
|
675
|
-
_layers[name].remove();
|
|
676
|
-
delete _layers[name];
|
|
677
|
-
});
|
|
818
|
+
const { _layers: { viewsMap }} = this;
|
|
819
|
+
Object.values(viewsMap).forEach(layerView => this._removeLayer(layerView));
|
|
678
820
|
},
|
|
679
821
|
|
|
680
822
|
resetLayers: function() {
|
|
681
|
-
const { _layers } = this;
|
|
682
|
-
Object.
|
|
683
|
-
_layers[name].removePivots();
|
|
684
|
-
});
|
|
823
|
+
const { _layers: { viewsMap }} = this;
|
|
824
|
+
Object.values(viewsMap).forEach(layerView => layerView.removePivots());
|
|
685
825
|
},
|
|
686
826
|
|
|
687
827
|
update: function() {
|
|
@@ -816,47 +956,46 @@ export const Paper = View.extend({
|
|
|
816
956
|
var links = this.model.getConnectedLinks(model);
|
|
817
957
|
for (var j = 0, n = links.length; j < n; j++) {
|
|
818
958
|
var link = links[j];
|
|
819
|
-
var linkView = this.
|
|
959
|
+
var linkView = this._getCellViewLike(link);
|
|
820
960
|
if (!linkView) continue;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
if (
|
|
961
|
+
// We do not have to update placeholder views.
|
|
962
|
+
// They will be updated on initial render.
|
|
963
|
+
if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
|
|
824
964
|
var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
|
|
825
|
-
this.scheduleViewUpdate(linkView, linkView.getFlag(
|
|
965
|
+
this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
|
|
826
966
|
}
|
|
827
967
|
}
|
|
828
968
|
},
|
|
829
969
|
|
|
830
970
|
forcePostponedViewUpdate: function(view, flag) {
|
|
831
971
|
if (!view || !(view instanceof CellView)) return false;
|
|
832
|
-
|
|
972
|
+
const model = view.model;
|
|
833
973
|
if (model.isElement()) return false;
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return !this.dumpView(view, dumpOptions);
|
|
853
|
-
}
|
|
974
|
+
const dumpOptions = { silent: true };
|
|
975
|
+
// LinkView is waiting for the target or the source cellView to be rendered
|
|
976
|
+
// This can happen when the cells are not in the viewport.
|
|
977
|
+
let sourceFlag = 0;
|
|
978
|
+
const sourceCell = model.getSourceCell();
|
|
979
|
+
if (sourceCell && !this.isCellVisible(sourceCell)) {
|
|
980
|
+
const sourceView = this.findViewByModel(sourceCell);
|
|
981
|
+
sourceFlag = this.dumpView(sourceView, dumpOptions);
|
|
982
|
+
}
|
|
983
|
+
let targetFlag = 0;
|
|
984
|
+
const targetCell = model.getTargetCell();
|
|
985
|
+
if (targetCell && !this.isCellVisible(targetCell)) {
|
|
986
|
+
const targetView = this.findViewByModel(targetCell);
|
|
987
|
+
targetFlag = this.dumpView(targetView, dumpOptions);
|
|
988
|
+
}
|
|
989
|
+
if (sourceFlag === 0 && targetFlag === 0) {
|
|
990
|
+
// If leftover flag is 0, all view updates were done.
|
|
991
|
+
return !this.dumpView(view, dumpOptions);
|
|
854
992
|
}
|
|
855
993
|
return false;
|
|
856
994
|
},
|
|
857
995
|
|
|
858
996
|
requestViewUpdate: function(view, flag, priority, opt) {
|
|
859
997
|
opt || (opt = {});
|
|
998
|
+
// Note: `scheduleViewUpdate` wakes up the paper if it is idle.
|
|
860
999
|
this.scheduleViewUpdate(view, flag, priority, opt);
|
|
861
1000
|
var isAsync = this.isAsync();
|
|
862
1001
|
if (this.isFrozen() || (isAsync && opt.async !== false)) return;
|
|
@@ -867,13 +1006,14 @@ export const Paper = View.extend({
|
|
|
867
1006
|
|
|
868
1007
|
scheduleViewUpdate: function(view, type, priority, opt) {
|
|
869
1008
|
const { _updates: updates, options } = this;
|
|
870
|
-
if (updates.idle) {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
this.
|
|
874
|
-
}
|
|
1009
|
+
if (updates.idle && options.autoFreeze) {
|
|
1010
|
+
this.legacyMode
|
|
1011
|
+
? this.unfreeze() // Restart rendering loop without original options
|
|
1012
|
+
: this.wakeUp();
|
|
875
1013
|
}
|
|
876
|
-
const { FLAG_REMOVE, FLAG_INSERT
|
|
1014
|
+
const { FLAG_REMOVE, FLAG_INSERT } = this;
|
|
1015
|
+
const { UPDATE_PRIORITY, cid } = view;
|
|
1016
|
+
|
|
877
1017
|
let priorityUpdates = updates.priorities[priority];
|
|
878
1018
|
if (!priorityUpdates) priorityUpdates = updates.priorities[priority] = {};
|
|
879
1019
|
// Move higher priority updates to this priority
|
|
@@ -919,20 +1059,18 @@ export const Paper = View.extend({
|
|
|
919
1059
|
dumpView: function(view, opt = {}) {
|
|
920
1060
|
const flag = this.dumpViewUpdate(view);
|
|
921
1061
|
if (!flag) return 0;
|
|
922
|
-
|
|
923
|
-
if (shouldNotify) this.notifyBeforeRender(opt);
|
|
1062
|
+
this.notifyBeforeRender(opt);
|
|
924
1063
|
const leftover = this.updateView(view, flag, opt);
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
this.notifyAfterRender(stats, opt);
|
|
928
|
-
}
|
|
1064
|
+
const stats = { updated: 1, priority: view.UPDATE_PRIORITY };
|
|
1065
|
+
this.notifyAfterRender(stats, opt);
|
|
929
1066
|
return leftover;
|
|
930
1067
|
},
|
|
931
1068
|
|
|
932
1069
|
updateView: function(view, flag, opt) {
|
|
933
1070
|
if (!view) return 0;
|
|
934
|
-
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT
|
|
935
|
-
|
|
1071
|
+
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT } = this;
|
|
1072
|
+
const { model } = view;
|
|
1073
|
+
if (view[CELL_VIEW_MARKER]) {
|
|
936
1074
|
if (flag & FLAG_REMOVE) {
|
|
937
1075
|
this.removeView(model);
|
|
938
1076
|
return 0;
|
|
@@ -960,57 +1098,70 @@ export const Paper = View.extend({
|
|
|
960
1098
|
registerUnmountedView: function(view) {
|
|
961
1099
|
var cid = view.cid;
|
|
962
1100
|
var updates = this._updates;
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
updates.
|
|
966
|
-
|
|
1101
|
+
if (updates.unmountedList.has(cid)) return 0;
|
|
1102
|
+
const flag = this.FLAG_INSERT;
|
|
1103
|
+
updates.unmountedList.pushTail(cid, flag);
|
|
1104
|
+
updates.mountedList.delete(cid);
|
|
967
1105
|
return flag;
|
|
968
1106
|
},
|
|
969
1107
|
|
|
970
1108
|
registerMountedView: function(view) {
|
|
971
1109
|
var cid = view.cid;
|
|
972
1110
|
var updates = this._updates;
|
|
973
|
-
if (
|
|
974
|
-
updates.
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1111
|
+
if (updates.mountedList.has(cid)) return 0;
|
|
1112
|
+
const unmountedItem = updates.unmountedList.get(cid);
|
|
1113
|
+
const flag = unmountedItem ? unmountedItem.value : 0;
|
|
1114
|
+
updates.unmountedList.delete(cid);
|
|
1115
|
+
updates.mountedList.pushTail(cid);
|
|
978
1116
|
return flag;
|
|
979
1117
|
},
|
|
980
1118
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return (cid in updates.mounted);
|
|
1119
|
+
isCellVisible: function(cellOrId) {
|
|
1120
|
+
const cid = cellOrId && this._idToCid[cellOrId.id || cellOrId];
|
|
1121
|
+
if (!cid) return false; // The view is not registered.
|
|
1122
|
+
return this.isViewMounted(cid);
|
|
986
1123
|
},
|
|
987
1124
|
|
|
1125
|
+
isViewMounted: function(viewOrCid) {
|
|
1126
|
+
if (!viewOrCid) return false;
|
|
1127
|
+
let cid;
|
|
1128
|
+
if (viewOrCid[CELL_VIEW_MARKER] || viewOrCid[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1129
|
+
cid = viewOrCid.cid;
|
|
1130
|
+
} else {
|
|
1131
|
+
cid = viewOrCid;
|
|
1132
|
+
}
|
|
1133
|
+
return this._updates.mountedList.has(cid);
|
|
1134
|
+
},
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* @deprecated use `updateCellsVisibility` instead.
|
|
1138
|
+
* `paper.updateCellsVisibility({ cellVisibility: () => true });`
|
|
1139
|
+
*/
|
|
988
1140
|
dumpViews: function(opt) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
this.
|
|
1141
|
+
// Update cell visibility without `cellVisibility` callback i.e. make the cells visible
|
|
1142
|
+
const passingOpt = defaults({}, opt, { cellVisibility: null, viewport: null });
|
|
1143
|
+
this.updateCellsVisibility(passingOpt);
|
|
992
1144
|
},
|
|
993
1145
|
|
|
994
|
-
|
|
995
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* Process all scheduled updates synchronously.
|
|
1148
|
+
*/
|
|
1149
|
+
updateViews: function(opt = {}) {
|
|
996
1150
|
this.notifyBeforeRender(opt);
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
updateCount += batchStats.updated;
|
|
1005
|
-
priority = Math.min(batchStats.priority, priority);
|
|
1006
|
-
} while (!batchStats.empty);
|
|
1007
|
-
const stats = { updated: updateCount, batches: batchCount, priority };
|
|
1151
|
+
const batchStats = this.updateViewsBatch({ ...opt, batchSize: Infinity });
|
|
1152
|
+
const stats = {
|
|
1153
|
+
updated: batchStats.updated,
|
|
1154
|
+
priority: batchStats.priority,
|
|
1155
|
+
// For backward compatibility. Will be removed in the future.
|
|
1156
|
+
batches: Number.isFinite(opt.batchSize) ? Math.ceil(batchStats.updated / opt.batchSize) : 1
|
|
1157
|
+
};
|
|
1008
1158
|
this.notifyAfterRender(stats, opt);
|
|
1009
1159
|
return stats;
|
|
1010
1160
|
},
|
|
1011
1161
|
|
|
1012
1162
|
hasScheduledUpdates: function() {
|
|
1013
|
-
const
|
|
1163
|
+
const updates = this._updates;
|
|
1164
|
+
const priorities = updates.priorities;
|
|
1014
1165
|
const priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
1015
1166
|
let i = priorityIndexes.length;
|
|
1016
1167
|
while (i > 0 && i--) {
|
|
@@ -1022,11 +1173,38 @@ export const Paper = View.extend({
|
|
|
1022
1173
|
|
|
1023
1174
|
updateViewsAsync: function(opt, data) {
|
|
1024
1175
|
opt || (opt = {});
|
|
1025
|
-
data || (data = {
|
|
1176
|
+
data || (data = {
|
|
1177
|
+
processed: 0,
|
|
1178
|
+
priority: MIN_PRIORITY,
|
|
1179
|
+
checkedUnmounted: 0,
|
|
1180
|
+
checkedMounted: 0,
|
|
1181
|
+
});
|
|
1026
1182
|
const { _updates: updates, options } = this;
|
|
1027
|
-
const id = updates
|
|
1028
|
-
|
|
1183
|
+
const { id, mountedList, unmountedList, freshAfterReset } = updates;
|
|
1184
|
+
|
|
1185
|
+
// Should we run the next batch update this frame?
|
|
1186
|
+
let runBatchUpdate = true;
|
|
1187
|
+
if (!id) {
|
|
1188
|
+
// If there's no scheduled frame, no batch update is needed.
|
|
1189
|
+
runBatchUpdate = false;
|
|
1190
|
+
} else {
|
|
1191
|
+
// Cancel any scheduled frame.
|
|
1029
1192
|
cancelFrame(id);
|
|
1193
|
+
if (freshAfterReset) {
|
|
1194
|
+
// First update after a reset.
|
|
1195
|
+
updates.freshAfterReset = false;
|
|
1196
|
+
// When `initializeUnmounted` is enabled, there are no scheduled updates.
|
|
1197
|
+
// We check whether the `mountedList` and `unmountedList` are empty.
|
|
1198
|
+
if (!this.legacyMode && mountedList.length === 0 && unmountedList.length === 0) {
|
|
1199
|
+
// No updates to process; We trigger before/after render events via `updateViews`.
|
|
1200
|
+
// Note: If `autoFreeze` is enabled, 'idle' event triggers next frame.
|
|
1201
|
+
this.updateViews();
|
|
1202
|
+
runBatchUpdate = false;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (runBatchUpdate) {
|
|
1030
1208
|
if (data.processed === 0 && this.hasScheduledUpdates()) {
|
|
1031
1209
|
this.notifyBeforeRender(opt);
|
|
1032
1210
|
}
|
|
@@ -1035,7 +1213,7 @@ export const Paper = View.extend({
|
|
|
1035
1213
|
mountBatchSize: MOUNT_BATCH_SIZE - stats.mounted,
|
|
1036
1214
|
unmountBatchSize: MOUNT_BATCH_SIZE - stats.unmounted
|
|
1037
1215
|
});
|
|
1038
|
-
const checkStats = this.
|
|
1216
|
+
const checkStats = this.scheduleCellsVisibilityUpdate(passingOpt);
|
|
1039
1217
|
const unmountCount = checkStats.unmounted;
|
|
1040
1218
|
const mountCount = checkStats.mounted;
|
|
1041
1219
|
let processed = data.processed;
|
|
@@ -1056,11 +1234,22 @@ export const Paper = View.extend({
|
|
|
1056
1234
|
} else {
|
|
1057
1235
|
data.processed = processed;
|
|
1058
1236
|
}
|
|
1237
|
+
data.checkedUnmounted = 0;
|
|
1238
|
+
data.checkedMounted = 0;
|
|
1059
1239
|
} else {
|
|
1060
|
-
|
|
1061
|
-
|
|
1240
|
+
data.checkedUnmounted += Math.max(passingOpt.mountBatchSize, 0);
|
|
1241
|
+
data.checkedMounted += Math.max(passingOpt.unmountBatchSize, 0);
|
|
1242
|
+
// The `scheduleCellsVisibilityUpdate` could have scheduled some insertions
|
|
1243
|
+
// (note that removals are currently done synchronously).
|
|
1244
|
+
if (options.autoFreeze && !this.hasScheduledUpdates()) {
|
|
1245
|
+
// If there are no updates scheduled and we checked all unmounted views,
|
|
1246
|
+
if (
|
|
1247
|
+
data.checkedUnmounted >= unmountedList.length &&
|
|
1248
|
+
data.checkedMounted >= mountedList.length
|
|
1249
|
+
) {
|
|
1250
|
+
// We freeze the paper and notify the idle state.
|
|
1062
1251
|
this.freeze();
|
|
1063
|
-
updates.idle =
|
|
1252
|
+
updates.idle = { wakeUpOptions: opt };
|
|
1064
1253
|
this.trigger('render:idle', opt);
|
|
1065
1254
|
}
|
|
1066
1255
|
}
|
|
@@ -1080,6 +1269,7 @@ export const Paper = View.extend({
|
|
|
1080
1269
|
},
|
|
1081
1270
|
|
|
1082
1271
|
notifyBeforeRender: function(opt = {}) {
|
|
1272
|
+
if (opt.silent) return;
|
|
1083
1273
|
let beforeFn = opt.beforeRender;
|
|
1084
1274
|
if (typeof beforeFn !== 'function') {
|
|
1085
1275
|
beforeFn = this.options.beforeRender;
|
|
@@ -1089,6 +1279,7 @@ export const Paper = View.extend({
|
|
|
1089
1279
|
},
|
|
1090
1280
|
|
|
1091
1281
|
notifyAfterRender: function(stats, opt = {}) {
|
|
1282
|
+
if (opt.silent) return;
|
|
1092
1283
|
let afterFn = opt.afterRender;
|
|
1093
1284
|
if (typeof afterFn !== 'function') {
|
|
1094
1285
|
afterFn = this.options.afterRender;
|
|
@@ -1099,6 +1290,56 @@ export const Paper = View.extend({
|
|
|
1099
1290
|
this.trigger('render:done', stats, opt);
|
|
1100
1291
|
},
|
|
1101
1292
|
|
|
1293
|
+
prioritizeCellViewMount: function(cellOrId) {
|
|
1294
|
+
if (!cellOrId) return false;
|
|
1295
|
+
const cid = this._idToCid[cellOrId.id || cellOrId];
|
|
1296
|
+
if (!cid) return false;
|
|
1297
|
+
const { unmountedList } = this._updates;
|
|
1298
|
+
if (!unmountedList.has(cid)) return false;
|
|
1299
|
+
// Move the view to the head of the mounted list
|
|
1300
|
+
unmountedList.moveToHead(cid);
|
|
1301
|
+
return true;
|
|
1302
|
+
},
|
|
1303
|
+
|
|
1304
|
+
prioritizeCellViewUnmount: function(cellOrId) {
|
|
1305
|
+
if (!cellOrId) return false;
|
|
1306
|
+
const cid = this._idToCid[cellOrId.id || cellOrId];
|
|
1307
|
+
if (!cid) return false;
|
|
1308
|
+
const { mountedList } = this._updates;
|
|
1309
|
+
if (!mountedList.has(cid)) return false;
|
|
1310
|
+
// Move the view to the head of the unmounted list
|
|
1311
|
+
mountedList.moveToHead(cid);
|
|
1312
|
+
return true;
|
|
1313
|
+
},
|
|
1314
|
+
|
|
1315
|
+
_evalCellVisibility: function(viewLike, isMounted, visibilityCallback) {
|
|
1316
|
+
if (!visibilityCallback || !viewLike.DETACHABLE) return true;
|
|
1317
|
+
if (this.legacyMode) {
|
|
1318
|
+
return visibilityCallback.call(this, viewLike, isMounted, this);
|
|
1319
|
+
}
|
|
1320
|
+
// The visibility check runs for CellView only.
|
|
1321
|
+
if (!viewLike[CELL_VIEW_MARKER] && !viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) return true;
|
|
1322
|
+
// The cellView model must be a member of this graph.
|
|
1323
|
+
if (viewLike.model.graph !== this.model) {
|
|
1324
|
+
// It could have been removed from the graph.
|
|
1325
|
+
// If the view was mounted, we keep it mounted.
|
|
1326
|
+
return isMounted;
|
|
1327
|
+
}
|
|
1328
|
+
return visibilityCallback.call(this, viewLike.model, isMounted, this);
|
|
1329
|
+
},
|
|
1330
|
+
|
|
1331
|
+
_getCellVisibilityCallback: function(opt) {
|
|
1332
|
+
const { options } = this;
|
|
1333
|
+
if (this.legacyMode) {
|
|
1334
|
+
const viewportFn = 'viewport' in opt ? opt.viewport : options.viewport;
|
|
1335
|
+
if (typeof viewportFn === 'function') return viewportFn;
|
|
1336
|
+
} else {
|
|
1337
|
+
const isVisibleFn = 'cellVisibility' in opt ? opt.cellVisibility : options.cellVisibility;
|
|
1338
|
+
if (typeof isVisibleFn === 'function') return isVisibleFn;
|
|
1339
|
+
}
|
|
1340
|
+
return null;
|
|
1341
|
+
},
|
|
1342
|
+
|
|
1102
1343
|
updateViewsBatch: function(opt) {
|
|
1103
1344
|
opt || (opt = {});
|
|
1104
1345
|
var batchSize = opt.batchSize || UPDATE_BATCH_SIZE;
|
|
@@ -1111,8 +1352,7 @@ export const Paper = View.extend({
|
|
|
1111
1352
|
var empty = true;
|
|
1112
1353
|
var options = this.options;
|
|
1113
1354
|
var priorities = updates.priorities;
|
|
1114
|
-
|
|
1115
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
1355
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1116
1356
|
var postponeViewFn = options.onViewPostponed;
|
|
1117
1357
|
if (typeof postponeViewFn !== 'function') postponeViewFn = null;
|
|
1118
1358
|
var priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
@@ -1124,33 +1364,56 @@ export const Paper = View.extend({
|
|
|
1124
1364
|
empty = false;
|
|
1125
1365
|
break main;
|
|
1126
1366
|
}
|
|
1127
|
-
var view =
|
|
1367
|
+
var view = viewsRegistry[cid];
|
|
1128
1368
|
if (!view) {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1369
|
+
view = this._viewPlaceholders[cid];
|
|
1370
|
+
if (!view) {
|
|
1371
|
+
/**
|
|
1372
|
+
* This can occur when:
|
|
1373
|
+
* - the model is removed and a new model with the same id is added
|
|
1374
|
+
* - the view `initialize` method was overridden and the view was not registered
|
|
1375
|
+
* - an mvc.View scheduled an update, was removed and paper was not notified
|
|
1376
|
+
*/
|
|
1377
|
+
delete priorityUpdates[cid];
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1132
1380
|
}
|
|
1133
1381
|
var currentFlag = priorityUpdates[cid];
|
|
1134
|
-
if ((currentFlag &
|
|
1382
|
+
if ((currentFlag & this.FLAG_REMOVE) === 0) {
|
|
1135
1383
|
// We should never check a view for viewport if we are about to remove the view
|
|
1136
|
-
|
|
1137
|
-
if (
|
|
1384
|
+
const isMounted = !updates.unmountedList.has(cid);
|
|
1385
|
+
if (!this._evalCellVisibility(view, isMounted, visibilityCb)) {
|
|
1138
1386
|
// Unmount View
|
|
1139
|
-
if (
|
|
1387
|
+
if (isMounted) {
|
|
1388
|
+
// The view is currently mounted. Hide the view (detach or remove it).
|
|
1140
1389
|
this.registerUnmountedView(view);
|
|
1141
|
-
this.
|
|
1390
|
+
this._hideView(view);
|
|
1391
|
+
} else {
|
|
1392
|
+
// The view is not mounted. We can just update the unmounted list.
|
|
1393
|
+
// We ADD the current flag to the flag that was already scheduled.
|
|
1394
|
+
this._mergeUnmountedViewScheduledUpdates(cid, currentFlag);
|
|
1142
1395
|
}
|
|
1143
|
-
|
|
1396
|
+
// Delete the current update as it has been processed.
|
|
1144
1397
|
delete priorityUpdates[cid];
|
|
1145
1398
|
unmountCount++;
|
|
1146
1399
|
continue;
|
|
1147
1400
|
}
|
|
1148
1401
|
// Mount View
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1402
|
+
if (view[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1403
|
+
view = this._resolveCellViewPlaceholder(view);
|
|
1404
|
+
// Newly initialized view needs to be initialized
|
|
1405
|
+
currentFlag |= this.getCellViewInitFlag(view);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
if (!isMounted) {
|
|
1409
|
+
currentFlag |= this.FLAG_INSERT;
|
|
1151
1410
|
mountCount++;
|
|
1152
1411
|
}
|
|
1153
1412
|
currentFlag |= this.registerMountedView(view);
|
|
1413
|
+
} else if (view[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1414
|
+
// We are trying to remove a placeholder view.
|
|
1415
|
+
// This should not occur as the placeholder should have been unregistered
|
|
1416
|
+
continue;
|
|
1154
1417
|
}
|
|
1155
1418
|
var leftoverFlag = this.updateView(view, currentFlag, opt);
|
|
1156
1419
|
if (leftoverFlag > 0) {
|
|
@@ -1177,104 +1440,125 @@ export const Paper = View.extend({
|
|
|
1177
1440
|
};
|
|
1178
1441
|
},
|
|
1179
1442
|
|
|
1443
|
+
getCellViewInitFlag: function(cellView) {
|
|
1444
|
+
return this.FLAG_INIT | cellView.getFlag(result(cellView, 'initFlag'));
|
|
1445
|
+
},
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* @ignore This method returns an array of cellViewLike objects and therefore
|
|
1449
|
+
* is meant for internal/test use only.
|
|
1450
|
+
* The view placeholders are not exposed via public API.
|
|
1451
|
+
*/
|
|
1180
1452
|
getUnmountedViews: function() {
|
|
1181
1453
|
const updates = this._updates;
|
|
1182
|
-
const
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
for (
|
|
1186
|
-
|
|
1454
|
+
const unmountedViews = new Array(updates.unmountedList.length);
|
|
1455
|
+
const unmountedCids = updates.unmountedList.keys();
|
|
1456
|
+
let i = 0;
|
|
1457
|
+
for (const cid of unmountedCids) {
|
|
1458
|
+
// If the view is a placeholder, it won't be in the global views map
|
|
1459
|
+
// If the view is not a cell view, it won't be in the viewPlaceholders map
|
|
1460
|
+
unmountedViews[i++] = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
1187
1461
|
}
|
|
1188
1462
|
return unmountedViews;
|
|
1189
1463
|
},
|
|
1190
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* @ignore This method returns an array of cellViewLike objects and therefore
|
|
1467
|
+
* is meant for internal/test use only.
|
|
1468
|
+
* The view placeholders are not exposed via public API.
|
|
1469
|
+
*/
|
|
1191
1470
|
getMountedViews: function() {
|
|
1192
1471
|
const updates = this._updates;
|
|
1193
|
-
const
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
for (
|
|
1197
|
-
mountedViews[i] =
|
|
1472
|
+
const mountedViews = new Array(updates.mountedList.length);
|
|
1473
|
+
const mountedCids = updates.mountedList.keys();
|
|
1474
|
+
let i = 0;
|
|
1475
|
+
for (const cid of mountedCids) {
|
|
1476
|
+
mountedViews[i++] = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
1198
1477
|
}
|
|
1199
1478
|
return mountedViews;
|
|
1200
1479
|
},
|
|
1201
1480
|
|
|
1202
|
-
checkUnmountedViews: function(
|
|
1481
|
+
checkUnmountedViews: function(visibilityCb, opt) {
|
|
1203
1482
|
opt || (opt = {});
|
|
1204
1483
|
var mountCount = 0;
|
|
1205
|
-
if (typeof
|
|
1484
|
+
if (typeof visibilityCb !== 'function') visibilityCb = null;
|
|
1206
1485
|
var batchSize = 'mountBatchSize' in opt ? opt.mountBatchSize : Infinity;
|
|
1207
1486
|
var updates = this._updates;
|
|
1208
|
-
var
|
|
1209
|
-
var
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (!
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1487
|
+
var unmountedList = updates.unmountedList;
|
|
1488
|
+
for (var i = 0, n = Math.min(unmountedList.length, batchSize); i < n; i++) {
|
|
1489
|
+
const { key: cid } = unmountedList.peekHead();
|
|
1490
|
+
let view = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
1491
|
+
if (!view) {
|
|
1492
|
+
// This should not occur
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
if (!this._evalCellVisibility(view, false, visibilityCb)) {
|
|
1216
1496
|
// Push at the end of all unmounted ids, so this can be check later again
|
|
1217
|
-
|
|
1497
|
+
unmountedList.rotate();
|
|
1218
1498
|
continue;
|
|
1219
1499
|
}
|
|
1500
|
+
// Remove the view from the unmounted list
|
|
1501
|
+
const { value: prevFlag } = unmountedList.popHead();
|
|
1220
1502
|
mountCount++;
|
|
1221
|
-
|
|
1503
|
+
const flag = this.registerMountedView(view) | prevFlag;
|
|
1222
1504
|
if (flag) this.scheduleViewUpdate(view, flag, view.UPDATE_PRIORITY, { mounting: true });
|
|
1223
1505
|
}
|
|
1224
|
-
// Get rid of views, that have been mounted
|
|
1225
|
-
unmountedCids.splice(0, i);
|
|
1226
1506
|
return mountCount;
|
|
1227
1507
|
},
|
|
1228
1508
|
|
|
1229
|
-
checkMountedViews: function(
|
|
1509
|
+
checkMountedViews: function(visibilityCb, opt) {
|
|
1230
1510
|
opt || (opt = {});
|
|
1231
1511
|
var unmountCount = 0;
|
|
1232
|
-
if (typeof
|
|
1512
|
+
if (typeof visibilityCb !== 'function') return unmountCount;
|
|
1233
1513
|
var batchSize = 'unmountBatchSize' in opt ? opt.unmountBatchSize : Infinity;
|
|
1234
1514
|
var updates = this._updates;
|
|
1235
|
-
|
|
1236
|
-
var
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
if (!
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1515
|
+
const mountedList = updates.mountedList;
|
|
1516
|
+
for (var i = 0, n = Math.min(mountedList.length, batchSize); i < n; i++) {
|
|
1517
|
+
const { key: cid } = mountedList.peekHead();
|
|
1518
|
+
const view = viewsRegistry[cid];
|
|
1519
|
+
if (!view) {
|
|
1520
|
+
// A view (not a cell view) has been removed from the paper.
|
|
1521
|
+
// Remove it from the mounted list and continue.
|
|
1522
|
+
mountedList.popHead();
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (this._evalCellVisibility(view, true, visibilityCb)) {
|
|
1243
1526
|
// Push at the end of all mounted ids, so this can be check later again
|
|
1244
|
-
|
|
1527
|
+
mountedList.rotate();
|
|
1245
1528
|
continue;
|
|
1246
1529
|
}
|
|
1530
|
+
// Remove the view from the mounted list
|
|
1531
|
+
mountedList.popHead();
|
|
1247
1532
|
unmountCount++;
|
|
1248
1533
|
var flag = this.registerUnmountedView(view);
|
|
1249
|
-
if (flag)
|
|
1534
|
+
if (flag) {
|
|
1535
|
+
this._hideView(view);
|
|
1536
|
+
}
|
|
1250
1537
|
}
|
|
1251
|
-
// Get rid of views, that have been unmounted
|
|
1252
|
-
mountedCids.splice(0, i);
|
|
1253
1538
|
return unmountCount;
|
|
1254
1539
|
},
|
|
1255
1540
|
|
|
1256
1541
|
checkViewVisibility: function(cellView, opt = {}) {
|
|
1257
|
-
|
|
1258
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
1542
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1259
1543
|
const updates = this._updates;
|
|
1260
|
-
const {
|
|
1261
|
-
|
|
1544
|
+
const { mountedList, unmountedList } = updates;
|
|
1545
|
+
|
|
1546
|
+
const visible = this._evalCellVisibility(cellView, false, visibilityCb);
|
|
1262
1547
|
|
|
1263
1548
|
let isUnmounted = false;
|
|
1264
1549
|
let isMounted = false;
|
|
1265
1550
|
|
|
1266
|
-
if (cellView.cid
|
|
1551
|
+
if (mountedList.has(cellView.cid) && !visible) {
|
|
1267
1552
|
const flag = this.registerUnmountedView(cellView);
|
|
1268
|
-
if (flag) this.
|
|
1269
|
-
|
|
1270
|
-
updates.mountedCids.splice(i, 1);
|
|
1553
|
+
if (flag) this._hideView(cellView);
|
|
1554
|
+
mountedList.delete(cellView.cid);
|
|
1271
1555
|
isUnmounted = true;
|
|
1272
1556
|
}
|
|
1273
1557
|
|
|
1274
|
-
if (!isUnmounted && cellView.cid
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1558
|
+
if (!isUnmounted && unmountedList.has(cellView.cid) && visible) {
|
|
1559
|
+
const unmountedItem = unmountedList.get(cellView.cid);
|
|
1560
|
+
unmountedList.delete(cellView.cid);
|
|
1561
|
+
const flag = unmountedItem.value | this.registerMountedView(cellView);
|
|
1278
1562
|
if (flag) this.scheduleViewUpdate(cellView, flag, cellView.UPDATE_PRIORITY, { mounting: true });
|
|
1279
1563
|
isMounted = true;
|
|
1280
1564
|
}
|
|
@@ -1285,25 +1569,65 @@ export const Paper = View.extend({
|
|
|
1285
1569
|
};
|
|
1286
1570
|
},
|
|
1287
1571
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1572
|
+
/**
|
|
1573
|
+
* @public
|
|
1574
|
+
* Update the visibility of a single cell.
|
|
1575
|
+
*/
|
|
1576
|
+
updateCellVisibility: function(cell, opt = {}) {
|
|
1577
|
+
const cellViewLike = this._getCellViewLike(cell);
|
|
1578
|
+
if (!cellViewLike) return;
|
|
1579
|
+
const stats = this.checkViewVisibility(cellViewLike, opt);
|
|
1580
|
+
// Note: `unmounted` views are removed immediately
|
|
1581
|
+
if (stats.mounted > 0) {
|
|
1582
|
+
// Mounting is scheduled. Run the update.
|
|
1583
|
+
// Note: the view might be a placeholder.
|
|
1584
|
+
this.requireView(cell, opt);
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* @public
|
|
1590
|
+
* Update the visibility of all cells.
|
|
1591
|
+
*/
|
|
1592
|
+
updateCellsVisibility: function(opt = {}) {
|
|
1593
|
+
// Check the visibility of all cells and schedule their updates.
|
|
1594
|
+
this.scheduleCellsVisibilityUpdate(opt);
|
|
1595
|
+
// Perform the scheduled updates while avoiding re-evaluating the visibility.
|
|
1596
|
+
const keepCurrentVisibility = (_, isVisible) => isVisible;
|
|
1597
|
+
this.updateViews({ ...opt, cellVisibility: keepCurrentVisibility });
|
|
1598
|
+
},
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* @protected
|
|
1602
|
+
* Run visibility checks for all cells and schedule their updates.
|
|
1603
|
+
*/
|
|
1604
|
+
scheduleCellsVisibilityUpdate(opt) {
|
|
1605
|
+
const passingOpt = defaults({}, opt, {
|
|
1290
1606
|
mountBatchSize: Infinity,
|
|
1291
1607
|
unmountBatchSize: Infinity
|
|
1292
1608
|
});
|
|
1293
|
-
|
|
1294
|
-
|
|
1609
|
+
const visibilityCb = this._getCellVisibilityCallback(passingOpt);
|
|
1610
|
+
const unmountedCount = this.checkMountedViews(visibilityCb, passingOpt);
|
|
1295
1611
|
if (unmountedCount > 0) {
|
|
1296
1612
|
// Do not check views, that have been just unmounted and pushed at the end of the cids array
|
|
1297
|
-
var
|
|
1298
|
-
passingOpt.mountBatchSize = Math.min(
|
|
1613
|
+
var unmountedList = this._updates.unmountedList;
|
|
1614
|
+
passingOpt.mountBatchSize = Math.min(unmountedList.length - unmountedCount, passingOpt.mountBatchSize);
|
|
1299
1615
|
}
|
|
1300
|
-
|
|
1616
|
+
const mountedCount = this.checkUnmountedViews(visibilityCb, passingOpt);
|
|
1301
1617
|
return {
|
|
1302
1618
|
mounted: mountedCount,
|
|
1303
1619
|
unmounted: unmountedCount
|
|
1304
1620
|
};
|
|
1305
1621
|
},
|
|
1306
1622
|
|
|
1623
|
+
/**
|
|
1624
|
+
* @deprecated use `updateCellsVisibility` instead
|
|
1625
|
+
* This method will be renamed and made private in the future.
|
|
1626
|
+
*/
|
|
1627
|
+
checkViewport: function(opt) {
|
|
1628
|
+
return this.scheduleCellsVisibilityUpdate(opt);
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1307
1631
|
freeze: function(opt) {
|
|
1308
1632
|
opt || (opt = {});
|
|
1309
1633
|
var updates = this._updates;
|
|
@@ -1319,6 +1643,10 @@ export const Paper = View.extend({
|
|
|
1319
1643
|
this.options.frozen = true;
|
|
1320
1644
|
var id = updates.id;
|
|
1321
1645
|
updates.id = null;
|
|
1646
|
+
if (!this.legacyMode) {
|
|
1647
|
+
// Make sure the `freeze()` method ends the idle state.
|
|
1648
|
+
updates.idle = false;
|
|
1649
|
+
}
|
|
1322
1650
|
if (this.isAsync() && id) cancelFrame(id);
|
|
1323
1651
|
},
|
|
1324
1652
|
|
|
@@ -1332,6 +1660,7 @@ export const Paper = View.extend({
|
|
|
1332
1660
|
updates.freezeKey = null;
|
|
1333
1661
|
// key passed, but the paper is already freezed
|
|
1334
1662
|
if (key && key === freezeKey && updates.keyFrozen) return;
|
|
1663
|
+
updates.idle = false;
|
|
1335
1664
|
if (this.isAsync()) {
|
|
1336
1665
|
this.freeze();
|
|
1337
1666
|
this.updateViewsAsync(opt);
|
|
@@ -1345,12 +1674,25 @@ export const Paper = View.extend({
|
|
|
1345
1674
|
}
|
|
1346
1675
|
},
|
|
1347
1676
|
|
|
1677
|
+
wakeUp: function() {
|
|
1678
|
+
if (!this.isIdle()) return;
|
|
1679
|
+
this.unfreeze(this._updates.idle.wakeUpOptions);
|
|
1680
|
+
},
|
|
1681
|
+
|
|
1348
1682
|
isAsync: function() {
|
|
1349
1683
|
return !!this.options.async;
|
|
1350
1684
|
},
|
|
1351
1685
|
|
|
1352
1686
|
isFrozen: function() {
|
|
1353
|
-
return !!this.options.frozen;
|
|
1687
|
+
return !!this.options.frozen && !this.isIdle();
|
|
1688
|
+
},
|
|
1689
|
+
|
|
1690
|
+
isIdle: function() {
|
|
1691
|
+
if (this.legacyMode) {
|
|
1692
|
+
// Not implemented in the legacy mode.
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
return !!(this._updates && this._updates.idle);
|
|
1354
1696
|
},
|
|
1355
1697
|
|
|
1356
1698
|
isExactSorting: function() {
|
|
@@ -1650,21 +1992,57 @@ export const Paper = View.extend({
|
|
|
1650
1992
|
return restrictedArea;
|
|
1651
1993
|
},
|
|
1652
1994
|
|
|
1653
|
-
|
|
1995
|
+
_resolveCellViewPlaceholder: function(placeholder) {
|
|
1996
|
+
const { model, viewClass, cid } = placeholder;
|
|
1997
|
+
const view = this._initializeCellView(viewClass, model, cid);
|
|
1998
|
+
this._registerCellView(view);
|
|
1999
|
+
this._unregisterCellViewPlaceholder(placeholder);
|
|
2000
|
+
return view;
|
|
2001
|
+
},
|
|
1654
2002
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2003
|
+
_registerCellViewPlaceholder: function(cell, cid = uniqueId('view')) {
|
|
2004
|
+
const ViewClass = this._resolveCellViewClass(cell);
|
|
2005
|
+
const placeholder = {
|
|
2006
|
+
// A tag to identify the placeholder from a CellView.
|
|
2007
|
+
[CELL_VIEW_PLACEHOLDER_MARKER]: true,
|
|
2008
|
+
cid,
|
|
2009
|
+
model: cell,
|
|
2010
|
+
DETACHABLE: true,
|
|
2011
|
+
viewClass: ViewClass,
|
|
2012
|
+
UPDATE_PRIORITY: ViewClass.prototype.UPDATE_PRIORITY,
|
|
2013
|
+
};
|
|
2014
|
+
this._viewPlaceholders[cid] = placeholder;
|
|
2015
|
+
return placeholder;
|
|
2016
|
+
},
|
|
1658
2017
|
|
|
1659
|
-
|
|
1660
|
-
|
|
2018
|
+
_registerCellView: function(cellView) {
|
|
2019
|
+
cellView.paper = this;
|
|
2020
|
+
this._views[cellView.model.id] = cellView;
|
|
2021
|
+
},
|
|
1661
2022
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
var type = cell.get('type') + 'View';
|
|
1666
|
-
var namespaceViewClass = getByPath(namespace, type, '.');
|
|
2023
|
+
_unregisterCellViewPlaceholder: function(placeholder) {
|
|
2024
|
+
delete this._viewPlaceholders[placeholder.cid];
|
|
2025
|
+
},
|
|
1667
2026
|
|
|
2027
|
+
_initializeCellView: function(ViewClass, cell, cid) {
|
|
2028
|
+
const { options } = this;
|
|
2029
|
+
const { interactive, labelsLayer } = options;
|
|
2030
|
+
return new ViewClass({
|
|
2031
|
+
cid,
|
|
2032
|
+
model: cell,
|
|
2033
|
+
interactive,
|
|
2034
|
+
labelsLayer: labelsLayer === true ? LayersNames.LABELS : labelsLayer
|
|
2035
|
+
});
|
|
2036
|
+
},
|
|
2037
|
+
|
|
2038
|
+
_resolveCellViewClass: function(cell) {
|
|
2039
|
+
const { options } = this;
|
|
2040
|
+
const { cellViewNamespace } = options;
|
|
2041
|
+
const type = cell.get('type') + 'View';
|
|
2042
|
+
const namespaceViewClass = getByPath(cellViewNamespace, type, '.');
|
|
2043
|
+
// A class taken from the paper options.
|
|
2044
|
+
let optionalViewClass;
|
|
2045
|
+
let defaultViewClass;
|
|
1668
2046
|
if (cell.isLink()) {
|
|
1669
2047
|
optionalViewClass = options.linkView;
|
|
1670
2048
|
defaultViewClass = LinkView;
|
|
@@ -1672,7 +2050,6 @@ export const Paper = View.extend({
|
|
|
1672
2050
|
optionalViewClass = options.elementView;
|
|
1673
2051
|
defaultViewClass = ElementView;
|
|
1674
2052
|
}
|
|
1675
|
-
|
|
1676
2053
|
// a) the paper options view is a class (deprecated)
|
|
1677
2054
|
// 1. search the namespace for a view
|
|
1678
2055
|
// 2. if no view was found, use view from the paper options
|
|
@@ -1680,29 +2057,54 @@ export const Paper = View.extend({
|
|
|
1680
2057
|
// 1. call the function from the paper options
|
|
1681
2058
|
// 2. if no view was return, search the namespace for a view
|
|
1682
2059
|
// 3. if no view was found, use the default
|
|
1683
|
-
|
|
2060
|
+
return (optionalViewClass.prototype instanceof ViewBase)
|
|
1684
2061
|
? namespaceViewClass || optionalViewClass
|
|
1685
2062
|
: optionalViewClass.call(this, cell) || namespaceViewClass || defaultViewClass;
|
|
2063
|
+
},
|
|
1686
2064
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
2065
|
+
// Returns a CellView instance or its placeholder for the given cell.
|
|
2066
|
+
_getCellViewLike: function(cell) {
|
|
2067
|
+
|
|
2068
|
+
let id;
|
|
2069
|
+
if (isString(cell) || isNumber(cell)) {
|
|
2070
|
+
// If the cell is a string or number, it is an id of the view.
|
|
2071
|
+
id = cell;
|
|
2072
|
+
} else if (cell) {
|
|
2073
|
+
// If the cell is an object, it should have an id property.
|
|
2074
|
+
id = cell.id;
|
|
2075
|
+
} else {
|
|
2076
|
+
// If the cell is falsy, return null.
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const view = this._views[id];
|
|
2081
|
+
if (view) return view;
|
|
2082
|
+
|
|
2083
|
+
// If the view is not found, it may be a placeholder
|
|
2084
|
+
const cid = this._idToCid[id];
|
|
2085
|
+
if (cid) {
|
|
2086
|
+
return this._viewPlaceholders[cid];
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
return null;
|
|
1692
2090
|
},
|
|
1693
2091
|
|
|
1694
|
-
|
|
2092
|
+
createViewForModel: function(cell, cid) {
|
|
2093
|
+
return this._initializeCellView(this._resolveCellViewClass(cell), cell, cid);
|
|
2094
|
+
},
|
|
1695
2095
|
|
|
2096
|
+
removeView: function(cell) {
|
|
1696
2097
|
const { id } = cell;
|
|
1697
2098
|
const { _views, _updates } = this;
|
|
1698
2099
|
const view = _views[id];
|
|
1699
2100
|
if (view) {
|
|
1700
2101
|
var { cid } = view;
|
|
1701
|
-
const {
|
|
2102
|
+
const { mountedList, unmountedList } = _updates;
|
|
1702
2103
|
view.remove();
|
|
1703
2104
|
delete _views[id];
|
|
1704
|
-
delete
|
|
1705
|
-
delete
|
|
2105
|
+
delete this._idToCid[id];
|
|
2106
|
+
mountedList.delete(cid);
|
|
2107
|
+
unmountedList.delete(cid);
|
|
1706
2108
|
}
|
|
1707
2109
|
return view;
|
|
1708
2110
|
},
|
|
@@ -1716,7 +2118,7 @@ export const Paper = View.extend({
|
|
|
1716
2118
|
if (id in views) {
|
|
1717
2119
|
view = views[id];
|
|
1718
2120
|
if (view.model === cell) {
|
|
1719
|
-
flag =
|
|
2121
|
+
flag = this.FLAG_INSERT;
|
|
1720
2122
|
create = false;
|
|
1721
2123
|
} else {
|
|
1722
2124
|
// The view for this `id` already exist.
|
|
@@ -1726,14 +2128,42 @@ export const Paper = View.extend({
|
|
|
1726
2128
|
}
|
|
1727
2129
|
}
|
|
1728
2130
|
if (create) {
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2131
|
+
const { viewManagement } = this.options;
|
|
2132
|
+
const cid = uniqueId('view');
|
|
2133
|
+
this._idToCid[cell.id] = cid;
|
|
2134
|
+
if (viewManagement.lazyInitialize) {
|
|
2135
|
+
// Register only a placeholder for the view
|
|
2136
|
+
view = this._registerCellViewPlaceholder(cell, cid);
|
|
2137
|
+
flag = this.registerUnmountedView(view);
|
|
2138
|
+
} else {
|
|
2139
|
+
// Create a new view instance
|
|
2140
|
+
view = this.createViewForModel(cell, cid);
|
|
2141
|
+
this._registerCellView(view);
|
|
2142
|
+
flag = this.registerUnmountedView(view);
|
|
2143
|
+
// The newly created view needs to be initialized
|
|
2144
|
+
flag |= this.getCellViewInitFlag(view);
|
|
2145
|
+
}
|
|
2146
|
+
if (viewManagement.initializeUnmounted) {
|
|
2147
|
+
// Save the initialization flags for later and exit early
|
|
2148
|
+
this._mergeUnmountedViewScheduledUpdates(cid, flag);
|
|
2149
|
+
return view;
|
|
2150
|
+
}
|
|
1732
2151
|
}
|
|
2152
|
+
|
|
1733
2153
|
this.requestViewUpdate(view, flag, view.UPDATE_PRIORITY, opt);
|
|
2154
|
+
|
|
1734
2155
|
return view;
|
|
1735
2156
|
},
|
|
1736
2157
|
|
|
2158
|
+
// Update the view flags in the `unmountedList` using the bitwise OR operation
|
|
2159
|
+
_mergeUnmountedViewScheduledUpdates: function(cid, flag) {
|
|
2160
|
+
const { unmountedList } = this._updates;
|
|
2161
|
+
const unmountedItem = unmountedList.get(cid);
|
|
2162
|
+
if (unmountedItem) {
|
|
2163
|
+
unmountedItem.value |= flag;
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
|
|
1737
2167
|
onImageDragStart: function() {
|
|
1738
2168
|
// This is the only way to prevent image dragging in Firefox that works.
|
|
1739
2169
|
// Setting -moz-user-select: none, draggable="false" attribute or user-drag: none didn't help.
|
|
@@ -1744,11 +2174,11 @@ export const Paper = View.extend({
|
|
|
1744
2174
|
resetViews: function(cells, opt) {
|
|
1745
2175
|
opt || (opt = {});
|
|
1746
2176
|
cells || (cells = []);
|
|
2177
|
+
// Allows to unfreeze normally while in the idle state using autoFreeze option
|
|
2178
|
+
const key = (this.legacyMode ? this.options.autoFreeze : this.isIdle()) ? null : 'reset';
|
|
1747
2179
|
this._resetUpdates();
|
|
1748
2180
|
// clearing views removes any event listeners
|
|
1749
2181
|
this.removeViews();
|
|
1750
|
-
// Allows to unfreeze normally while in the idle state using autoFreeze option
|
|
1751
|
-
const key = this.options.autoFreeze ? null : 'reset';
|
|
1752
2182
|
this.freeze({ key });
|
|
1753
2183
|
for (var i = 0, n = cells.length; i < n; i++) {
|
|
1754
2184
|
this.renderView(cells[i], opt);
|
|
@@ -1758,10 +2188,16 @@ export const Paper = View.extend({
|
|
|
1758
2188
|
},
|
|
1759
2189
|
|
|
1760
2190
|
removeViews: function() {
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2191
|
+
// Remove all views and their references from the paper.
|
|
2192
|
+
for (const id in this._views) {
|
|
2193
|
+
const view = this._views[id];
|
|
2194
|
+
if (view) {
|
|
2195
|
+
view.remove();
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
1764
2198
|
this._views = {};
|
|
2199
|
+
this._viewPlaceholders = {};
|
|
2200
|
+
this._idToCid = {};
|
|
1765
2201
|
},
|
|
1766
2202
|
|
|
1767
2203
|
sortViews: function() {
|
|
@@ -1770,7 +2206,7 @@ export const Paper = View.extend({
|
|
|
1770
2206
|
// noop
|
|
1771
2207
|
return;
|
|
1772
2208
|
}
|
|
1773
|
-
if (this.isFrozen()) {
|
|
2209
|
+
if (this.isFrozen() || this.isIdle()) {
|
|
1774
2210
|
// sort views once unfrozen
|
|
1775
2211
|
this._updates.sort = true;
|
|
1776
2212
|
return;
|
|
@@ -1796,8 +2232,11 @@ export const Paper = View.extend({
|
|
|
1796
2232
|
},
|
|
1797
2233
|
|
|
1798
2234
|
insertView: function(view, isInitialInsert) {
|
|
1799
|
-
const layerView = this.getLayerView(LayersNames.CELLS);
|
|
1800
2235
|
const { el, model } = view;
|
|
2236
|
+
|
|
2237
|
+
const layerName = model.get('layer') || this.DEFAULT_CELL_LAYER;
|
|
2238
|
+
const layerView = this.getLayerView(layerName);
|
|
2239
|
+
|
|
1801
2240
|
switch (this.options.sorting) {
|
|
1802
2241
|
case sortingTypes.APPROX:
|
|
1803
2242
|
layerView.insertSortedNode(el, model.get('z'));
|
|
@@ -1810,9 +2249,58 @@ export const Paper = View.extend({
|
|
|
1810
2249
|
view.onMount(isInitialInsert);
|
|
1811
2250
|
},
|
|
1812
2251
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2252
|
+
_hideView: function(viewLike) {
|
|
2253
|
+
if (!viewLike || viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
2254
|
+
// A placeholder view was never mounted
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
if (viewLike[CELL_VIEW_MARKER]) {
|
|
2258
|
+
this._hideCellView(viewLike);
|
|
2259
|
+
} else {
|
|
2260
|
+
// A generic view that is not a cell view.
|
|
2261
|
+
viewLike.unmount();
|
|
2262
|
+
}
|
|
2263
|
+
},
|
|
2264
|
+
|
|
2265
|
+
// If `cellVisibility` returns `false`, the view will be hidden using this method.
|
|
2266
|
+
_hideCellView: function(cellView) {
|
|
2267
|
+
if (this.options.viewManagement.disposeHidden) {
|
|
2268
|
+
if (this._disposeCellView(cellView)) return;
|
|
2269
|
+
}
|
|
2270
|
+
// Detach the view from the paper, but keep it in memory
|
|
2271
|
+
this._detachCellView(cellView);
|
|
2272
|
+
},
|
|
2273
|
+
|
|
2274
|
+
_disposeCellView: function(cellView) {
|
|
2275
|
+
if (HighlighterView.has(cellView) || cellView.hasTools()) {
|
|
2276
|
+
// We currently do not dispose views which has a highlighter or tools attached
|
|
2277
|
+
// Note: Possible improvement would be to serialize highlighters/tools and
|
|
2278
|
+
// restore them on view re-mount.
|
|
2279
|
+
return false;
|
|
2280
|
+
}
|
|
2281
|
+
const cell = cellView.model;
|
|
2282
|
+
// Remove the view from the paper and dispose it
|
|
2283
|
+
cellView.remove();
|
|
2284
|
+
delete this._views[cell.id];
|
|
2285
|
+
this._registerCellViewPlaceholder(cell, cellView.cid);
|
|
2286
|
+
return true;
|
|
2287
|
+
},
|
|
2288
|
+
|
|
2289
|
+
// Dispose (release resources) all hidden views.
|
|
2290
|
+
disposeHiddenCellViews: function() {
|
|
2291
|
+
// Only cell views can be in the unmounted list (not in the legacy mode).
|
|
2292
|
+
if (this.legacyMode) return;
|
|
2293
|
+
const unmountedCids = this._updates.unmountedList.keys();
|
|
2294
|
+
for (const cid of unmountedCids) {
|
|
2295
|
+
const cellView = viewsRegistry[cid];
|
|
2296
|
+
cellView && this._disposeCellView(cellView);
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
|
|
2300
|
+
// Detach a view from the paper, but keep it in memory.
|
|
2301
|
+
_detachCellView(cellView) {
|
|
2302
|
+
cellView.unmount();
|
|
2303
|
+
cellView.onDetach();
|
|
1816
2304
|
},
|
|
1817
2305
|
|
|
1818
2306
|
// Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
|
|
@@ -1830,11 +2318,32 @@ export const Paper = View.extend({
|
|
|
1830
2318
|
},
|
|
1831
2319
|
|
|
1832
2320
|
// Find a view for a model `cell`. `cell` can also be a string or number representing a model `id`.
|
|
1833
|
-
findViewByModel: function(
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2321
|
+
findViewByModel: function(cellOrId) {
|
|
2322
|
+
|
|
2323
|
+
const cellViewLike = this._getCellViewLike(cellOrId);
|
|
2324
|
+
if (!cellViewLike) return undefined;
|
|
2325
|
+
if (cellViewLike[CELL_VIEW_MARKER]) {
|
|
2326
|
+
// If the view is not a placeholder, return it directly
|
|
2327
|
+
return cellViewLike;
|
|
2328
|
+
}
|
|
2329
|
+
// We do not expose placeholder views directly. We resolve them before returning.
|
|
2330
|
+
const cellView = this._resolveCellViewPlaceholder(cellViewLike);
|
|
2331
|
+
const flag = this.getCellViewInitFlag(cellView);
|
|
2332
|
+
if (this.isViewMounted(cellView)) {
|
|
2333
|
+
// The view was acting as a placeholder and is already present in the `mounted` list,
|
|
2334
|
+
// indicating that its visibility has been checked, but the update hasn't occurred yet.
|
|
2335
|
+
// Placeholders are resolved during the update routine. Since we're handling it
|
|
2336
|
+
// manually here, we must ensure the view is properly initialized on the next update.
|
|
2337
|
+
this.scheduleViewUpdate(cellView, flag, cellView.UPDATE_PRIORITY, {
|
|
2338
|
+
// It's important to run in isolation to avoid triggering the update of
|
|
2339
|
+
// connected links
|
|
2340
|
+
isolate: true
|
|
2341
|
+
});
|
|
2342
|
+
} else {
|
|
2343
|
+
// Update the flags in the `unmounted` list
|
|
2344
|
+
this._mergeUnmountedViewScheduledUpdates(cellView.cid, flag);
|
|
2345
|
+
}
|
|
2346
|
+
return cellView;
|
|
1838
2347
|
},
|
|
1839
2348
|
|
|
1840
2349
|
// Find all views at given point
|
|
@@ -1913,6 +2422,92 @@ export const Paper = View.extend({
|
|
|
1913
2422
|
);
|
|
1914
2423
|
},
|
|
1915
2424
|
|
|
2425
|
+
findClosestMagnetToPoint: function(point, options = {}) {
|
|
2426
|
+
let minDistance = Number.MAX_SAFE_INTEGER;
|
|
2427
|
+
let bestPriority = -Infinity;
|
|
2428
|
+
const pointer = new Point(point);
|
|
2429
|
+
|
|
2430
|
+
const radius = options.radius || Number.MAX_SAFE_INTEGER;
|
|
2431
|
+
const viewsInArea = this.findCellViewsInArea(
|
|
2432
|
+
{ x: pointer.x - radius, y: pointer.y - radius, width: 2 * radius, height: 2 * radius },
|
|
2433
|
+
options.findInAreaOptions
|
|
2434
|
+
);
|
|
2435
|
+
// Enable all connections by default
|
|
2436
|
+
const filterFn = typeof options.filter === 'function' ? options.filter : null;
|
|
2437
|
+
|
|
2438
|
+
let closestView = null;
|
|
2439
|
+
let closestMagnet = null;
|
|
2440
|
+
|
|
2441
|
+
// Note: If snapRadius is smaller than magnet size, views will not be found.
|
|
2442
|
+
viewsInArea.forEach((view) => {
|
|
2443
|
+
|
|
2444
|
+
const candidates = [];
|
|
2445
|
+
const { model } = view;
|
|
2446
|
+
// skip connecting to the element in case '.': { magnet: false } attribute present
|
|
2447
|
+
if (view.el.getAttribute('magnet') !== 'false') {
|
|
2448
|
+
|
|
2449
|
+
if (model.isLink()) {
|
|
2450
|
+
const connection = view.getConnection();
|
|
2451
|
+
candidates.push({
|
|
2452
|
+
// find distance from the closest point of a link to pointer coordinates
|
|
2453
|
+
priority: 0,
|
|
2454
|
+
distance: connection.closestPoint(pointer).squaredDistance(pointer),
|
|
2455
|
+
magnet: view.el
|
|
2456
|
+
});
|
|
2457
|
+
} else {
|
|
2458
|
+
candidates.push({
|
|
2459
|
+
// Set the priority to the level of nested elements of the model
|
|
2460
|
+
// To ensure that the embedded cells get priority over the parent cells
|
|
2461
|
+
priority: model.getAncestors().length,
|
|
2462
|
+
// find distance from the center of the model to pointer coordinates
|
|
2463
|
+
distance: model.getBBox().center().squaredDistance(pointer),
|
|
2464
|
+
magnet: view.el
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
view.$('[magnet]').toArray().forEach(magnet => {
|
|
2470
|
+
|
|
2471
|
+
const magnetBBox = view.getNodeBBox(magnet);
|
|
2472
|
+
let magnetDistance = magnetBBox.pointNearestToPoint(pointer).squaredDistance(pointer);
|
|
2473
|
+
if (magnetBBox.containsPoint(pointer)) {
|
|
2474
|
+
// Pointer sits inside this magnet.
|
|
2475
|
+
// Push its distance far into the negative range so any
|
|
2476
|
+
// "under-pointer" magnet outranks magnets that are only nearby
|
|
2477
|
+
// (positive distance) and every non-magnet candidate.
|
|
2478
|
+
// We add the original distance back to keep ordering among
|
|
2479
|
+
// overlapping magnets: the one whose border is closest to the
|
|
2480
|
+
// pointer (smaller original distance) still wins.
|
|
2481
|
+
magnetDistance = -Number.MAX_SAFE_INTEGER + magnetDistance;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// Check if magnet is inside the snap radius.
|
|
2485
|
+
if (magnetDistance <= radius * radius) {
|
|
2486
|
+
candidates.push({
|
|
2487
|
+
// Give magnets priority over other candidates.
|
|
2488
|
+
priority: Number.MAX_SAFE_INTEGER,
|
|
2489
|
+
distance: magnetDistance,
|
|
2490
|
+
magnet
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
candidates.forEach(candidate => {
|
|
2496
|
+
const { magnet, distance, priority } = candidate;
|
|
2497
|
+
const isBetterCandidate = (priority > bestPriority) || (priority === bestPriority && distance < minDistance);
|
|
2498
|
+
if (isBetterCandidate && (!filterFn || filterFn(view, magnet))) {
|
|
2499
|
+
bestPriority = priority;
|
|
2500
|
+
minDistance = distance;
|
|
2501
|
+
closestView = view;
|
|
2502
|
+
closestMagnet = magnet;
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
return closestView ? { view: closestView, magnet: closestMagnet } : null;
|
|
2509
|
+
},
|
|
2510
|
+
|
|
1916
2511
|
_findInExtendedArea: function(area, findCellsFn, opt = {}) {
|
|
1917
2512
|
const {
|
|
1918
2513
|
buffer = this.DEFAULT_FIND_BUFFER,
|
|
@@ -2365,8 +2960,11 @@ export const Paper = View.extend({
|
|
|
2365
2960
|
|
|
2366
2961
|
var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
|
|
2367
2962
|
|
|
2368
|
-
|
|
2963
|
+
let view = data.sourceView;
|
|
2369
2964
|
if (view) {
|
|
2965
|
+
// The view could have been disposed during dragging
|
|
2966
|
+
// e.g. dragged outside of the viewport and hidden
|
|
2967
|
+
view = this.findViewByModel(view.model);
|
|
2370
2968
|
view.pointermove(evt, localPoint.x, localPoint.y);
|
|
2371
2969
|
} else {
|
|
2372
2970
|
this.trigger('blank:pointermove', evt, localPoint.x, localPoint.y);
|
|
@@ -2383,8 +2981,11 @@ export const Paper = View.extend({
|
|
|
2383
2981
|
|
|
2384
2982
|
var localPoint = this.snapToGrid(normalizedEvt.clientX, normalizedEvt.clientY);
|
|
2385
2983
|
|
|
2386
|
-
|
|
2984
|
+
let view = this.eventData(evt).sourceView;
|
|
2387
2985
|
if (view) {
|
|
2986
|
+
// The view could have been disposed during dragging
|
|
2987
|
+
// e.g. dragged outside of the viewport and hidden
|
|
2988
|
+
view = this.findViewByModel(view.model);
|
|
2388
2989
|
view.pointerup(normalizedEvt, localPoint.x, localPoint.y);
|
|
2389
2990
|
} else {
|
|
2390
2991
|
this.trigger('blank:pointerup', normalizedEvt, localPoint.x, localPoint.y);
|
|
@@ -3254,4 +3855,3 @@ export const Paper = View.extend({
|
|
|
3254
3855
|
}]
|
|
3255
3856
|
}
|
|
3256
3857
|
});
|
|
3257
|
-
|