@joint/core 4.2.0-alpha.0 → 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 +3 -1
- package/dist/geometry.js +2 -2
- package/dist/geometry.min.js +3 -2
- package/dist/joint.d.ts +280 -149
- package/dist/joint.js +1427 -475
- package/dist/joint.min.js +4 -3
- package/dist/joint.nowrap.js +1427 -475
- package/dist/joint.nowrap.min.js +4 -3
- package/dist/vectorizer.js +21 -8
- package/dist/vectorizer.min.js +4 -3
- package/dist/version.mjs +1 -1
- package/package.json +7 -7
- package/src/V/index.mjs +20 -5
- package/src/alg/Deque.mjs +126 -0
- package/src/cellTools/Boundary.mjs +15 -13
- package/src/cellTools/Button.mjs +7 -5
- package/src/cellTools/Control.mjs +37 -14
- package/src/cellTools/HoverConnect.mjs +5 -1
- package/src/cellTools/helpers.mjs +44 -3
- package/src/config/index.mjs +8 -0
- package/src/dia/Cell.mjs +26 -10
- package/src/dia/CellView.mjs +7 -0
- package/src/dia/Element.mjs +4 -2
- package/src/dia/ElementView.mjs +2 -1
- package/src/dia/HighlighterView.mjs +22 -0
- package/src/dia/LinkView.mjs +118 -98
- package/src/dia/Paper.mjs +697 -209
- package/src/dia/ToolView.mjs +4 -0
- package/src/dia/ToolsView.mjs +12 -3
- package/src/dia/attributes/text.mjs +4 -2
- package/src/dia/ports.mjs +202 -82
- package/src/elementTools/HoverConnect.mjs +14 -8
- package/src/env/index.mjs +6 -3
- package/src/layout/ports/port.mjs +30 -15
- package/src/layout/ports/portLabel.mjs +1 -1
- package/src/mvc/View.mjs +4 -0
- package/src/mvc/ViewBase.mjs +1 -1
- package/types/geometry.d.ts +64 -60
- package/types/joint.d.ts +205 -88
- package/types/vectorizer.d.ts +11 -1
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
|
|
|
@@ -390,6 +397,11 @@ export const Paper = View.extend({
|
|
|
390
397
|
// Default layer to insert the cell views into.
|
|
391
398
|
DEFAULT_CELL_LAYER: LayersNames.CELLS,
|
|
392
399
|
|
|
400
|
+
// Update flags
|
|
401
|
+
FLAG_INSERT: 1<<30,
|
|
402
|
+
FLAG_REMOVE: 1<<29,
|
|
403
|
+
FLAG_INIT: 1<<28,
|
|
404
|
+
|
|
393
405
|
init: function() {
|
|
394
406
|
|
|
395
407
|
const { options } = this;
|
|
@@ -401,6 +413,10 @@ export const Paper = View.extend({
|
|
|
401
413
|
|
|
402
414
|
const model = this.model = options.model || new Graph;
|
|
403
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
|
+
|
|
404
420
|
// Layers (SVGGroups)
|
|
405
421
|
this._layers = {
|
|
406
422
|
viewsMap: {},
|
|
@@ -415,6 +431,8 @@ export const Paper = View.extend({
|
|
|
415
431
|
|
|
416
432
|
// Hash of all cell views.
|
|
417
433
|
this._views = {};
|
|
434
|
+
this._viewPlaceholders = {};
|
|
435
|
+
this._idToCid = {};
|
|
418
436
|
|
|
419
437
|
// Mouse wheel events buffer
|
|
420
438
|
this._mw_evt_buffer = {
|
|
@@ -424,8 +442,6 @@ export const Paper = View.extend({
|
|
|
424
442
|
|
|
425
443
|
// Render existing cells in the graph
|
|
426
444
|
this.resetViews(model.attributes.cells.models);
|
|
427
|
-
// Start the Rendering Loop
|
|
428
|
-
if (!this.isFrozen() && this.isAsync()) this.updateViewsAsync();
|
|
429
445
|
},
|
|
430
446
|
|
|
431
447
|
_resetUpdates: function() {
|
|
@@ -434,16 +450,15 @@ export const Paper = View.extend({
|
|
|
434
450
|
return this._updates = {
|
|
435
451
|
id: null,
|
|
436
452
|
priorities: [{}, {}, {}],
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
unmounted: {},
|
|
440
|
-
mounted: {},
|
|
453
|
+
unmountedList: new Deque(),
|
|
454
|
+
mountedList: new Deque(),
|
|
441
455
|
count: 0,
|
|
442
456
|
keyFrozen: false,
|
|
443
457
|
freezeKey: null,
|
|
444
458
|
sort: false,
|
|
445
459
|
disabled: false,
|
|
446
|
-
idle: false
|
|
460
|
+
idle: false,
|
|
461
|
+
freshAfterReset: true,
|
|
447
462
|
};
|
|
448
463
|
},
|
|
449
464
|
|
|
@@ -472,8 +487,13 @@ export const Paper = View.extend({
|
|
|
472
487
|
},
|
|
473
488
|
|
|
474
489
|
onCellRemoved: function(cell, _, opt) {
|
|
475
|
-
const
|
|
476
|
-
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
|
+
}
|
|
477
497
|
},
|
|
478
498
|
|
|
479
499
|
onCellChange: function(cell, opt) {
|
|
@@ -482,8 +502,10 @@ export const Paper = View.extend({
|
|
|
482
502
|
cell.hasChanged('layer') ||
|
|
483
503
|
(cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX)
|
|
484
504
|
) {
|
|
485
|
-
const
|
|
486
|
-
if (
|
|
505
|
+
const viewLike = this._getCellViewLike(cell);
|
|
506
|
+
if (viewLike) {
|
|
507
|
+
this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
|
|
508
|
+
}
|
|
487
509
|
}
|
|
488
510
|
},
|
|
489
511
|
|
|
@@ -498,7 +520,7 @@ export const Paper = View.extend({
|
|
|
498
520
|
},
|
|
499
521
|
|
|
500
522
|
onGraphBatchStop: function(data) {
|
|
501
|
-
if (this.isFrozen()) return;
|
|
523
|
+
if (this.isFrozen() || this.isIdle()) return;
|
|
502
524
|
var name = data && data.batchName;
|
|
503
525
|
var graph = this.model;
|
|
504
526
|
if (!this.isAsync()) {
|
|
@@ -558,6 +580,15 @@ export const Paper = View.extend({
|
|
|
558
580
|
// Return the default highlighting options into the user specified options.
|
|
559
581
|
options.highlighting = defaultsDeep({}, highlighting, defaultHighlighting);
|
|
560
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
|
+
});
|
|
561
592
|
},
|
|
562
593
|
|
|
563
594
|
children: function() {
|
|
@@ -925,47 +956,46 @@ export const Paper = View.extend({
|
|
|
925
956
|
var links = this.model.getConnectedLinks(model);
|
|
926
957
|
for (var j = 0, n = links.length; j < n; j++) {
|
|
927
958
|
var link = links[j];
|
|
928
|
-
var linkView = this.
|
|
959
|
+
var linkView = this._getCellViewLike(link);
|
|
929
960
|
if (!linkView) continue;
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
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;
|
|
933
964
|
var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
|
|
934
|
-
this.scheduleViewUpdate(linkView, linkView.getFlag(
|
|
965
|
+
this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
|
|
935
966
|
}
|
|
936
967
|
}
|
|
937
968
|
},
|
|
938
969
|
|
|
939
970
|
forcePostponedViewUpdate: function(view, flag) {
|
|
940
971
|
if (!view || !(view instanceof CellView)) return false;
|
|
941
|
-
|
|
972
|
+
const model = view.model;
|
|
942
973
|
if (model.isElement()) return false;
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
return !this.dumpView(view, dumpOptions);
|
|
962
|
-
}
|
|
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);
|
|
963
992
|
}
|
|
964
993
|
return false;
|
|
965
994
|
},
|
|
966
995
|
|
|
967
996
|
requestViewUpdate: function(view, flag, priority, opt) {
|
|
968
997
|
opt || (opt = {});
|
|
998
|
+
// Note: `scheduleViewUpdate` wakes up the paper if it is idle.
|
|
969
999
|
this.scheduleViewUpdate(view, flag, priority, opt);
|
|
970
1000
|
var isAsync = this.isAsync();
|
|
971
1001
|
if (this.isFrozen() || (isAsync && opt.async !== false)) return;
|
|
@@ -976,13 +1006,14 @@ export const Paper = View.extend({
|
|
|
976
1006
|
|
|
977
1007
|
scheduleViewUpdate: function(view, type, priority, opt) {
|
|
978
1008
|
const { _updates: updates, options } = this;
|
|
979
|
-
if (updates.idle) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
this.
|
|
983
|
-
}
|
|
1009
|
+
if (updates.idle && options.autoFreeze) {
|
|
1010
|
+
this.legacyMode
|
|
1011
|
+
? this.unfreeze() // Restart rendering loop without original options
|
|
1012
|
+
: this.wakeUp();
|
|
984
1013
|
}
|
|
985
|
-
const { FLAG_REMOVE, FLAG_INSERT
|
|
1014
|
+
const { FLAG_REMOVE, FLAG_INSERT } = this;
|
|
1015
|
+
const { UPDATE_PRIORITY, cid } = view;
|
|
1016
|
+
|
|
986
1017
|
let priorityUpdates = updates.priorities[priority];
|
|
987
1018
|
if (!priorityUpdates) priorityUpdates = updates.priorities[priority] = {};
|
|
988
1019
|
// Move higher priority updates to this priority
|
|
@@ -1028,20 +1059,18 @@ export const Paper = View.extend({
|
|
|
1028
1059
|
dumpView: function(view, opt = {}) {
|
|
1029
1060
|
const flag = this.dumpViewUpdate(view);
|
|
1030
1061
|
if (!flag) return 0;
|
|
1031
|
-
|
|
1032
|
-
if (shouldNotify) this.notifyBeforeRender(opt);
|
|
1062
|
+
this.notifyBeforeRender(opt);
|
|
1033
1063
|
const leftover = this.updateView(view, flag, opt);
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
this.notifyAfterRender(stats, opt);
|
|
1037
|
-
}
|
|
1064
|
+
const stats = { updated: 1, priority: view.UPDATE_PRIORITY };
|
|
1065
|
+
this.notifyAfterRender(stats, opt);
|
|
1038
1066
|
return leftover;
|
|
1039
1067
|
},
|
|
1040
1068
|
|
|
1041
1069
|
updateView: function(view, flag, opt) {
|
|
1042
1070
|
if (!view) return 0;
|
|
1043
|
-
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT
|
|
1044
|
-
|
|
1071
|
+
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT } = this;
|
|
1072
|
+
const { model } = view;
|
|
1073
|
+
if (view[CELL_VIEW_MARKER]) {
|
|
1045
1074
|
if (flag & FLAG_REMOVE) {
|
|
1046
1075
|
this.removeView(model);
|
|
1047
1076
|
return 0;
|
|
@@ -1069,57 +1098,70 @@ export const Paper = View.extend({
|
|
|
1069
1098
|
registerUnmountedView: function(view) {
|
|
1070
1099
|
var cid = view.cid;
|
|
1071
1100
|
var updates = this._updates;
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
updates.
|
|
1075
|
-
|
|
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);
|
|
1076
1105
|
return flag;
|
|
1077
1106
|
},
|
|
1078
1107
|
|
|
1079
1108
|
registerMountedView: function(view) {
|
|
1080
1109
|
var cid = view.cid;
|
|
1081
1110
|
var updates = this._updates;
|
|
1082
|
-
if (
|
|
1083
|
-
updates.
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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);
|
|
1087
1116
|
return flag;
|
|
1088
1117
|
},
|
|
1089
1118
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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);
|
|
1123
|
+
},
|
|
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);
|
|
1095
1134
|
},
|
|
1096
1135
|
|
|
1136
|
+
/**
|
|
1137
|
+
* @deprecated use `updateCellsVisibility` instead.
|
|
1138
|
+
* `paper.updateCellsVisibility({ cellVisibility: () => true });`
|
|
1139
|
+
*/
|
|
1097
1140
|
dumpViews: function(opt) {
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
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);
|
|
1101
1144
|
},
|
|
1102
1145
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* Process all scheduled updates synchronously.
|
|
1148
|
+
*/
|
|
1149
|
+
updateViews: function(opt = {}) {
|
|
1105
1150
|
this.notifyBeforeRender(opt);
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
updateCount += batchStats.updated;
|
|
1114
|
-
priority = Math.min(batchStats.priority, priority);
|
|
1115
|
-
} while (!batchStats.empty);
|
|
1116
|
-
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
|
+
};
|
|
1117
1158
|
this.notifyAfterRender(stats, opt);
|
|
1118
1159
|
return stats;
|
|
1119
1160
|
},
|
|
1120
1161
|
|
|
1121
1162
|
hasScheduledUpdates: function() {
|
|
1122
|
-
const
|
|
1163
|
+
const updates = this._updates;
|
|
1164
|
+
const priorities = updates.priorities;
|
|
1123
1165
|
const priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
1124
1166
|
let i = priorityIndexes.length;
|
|
1125
1167
|
while (i > 0 && i--) {
|
|
@@ -1131,11 +1173,38 @@ export const Paper = View.extend({
|
|
|
1131
1173
|
|
|
1132
1174
|
updateViewsAsync: function(opt, data) {
|
|
1133
1175
|
opt || (opt = {});
|
|
1134
|
-
data || (data = {
|
|
1176
|
+
data || (data = {
|
|
1177
|
+
processed: 0,
|
|
1178
|
+
priority: MIN_PRIORITY,
|
|
1179
|
+
checkedUnmounted: 0,
|
|
1180
|
+
checkedMounted: 0,
|
|
1181
|
+
});
|
|
1135
1182
|
const { _updates: updates, options } = this;
|
|
1136
|
-
const id = updates
|
|
1137
|
-
|
|
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.
|
|
1138
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) {
|
|
1139
1208
|
if (data.processed === 0 && this.hasScheduledUpdates()) {
|
|
1140
1209
|
this.notifyBeforeRender(opt);
|
|
1141
1210
|
}
|
|
@@ -1144,7 +1213,7 @@ export const Paper = View.extend({
|
|
|
1144
1213
|
mountBatchSize: MOUNT_BATCH_SIZE - stats.mounted,
|
|
1145
1214
|
unmountBatchSize: MOUNT_BATCH_SIZE - stats.unmounted
|
|
1146
1215
|
});
|
|
1147
|
-
const checkStats = this.
|
|
1216
|
+
const checkStats = this.scheduleCellsVisibilityUpdate(passingOpt);
|
|
1148
1217
|
const unmountCount = checkStats.unmounted;
|
|
1149
1218
|
const mountCount = checkStats.mounted;
|
|
1150
1219
|
let processed = data.processed;
|
|
@@ -1165,11 +1234,22 @@ export const Paper = View.extend({
|
|
|
1165
1234
|
} else {
|
|
1166
1235
|
data.processed = processed;
|
|
1167
1236
|
}
|
|
1237
|
+
data.checkedUnmounted = 0;
|
|
1238
|
+
data.checkedMounted = 0;
|
|
1168
1239
|
} else {
|
|
1169
|
-
|
|
1170
|
-
|
|
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.
|
|
1171
1251
|
this.freeze();
|
|
1172
|
-
updates.idle =
|
|
1252
|
+
updates.idle = { wakeUpOptions: opt };
|
|
1173
1253
|
this.trigger('render:idle', opt);
|
|
1174
1254
|
}
|
|
1175
1255
|
}
|
|
@@ -1189,6 +1269,7 @@ export const Paper = View.extend({
|
|
|
1189
1269
|
},
|
|
1190
1270
|
|
|
1191
1271
|
notifyBeforeRender: function(opt = {}) {
|
|
1272
|
+
if (opt.silent) return;
|
|
1192
1273
|
let beforeFn = opt.beforeRender;
|
|
1193
1274
|
if (typeof beforeFn !== 'function') {
|
|
1194
1275
|
beforeFn = this.options.beforeRender;
|
|
@@ -1198,6 +1279,7 @@ export const Paper = View.extend({
|
|
|
1198
1279
|
},
|
|
1199
1280
|
|
|
1200
1281
|
notifyAfterRender: function(stats, opt = {}) {
|
|
1282
|
+
if (opt.silent) return;
|
|
1201
1283
|
let afterFn = opt.afterRender;
|
|
1202
1284
|
if (typeof afterFn !== 'function') {
|
|
1203
1285
|
afterFn = this.options.afterRender;
|
|
@@ -1208,6 +1290,56 @@ export const Paper = View.extend({
|
|
|
1208
1290
|
this.trigger('render:done', stats, opt);
|
|
1209
1291
|
},
|
|
1210
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
|
+
|
|
1211
1343
|
updateViewsBatch: function(opt) {
|
|
1212
1344
|
opt || (opt = {});
|
|
1213
1345
|
var batchSize = opt.batchSize || UPDATE_BATCH_SIZE;
|
|
@@ -1220,8 +1352,7 @@ export const Paper = View.extend({
|
|
|
1220
1352
|
var empty = true;
|
|
1221
1353
|
var options = this.options;
|
|
1222
1354
|
var priorities = updates.priorities;
|
|
1223
|
-
|
|
1224
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
1355
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1225
1356
|
var postponeViewFn = options.onViewPostponed;
|
|
1226
1357
|
if (typeof postponeViewFn !== 'function') postponeViewFn = null;
|
|
1227
1358
|
var priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
@@ -1233,33 +1364,56 @@ export const Paper = View.extend({
|
|
|
1233
1364
|
empty = false;
|
|
1234
1365
|
break main;
|
|
1235
1366
|
}
|
|
1236
|
-
var view =
|
|
1367
|
+
var view = viewsRegistry[cid];
|
|
1237
1368
|
if (!view) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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
|
+
}
|
|
1241
1380
|
}
|
|
1242
1381
|
var currentFlag = priorityUpdates[cid];
|
|
1243
|
-
if ((currentFlag &
|
|
1382
|
+
if ((currentFlag & this.FLAG_REMOVE) === 0) {
|
|
1244
1383
|
// We should never check a view for viewport if we are about to remove the view
|
|
1245
|
-
|
|
1246
|
-
if (
|
|
1384
|
+
const isMounted = !updates.unmountedList.has(cid);
|
|
1385
|
+
if (!this._evalCellVisibility(view, isMounted, visibilityCb)) {
|
|
1247
1386
|
// Unmount View
|
|
1248
|
-
if (
|
|
1387
|
+
if (isMounted) {
|
|
1388
|
+
// The view is currently mounted. Hide the view (detach or remove it).
|
|
1249
1389
|
this.registerUnmountedView(view);
|
|
1250
|
-
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);
|
|
1251
1395
|
}
|
|
1252
|
-
|
|
1396
|
+
// Delete the current update as it has been processed.
|
|
1253
1397
|
delete priorityUpdates[cid];
|
|
1254
1398
|
unmountCount++;
|
|
1255
1399
|
continue;
|
|
1256
1400
|
}
|
|
1257
1401
|
// Mount View
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
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;
|
|
1260
1410
|
mountCount++;
|
|
1261
1411
|
}
|
|
1262
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;
|
|
1263
1417
|
}
|
|
1264
1418
|
var leftoverFlag = this.updateView(view, currentFlag, opt);
|
|
1265
1419
|
if (leftoverFlag > 0) {
|
|
@@ -1286,104 +1440,125 @@ export const Paper = View.extend({
|
|
|
1286
1440
|
};
|
|
1287
1441
|
},
|
|
1288
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
|
+
*/
|
|
1289
1452
|
getUnmountedViews: function() {
|
|
1290
1453
|
const updates = this._updates;
|
|
1291
|
-
const
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
for (
|
|
1295
|
-
|
|
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];
|
|
1296
1461
|
}
|
|
1297
1462
|
return unmountedViews;
|
|
1298
1463
|
},
|
|
1299
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
|
+
*/
|
|
1300
1470
|
getMountedViews: function() {
|
|
1301
1471
|
const updates = this._updates;
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
for (
|
|
1306
|
-
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];
|
|
1307
1477
|
}
|
|
1308
1478
|
return mountedViews;
|
|
1309
1479
|
},
|
|
1310
1480
|
|
|
1311
|
-
checkUnmountedViews: function(
|
|
1481
|
+
checkUnmountedViews: function(visibilityCb, opt) {
|
|
1312
1482
|
opt || (opt = {});
|
|
1313
1483
|
var mountCount = 0;
|
|
1314
|
-
if (typeof
|
|
1484
|
+
if (typeof visibilityCb !== 'function') visibilityCb = null;
|
|
1315
1485
|
var batchSize = 'mountBatchSize' in opt ? opt.mountBatchSize : Infinity;
|
|
1316
1486
|
var updates = this._updates;
|
|
1317
|
-
var
|
|
1318
|
-
var
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
if (!
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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)) {
|
|
1325
1496
|
// Push at the end of all unmounted ids, so this can be check later again
|
|
1326
|
-
|
|
1497
|
+
unmountedList.rotate();
|
|
1327
1498
|
continue;
|
|
1328
1499
|
}
|
|
1500
|
+
// Remove the view from the unmounted list
|
|
1501
|
+
const { value: prevFlag } = unmountedList.popHead();
|
|
1329
1502
|
mountCount++;
|
|
1330
|
-
|
|
1503
|
+
const flag = this.registerMountedView(view) | prevFlag;
|
|
1331
1504
|
if (flag) this.scheduleViewUpdate(view, flag, view.UPDATE_PRIORITY, { mounting: true });
|
|
1332
1505
|
}
|
|
1333
|
-
// Get rid of views, that have been mounted
|
|
1334
|
-
unmountedCids.splice(0, i);
|
|
1335
1506
|
return mountCount;
|
|
1336
1507
|
},
|
|
1337
1508
|
|
|
1338
|
-
checkMountedViews: function(
|
|
1509
|
+
checkMountedViews: function(visibilityCb, opt) {
|
|
1339
1510
|
opt || (opt = {});
|
|
1340
1511
|
var unmountCount = 0;
|
|
1341
|
-
if (typeof
|
|
1512
|
+
if (typeof visibilityCb !== 'function') return unmountCount;
|
|
1342
1513
|
var batchSize = 'unmountBatchSize' in opt ? opt.unmountBatchSize : Infinity;
|
|
1343
1514
|
var updates = this._updates;
|
|
1344
|
-
|
|
1345
|
-
var
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
if (!
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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)) {
|
|
1352
1526
|
// Push at the end of all mounted ids, so this can be check later again
|
|
1353
|
-
|
|
1527
|
+
mountedList.rotate();
|
|
1354
1528
|
continue;
|
|
1355
1529
|
}
|
|
1530
|
+
// Remove the view from the mounted list
|
|
1531
|
+
mountedList.popHead();
|
|
1356
1532
|
unmountCount++;
|
|
1357
1533
|
var flag = this.registerUnmountedView(view);
|
|
1358
|
-
if (flag)
|
|
1534
|
+
if (flag) {
|
|
1535
|
+
this._hideView(view);
|
|
1536
|
+
}
|
|
1359
1537
|
}
|
|
1360
|
-
// Get rid of views, that have been unmounted
|
|
1361
|
-
mountedCids.splice(0, i);
|
|
1362
1538
|
return unmountCount;
|
|
1363
1539
|
},
|
|
1364
1540
|
|
|
1365
1541
|
checkViewVisibility: function(cellView, opt = {}) {
|
|
1366
|
-
|
|
1367
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
1542
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1368
1543
|
const updates = this._updates;
|
|
1369
|
-
const {
|
|
1370
|
-
|
|
1544
|
+
const { mountedList, unmountedList } = updates;
|
|
1545
|
+
|
|
1546
|
+
const visible = this._evalCellVisibility(cellView, false, visibilityCb);
|
|
1371
1547
|
|
|
1372
1548
|
let isUnmounted = false;
|
|
1373
1549
|
let isMounted = false;
|
|
1374
1550
|
|
|
1375
|
-
if (cellView.cid
|
|
1551
|
+
if (mountedList.has(cellView.cid) && !visible) {
|
|
1376
1552
|
const flag = this.registerUnmountedView(cellView);
|
|
1377
|
-
if (flag) this.
|
|
1378
|
-
|
|
1379
|
-
updates.mountedCids.splice(i, 1);
|
|
1553
|
+
if (flag) this._hideView(cellView);
|
|
1554
|
+
mountedList.delete(cellView.cid);
|
|
1380
1555
|
isUnmounted = true;
|
|
1381
1556
|
}
|
|
1382
1557
|
|
|
1383
|
-
if (!isUnmounted && cellView.cid
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
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);
|
|
1387
1562
|
if (flag) this.scheduleViewUpdate(cellView, flag, cellView.UPDATE_PRIORITY, { mounting: true });
|
|
1388
1563
|
isMounted = true;
|
|
1389
1564
|
}
|
|
@@ -1394,25 +1569,65 @@ export const Paper = View.extend({
|
|
|
1394
1569
|
};
|
|
1395
1570
|
},
|
|
1396
1571
|
|
|
1397
|
-
|
|
1398
|
-
|
|
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, {
|
|
1399
1606
|
mountBatchSize: Infinity,
|
|
1400
1607
|
unmountBatchSize: Infinity
|
|
1401
1608
|
});
|
|
1402
|
-
|
|
1403
|
-
|
|
1609
|
+
const visibilityCb = this._getCellVisibilityCallback(passingOpt);
|
|
1610
|
+
const unmountedCount = this.checkMountedViews(visibilityCb, passingOpt);
|
|
1404
1611
|
if (unmountedCount > 0) {
|
|
1405
1612
|
// Do not check views, that have been just unmounted and pushed at the end of the cids array
|
|
1406
|
-
var
|
|
1407
|
-
passingOpt.mountBatchSize = Math.min(
|
|
1613
|
+
var unmountedList = this._updates.unmountedList;
|
|
1614
|
+
passingOpt.mountBatchSize = Math.min(unmountedList.length - unmountedCount, passingOpt.mountBatchSize);
|
|
1408
1615
|
}
|
|
1409
|
-
|
|
1616
|
+
const mountedCount = this.checkUnmountedViews(visibilityCb, passingOpt);
|
|
1410
1617
|
return {
|
|
1411
1618
|
mounted: mountedCount,
|
|
1412
1619
|
unmounted: unmountedCount
|
|
1413
1620
|
};
|
|
1414
1621
|
},
|
|
1415
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
|
+
|
|
1416
1631
|
freeze: function(opt) {
|
|
1417
1632
|
opt || (opt = {});
|
|
1418
1633
|
var updates = this._updates;
|
|
@@ -1428,6 +1643,10 @@ export const Paper = View.extend({
|
|
|
1428
1643
|
this.options.frozen = true;
|
|
1429
1644
|
var id = updates.id;
|
|
1430
1645
|
updates.id = null;
|
|
1646
|
+
if (!this.legacyMode) {
|
|
1647
|
+
// Make sure the `freeze()` method ends the idle state.
|
|
1648
|
+
updates.idle = false;
|
|
1649
|
+
}
|
|
1431
1650
|
if (this.isAsync() && id) cancelFrame(id);
|
|
1432
1651
|
},
|
|
1433
1652
|
|
|
@@ -1441,6 +1660,7 @@ export const Paper = View.extend({
|
|
|
1441
1660
|
updates.freezeKey = null;
|
|
1442
1661
|
// key passed, but the paper is already freezed
|
|
1443
1662
|
if (key && key === freezeKey && updates.keyFrozen) return;
|
|
1663
|
+
updates.idle = false;
|
|
1444
1664
|
if (this.isAsync()) {
|
|
1445
1665
|
this.freeze();
|
|
1446
1666
|
this.updateViewsAsync(opt);
|
|
@@ -1454,12 +1674,25 @@ export const Paper = View.extend({
|
|
|
1454
1674
|
}
|
|
1455
1675
|
},
|
|
1456
1676
|
|
|
1677
|
+
wakeUp: function() {
|
|
1678
|
+
if (!this.isIdle()) return;
|
|
1679
|
+
this.unfreeze(this._updates.idle.wakeUpOptions);
|
|
1680
|
+
},
|
|
1681
|
+
|
|
1457
1682
|
isAsync: function() {
|
|
1458
1683
|
return !!this.options.async;
|
|
1459
1684
|
},
|
|
1460
1685
|
|
|
1461
1686
|
isFrozen: function() {
|
|
1462
|
-
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);
|
|
1463
1696
|
},
|
|
1464
1697
|
|
|
1465
1698
|
isExactSorting: function() {
|
|
@@ -1759,21 +1992,57 @@ export const Paper = View.extend({
|
|
|
1759
1992
|
return restrictedArea;
|
|
1760
1993
|
},
|
|
1761
1994
|
|
|
1762
|
-
|
|
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
|
+
},
|
|
1763
2002
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
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
|
+
},
|
|
1767
2017
|
|
|
1768
|
-
|
|
1769
|
-
|
|
2018
|
+
_registerCellView: function(cellView) {
|
|
2019
|
+
cellView.paper = this;
|
|
2020
|
+
this._views[cellView.model.id] = cellView;
|
|
2021
|
+
},
|
|
1770
2022
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
var type = cell.get('type') + 'View';
|
|
1775
|
-
var namespaceViewClass = getByPath(namespace, type, '.');
|
|
2023
|
+
_unregisterCellViewPlaceholder: function(placeholder) {
|
|
2024
|
+
delete this._viewPlaceholders[placeholder.cid];
|
|
2025
|
+
},
|
|
1776
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;
|
|
1777
2046
|
if (cell.isLink()) {
|
|
1778
2047
|
optionalViewClass = options.linkView;
|
|
1779
2048
|
defaultViewClass = LinkView;
|
|
@@ -1781,7 +2050,6 @@ export const Paper = View.extend({
|
|
|
1781
2050
|
optionalViewClass = options.elementView;
|
|
1782
2051
|
defaultViewClass = ElementView;
|
|
1783
2052
|
}
|
|
1784
|
-
|
|
1785
2053
|
// a) the paper options view is a class (deprecated)
|
|
1786
2054
|
// 1. search the namespace for a view
|
|
1787
2055
|
// 2. if no view was found, use view from the paper options
|
|
@@ -1789,29 +2057,54 @@ export const Paper = View.extend({
|
|
|
1789
2057
|
// 1. call the function from the paper options
|
|
1790
2058
|
// 2. if no view was return, search the namespace for a view
|
|
1791
2059
|
// 3. if no view was found, use the default
|
|
1792
|
-
|
|
2060
|
+
return (optionalViewClass.prototype instanceof ViewBase)
|
|
1793
2061
|
? namespaceViewClass || optionalViewClass
|
|
1794
2062
|
: optionalViewClass.call(this, cell) || namespaceViewClass || defaultViewClass;
|
|
2063
|
+
},
|
|
1795
2064
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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;
|
|
1801
2090
|
},
|
|
1802
2091
|
|
|
1803
|
-
|
|
2092
|
+
createViewForModel: function(cell, cid) {
|
|
2093
|
+
return this._initializeCellView(this._resolveCellViewClass(cell), cell, cid);
|
|
2094
|
+
},
|
|
1804
2095
|
|
|
2096
|
+
removeView: function(cell) {
|
|
1805
2097
|
const { id } = cell;
|
|
1806
2098
|
const { _views, _updates } = this;
|
|
1807
2099
|
const view = _views[id];
|
|
1808
2100
|
if (view) {
|
|
1809
2101
|
var { cid } = view;
|
|
1810
|
-
const {
|
|
2102
|
+
const { mountedList, unmountedList } = _updates;
|
|
1811
2103
|
view.remove();
|
|
1812
2104
|
delete _views[id];
|
|
1813
|
-
delete
|
|
1814
|
-
delete
|
|
2105
|
+
delete this._idToCid[id];
|
|
2106
|
+
mountedList.delete(cid);
|
|
2107
|
+
unmountedList.delete(cid);
|
|
1815
2108
|
}
|
|
1816
2109
|
return view;
|
|
1817
2110
|
},
|
|
@@ -1825,7 +2118,7 @@ export const Paper = View.extend({
|
|
|
1825
2118
|
if (id in views) {
|
|
1826
2119
|
view = views[id];
|
|
1827
2120
|
if (view.model === cell) {
|
|
1828
|
-
flag =
|
|
2121
|
+
flag = this.FLAG_INSERT;
|
|
1829
2122
|
create = false;
|
|
1830
2123
|
} else {
|
|
1831
2124
|
// The view for this `id` already exist.
|
|
@@ -1835,14 +2128,42 @@ export const Paper = View.extend({
|
|
|
1835
2128
|
}
|
|
1836
2129
|
}
|
|
1837
2130
|
if (create) {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
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
|
+
}
|
|
1841
2151
|
}
|
|
2152
|
+
|
|
1842
2153
|
this.requestViewUpdate(view, flag, view.UPDATE_PRIORITY, opt);
|
|
2154
|
+
|
|
1843
2155
|
return view;
|
|
1844
2156
|
},
|
|
1845
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
|
+
|
|
1846
2167
|
onImageDragStart: function() {
|
|
1847
2168
|
// This is the only way to prevent image dragging in Firefox that works.
|
|
1848
2169
|
// Setting -moz-user-select: none, draggable="false" attribute or user-drag: none didn't help.
|
|
@@ -1853,11 +2174,11 @@ export const Paper = View.extend({
|
|
|
1853
2174
|
resetViews: function(cells, opt) {
|
|
1854
2175
|
opt || (opt = {});
|
|
1855
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';
|
|
1856
2179
|
this._resetUpdates();
|
|
1857
2180
|
// clearing views removes any event listeners
|
|
1858
2181
|
this.removeViews();
|
|
1859
|
-
// Allows to unfreeze normally while in the idle state using autoFreeze option
|
|
1860
|
-
const key = this.options.autoFreeze ? null : 'reset';
|
|
1861
2182
|
this.freeze({ key });
|
|
1862
2183
|
for (var i = 0, n = cells.length; i < n; i++) {
|
|
1863
2184
|
this.renderView(cells[i], opt);
|
|
@@ -1867,10 +2188,16 @@ export const Paper = View.extend({
|
|
|
1867
2188
|
},
|
|
1868
2189
|
|
|
1869
2190
|
removeViews: function() {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
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
|
+
}
|
|
1873
2198
|
this._views = {};
|
|
2199
|
+
this._viewPlaceholders = {};
|
|
2200
|
+
this._idToCid = {};
|
|
1874
2201
|
},
|
|
1875
2202
|
|
|
1876
2203
|
sortViews: function() {
|
|
@@ -1879,7 +2206,7 @@ export const Paper = View.extend({
|
|
|
1879
2206
|
// noop
|
|
1880
2207
|
return;
|
|
1881
2208
|
}
|
|
1882
|
-
if (this.isFrozen()) {
|
|
2209
|
+
if (this.isFrozen() || this.isIdle()) {
|
|
1883
2210
|
// sort views once unfrozen
|
|
1884
2211
|
this._updates.sort = true;
|
|
1885
2212
|
return;
|
|
@@ -1922,9 +2249,58 @@ export const Paper = View.extend({
|
|
|
1922
2249
|
view.onMount(isInitialInsert);
|
|
1923
2250
|
},
|
|
1924
2251
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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();
|
|
1928
2304
|
},
|
|
1929
2305
|
|
|
1930
2306
|
// Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
|
|
@@ -1942,11 +2318,32 @@ export const Paper = View.extend({
|
|
|
1942
2318
|
},
|
|
1943
2319
|
|
|
1944
2320
|
// Find a view for a model `cell`. `cell` can also be a string or number representing a model `id`.
|
|
1945
|
-
findViewByModel: function(
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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;
|
|
1950
2347
|
},
|
|
1951
2348
|
|
|
1952
2349
|
// Find all views at given point
|
|
@@ -2025,6 +2422,92 @@ export const Paper = View.extend({
|
|
|
2025
2422
|
);
|
|
2026
2423
|
},
|
|
2027
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
|
+
|
|
2028
2511
|
_findInExtendedArea: function(area, findCellsFn, opt = {}) {
|
|
2029
2512
|
const {
|
|
2030
2513
|
buffer = this.DEFAULT_FIND_BUFFER,
|
|
@@ -2477,8 +2960,11 @@ export const Paper = View.extend({
|
|
|
2477
2960
|
|
|
2478
2961
|
var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
|
|
2479
2962
|
|
|
2480
|
-
|
|
2963
|
+
let view = data.sourceView;
|
|
2481
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);
|
|
2482
2968
|
view.pointermove(evt, localPoint.x, localPoint.y);
|
|
2483
2969
|
} else {
|
|
2484
2970
|
this.trigger('blank:pointermove', evt, localPoint.x, localPoint.y);
|
|
@@ -2495,8 +2981,11 @@ export const Paper = View.extend({
|
|
|
2495
2981
|
|
|
2496
2982
|
var localPoint = this.snapToGrid(normalizedEvt.clientX, normalizedEvt.clientY);
|
|
2497
2983
|
|
|
2498
|
-
|
|
2984
|
+
let view = this.eventData(evt).sourceView;
|
|
2499
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);
|
|
2500
2989
|
view.pointerup(normalizedEvt, localPoint.x, localPoint.y);
|
|
2501
2990
|
} else {
|
|
2502
2991
|
this.trigger('blank:pointerup', normalizedEvt, localPoint.x, localPoint.y);
|
|
@@ -3366,4 +3855,3 @@ export const Paper = View.extend({
|
|
|
3366
3855
|
}]
|
|
3367
3856
|
}
|
|
3368
3857
|
});
|
|
3369
|
-
|