@joint/core 4.2.0-alpha.0 → 4.2.0-beta.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 -3
- package/dist/joint.d.ts +595 -198
- package/dist/joint.js +3895 -1304
- package/dist/joint.min.js +3 -3
- package/dist/joint.nowrap.js +3895 -1304
- package/dist/joint.nowrap.min.js +3 -3
- package/dist/vectorizer.js +21 -8
- package/dist/vectorizer.min.js +3 -3
- package/dist/version.mjs +1 -1
- package/package.json +13 -13
- 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 +11 -1
- package/src/dia/Cell.mjs +96 -83
- package/src/dia/CellCollection.mjs +136 -0
- package/src/dia/CellView.mjs +6 -0
- package/src/dia/Element.mjs +6 -5
- package/src/dia/ElementView.mjs +2 -1
- package/src/dia/Graph.mjs +610 -317
- package/src/dia/GraphLayer.mjs +53 -0
- package/src/dia/GraphLayerCollection.mjs +313 -0
- package/src/dia/GraphLayerView.mjs +128 -0
- package/src/dia/GraphLayersController.mjs +166 -0
- package/src/dia/GraphTopologyIndex.mjs +222 -0
- package/src/dia/{layers/GridLayer.mjs → GridLayerView.mjs} +23 -16
- package/src/dia/HighlighterView.mjs +22 -0
- package/src/dia/{PaperLayer.mjs → LayerView.mjs} +52 -17
- package/src/dia/LegacyGraphLayerView.mjs +14 -0
- package/src/dia/LinkView.mjs +118 -98
- package/src/dia/Paper.mjs +1441 -620
- package/src/dia/ToolView.mjs +4 -0
- package/src/dia/ToolsView.mjs +14 -5
- package/src/dia/attributes/text.mjs +4 -2
- package/src/dia/index.mjs +6 -1
- package/src/dia/ports.mjs +213 -84
- package/src/dia/symbols.mjs +24 -0
- 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/Collection.mjs +19 -19
- package/src/mvc/Model.mjs +13 -10
- 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 +520 -137
- package/types/vectorizer.d.ts +11 -1
package/src/dia/Paper.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import V from '../V/index.mjs';
|
|
2
|
+
import * as g from '../g/index.mjs';
|
|
2
3
|
import {
|
|
3
4
|
isNumber,
|
|
4
5
|
assign,
|
|
@@ -12,7 +13,6 @@ import {
|
|
|
12
13
|
isFunction,
|
|
13
14
|
isPlainObject,
|
|
14
15
|
getByPath,
|
|
15
|
-
sortElements,
|
|
16
16
|
isString,
|
|
17
17
|
guid,
|
|
18
18
|
normalizeEvent,
|
|
@@ -23,31 +23,49 @@ import {
|
|
|
23
23
|
result,
|
|
24
24
|
camelCase,
|
|
25
25
|
cloneDeep,
|
|
26
|
+
clone,
|
|
26
27
|
invoke,
|
|
27
28
|
hashCode,
|
|
28
29
|
filter as _filter,
|
|
29
30
|
parseDOMJSON,
|
|
30
31
|
toArray,
|
|
31
|
-
has
|
|
32
|
+
has,
|
|
33
|
+
uniqueId,
|
|
32
34
|
} from '../util/index.mjs';
|
|
33
35
|
import { ViewBase } from '../mvc/ViewBase.mjs';
|
|
34
36
|
import { Rect, Point, toRad } from '../g/index.mjs';
|
|
35
|
-
import { View, views } from '../mvc/index.mjs';
|
|
37
|
+
import { View, views as viewsRegistry } from '../mvc/index.mjs';
|
|
36
38
|
import { CellView } from './CellView.mjs';
|
|
37
39
|
import { ElementView } from './ElementView.mjs';
|
|
38
40
|
import { LinkView } from './LinkView.mjs';
|
|
39
|
-
import { Cell } from './Cell.mjs';
|
|
40
41
|
import { Graph } from './Graph.mjs';
|
|
41
|
-
import {
|
|
42
|
+
import { LayerView } from './LayerView.mjs';
|
|
43
|
+
import { GraphLayerView } from './GraphLayerView.mjs';
|
|
44
|
+
import { LegacyGraphLayerView } from './LegacyGraphLayerView.mjs';
|
|
45
|
+
import { HighlighterView } from './HighlighterView.mjs';
|
|
46
|
+
import { Deque } from '../alg/Deque.mjs';
|
|
47
|
+
import {
|
|
48
|
+
CELL_MARKER, CELL_VIEW_MARKER, LAYER_VIEW_MARKER, GRAPH_LAYER_VIEW_MARKER
|
|
49
|
+
} from './symbols.mjs';
|
|
42
50
|
import * as highlighters from '../highlighters/index.mjs';
|
|
43
51
|
import * as linkAnchors from '../linkAnchors/index.mjs';
|
|
44
52
|
import * as connectionPoints from '../connectionPoints/index.mjs';
|
|
45
53
|
import * as anchors from '../anchors/index.mjs';
|
|
46
54
|
|
|
47
55
|
import $ from '../mvc/Dom/index.mjs';
|
|
48
|
-
import {
|
|
56
|
+
import { GridLayerView } from './GridLayerView.mjs';
|
|
57
|
+
|
|
58
|
+
const paperLayers = {
|
|
59
|
+
GRID: 'grid',
|
|
60
|
+
BACK: 'back',
|
|
61
|
+
/** @deprecated */
|
|
62
|
+
CELLS: 'cells',
|
|
63
|
+
FRONT: 'front',
|
|
64
|
+
TOOLS: 'tools',
|
|
65
|
+
LABELS: 'labels'
|
|
66
|
+
};
|
|
49
67
|
|
|
50
|
-
const sortingTypes = {
|
|
68
|
+
export const sortingTypes = {
|
|
51
69
|
NONE: 'sorting-none',
|
|
52
70
|
APPROX: 'sorting-approximate',
|
|
53
71
|
EXACT: 'sorting-exact'
|
|
@@ -82,22 +100,223 @@ const defaultHighlighting = {
|
|
|
82
100
|
}
|
|
83
101
|
};
|
|
84
102
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
const gridPatterns = {
|
|
104
|
+
|
|
105
|
+
dot: [{
|
|
106
|
+
color: '#AAAAAA',
|
|
107
|
+
thickness: 1,
|
|
108
|
+
markup: 'rect',
|
|
109
|
+
render: function(el, opt) {
|
|
110
|
+
V(el).attr({
|
|
111
|
+
width: opt.thickness,
|
|
112
|
+
height: opt.thickness,
|
|
113
|
+
fill: opt.color
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}],
|
|
117
|
+
|
|
118
|
+
fixedDot: [{
|
|
119
|
+
color: '#AAAAAA',
|
|
120
|
+
thickness: 1,
|
|
121
|
+
markup: 'rect',
|
|
122
|
+
render: function(el, opt) {
|
|
123
|
+
V(el).attr({ fill: opt.color });
|
|
124
|
+
},
|
|
125
|
+
update: function(el, opt, paper) {
|
|
126
|
+
const { sx, sy } = paper.scale();
|
|
127
|
+
const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
|
|
128
|
+
const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
|
|
129
|
+
V(el).attr({ width, height });
|
|
130
|
+
}
|
|
131
|
+
}],
|
|
132
|
+
|
|
133
|
+
mesh: [{
|
|
134
|
+
color: '#AAAAAA',
|
|
135
|
+
thickness: 1,
|
|
136
|
+
markup: 'path',
|
|
137
|
+
render: function(el, opt) {
|
|
138
|
+
|
|
139
|
+
var d;
|
|
140
|
+
var width = opt.width;
|
|
141
|
+
var height = opt.height;
|
|
142
|
+
var thickness = opt.thickness;
|
|
143
|
+
|
|
144
|
+
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
145
|
+
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
146
|
+
} else {
|
|
147
|
+
d = 'M 0 0 0 0';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
151
|
+
}
|
|
152
|
+
}],
|
|
153
|
+
|
|
154
|
+
doubleMesh: [{
|
|
155
|
+
color: '#AAAAAA',
|
|
156
|
+
thickness: 1,
|
|
157
|
+
markup: 'path',
|
|
158
|
+
render: function(el, opt) {
|
|
159
|
+
|
|
160
|
+
var d;
|
|
161
|
+
var width = opt.width;
|
|
162
|
+
var height = opt.height;
|
|
163
|
+
var thickness = opt.thickness;
|
|
164
|
+
|
|
165
|
+
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
166
|
+
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
167
|
+
} else {
|
|
168
|
+
d = 'M 0 0 0 0';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
172
|
+
}
|
|
173
|
+
}, {
|
|
174
|
+
color: '#000000',
|
|
175
|
+
thickness: 3,
|
|
176
|
+
scaleFactor: 4,
|
|
177
|
+
markup: 'path',
|
|
178
|
+
render: function(el, opt) {
|
|
179
|
+
|
|
180
|
+
var d;
|
|
181
|
+
var width = opt.width;
|
|
182
|
+
var height = opt.height;
|
|
183
|
+
var thickness = opt.thickness;
|
|
184
|
+
|
|
185
|
+
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
186
|
+
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
187
|
+
} else {
|
|
188
|
+
d = 'M 0 0 0 0';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
192
|
+
}
|
|
193
|
+
}]
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const backgroundPatterns = {
|
|
197
|
+
|
|
198
|
+
flipXy: function(img) {
|
|
199
|
+
// d b
|
|
200
|
+
// q p
|
|
201
|
+
|
|
202
|
+
var canvas = document.createElement('canvas');
|
|
203
|
+
var imgWidth = img.width;
|
|
204
|
+
var imgHeight = img.height;
|
|
205
|
+
|
|
206
|
+
canvas.width = 2 * imgWidth;
|
|
207
|
+
canvas.height = 2 * imgHeight;
|
|
208
|
+
|
|
209
|
+
var ctx = canvas.getContext('2d');
|
|
210
|
+
// top-left image
|
|
211
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
212
|
+
// xy-flipped bottom-right image
|
|
213
|
+
ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
|
|
214
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
215
|
+
// x-flipped top-right image
|
|
216
|
+
ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
|
|
217
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
218
|
+
// y-flipped bottom-left image
|
|
219
|
+
ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
|
|
220
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
221
|
+
|
|
222
|
+
return canvas;
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
flipX: function(img) {
|
|
226
|
+
// d b
|
|
227
|
+
// d b
|
|
228
|
+
|
|
229
|
+
var canvas = document.createElement('canvas');
|
|
230
|
+
var imgWidth = img.width;
|
|
231
|
+
var imgHeight = img.height;
|
|
232
|
+
|
|
233
|
+
canvas.width = imgWidth * 2;
|
|
234
|
+
canvas.height = imgHeight;
|
|
235
|
+
|
|
236
|
+
var ctx = canvas.getContext('2d');
|
|
237
|
+
// left image
|
|
238
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
239
|
+
// flipped right image
|
|
240
|
+
ctx.translate(2 * imgWidth, 0);
|
|
241
|
+
ctx.scale(-1, 1);
|
|
242
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
243
|
+
|
|
244
|
+
return canvas;
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
flipY: function(img) {
|
|
248
|
+
// d d
|
|
249
|
+
// q q
|
|
250
|
+
|
|
251
|
+
var canvas = document.createElement('canvas');
|
|
252
|
+
var imgWidth = img.width;
|
|
253
|
+
var imgHeight = img.height;
|
|
254
|
+
|
|
255
|
+
canvas.width = imgWidth;
|
|
256
|
+
canvas.height = imgHeight * 2;
|
|
257
|
+
|
|
258
|
+
var ctx = canvas.getContext('2d');
|
|
259
|
+
// top image
|
|
260
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
261
|
+
// flipped bottom image
|
|
262
|
+
ctx.translate(0, 2 * imgHeight);
|
|
263
|
+
ctx.scale(1, -1);
|
|
264
|
+
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
265
|
+
|
|
266
|
+
return canvas;
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
watermark: function(img, opt) {
|
|
270
|
+
// d
|
|
271
|
+
// d
|
|
272
|
+
|
|
273
|
+
opt = opt || {};
|
|
274
|
+
|
|
275
|
+
var imgWidth = img.width;
|
|
276
|
+
var imgHeight = img.height;
|
|
277
|
+
|
|
278
|
+
var canvas = document.createElement('canvas');
|
|
279
|
+
canvas.width = imgWidth * 3;
|
|
280
|
+
canvas.height = imgHeight * 3;
|
|
281
|
+
|
|
282
|
+
var ctx = canvas.getContext('2d');
|
|
283
|
+
var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
|
|
284
|
+
var radians = toRad(angle);
|
|
285
|
+
var stepX = canvas.width / 4;
|
|
286
|
+
var stepY = canvas.height / 4;
|
|
287
|
+
|
|
288
|
+
for (var i = 0; i < 4; i++) {
|
|
289
|
+
for (var j = 0; j < 4; j++) {
|
|
290
|
+
if ((i + j) % 2 > 0) {
|
|
291
|
+
// reset the current transformations
|
|
292
|
+
ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
|
|
293
|
+
ctx.rotate(radians);
|
|
294
|
+
ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return canvas;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const implicitLayers = [{
|
|
304
|
+
id: paperLayers.GRID,
|
|
305
|
+
type: 'GridLayerView',
|
|
306
|
+
patterns: gridPatterns
|
|
89
307
|
}, {
|
|
90
|
-
|
|
308
|
+
id: paperLayers.BACK,
|
|
91
309
|
}, {
|
|
92
|
-
|
|
310
|
+
id: paperLayers.LABELS,
|
|
93
311
|
}, {
|
|
94
|
-
|
|
312
|
+
id: paperLayers.FRONT
|
|
95
313
|
}, {
|
|
96
|
-
|
|
314
|
+
id: paperLayers.TOOLS
|
|
97
315
|
}];
|
|
98
316
|
|
|
99
|
-
|
|
317
|
+
const CELL_VIEW_PLACEHOLDER_MARKER = Symbol('joint.cellViewPlaceholderMarker');
|
|
100
318
|
|
|
319
|
+
export const Paper = View.extend({
|
|
101
320
|
className: 'paper',
|
|
102
321
|
|
|
103
322
|
options: {
|
|
@@ -173,7 +392,7 @@ export const Paper = View.extend({
|
|
|
173
392
|
// }
|
|
174
393
|
defaultLink: function() {
|
|
175
394
|
// Do not create hard dependency on the joint.shapes.standard namespace (by importing the standard.Link model directly)
|
|
176
|
-
const { cellNamespace } = this.model.
|
|
395
|
+
const { cellNamespace } = this.model.layerCollection;
|
|
177
396
|
const ctor = getByPath(cellNamespace, ['standard', 'Link']);
|
|
178
397
|
if (!ctor) throw new Error('dia.Paper: no default link model found. Use `options.defaultLink` to specify a default link model.');
|
|
179
398
|
return new ctor();
|
|
@@ -267,14 +486,28 @@ export const Paper = View.extend({
|
|
|
267
486
|
|
|
268
487
|
autoFreeze: false,
|
|
269
488
|
|
|
489
|
+
viewManagement: false,
|
|
490
|
+
|
|
270
491
|
// no docs yet
|
|
271
492
|
onViewUpdate: function(view, flag, priority, opt, paper) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
493
|
+
if (opt.mounting || opt.isolate) {
|
|
494
|
+
// Do not update connected links when:
|
|
495
|
+
// - the view was just mounted (added back to the paper by viewport function)
|
|
496
|
+
// - the change was marked as `isolate`.
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Always update connected links when the view model was replaced with another model
|
|
500
|
+
// with the same id.
|
|
501
|
+
// Note: the removal is done in 2 steps: remove the old model, add the new model.
|
|
502
|
+
// We update connected links on the add step.
|
|
503
|
+
if (!(opt.replace && opt.add)) {
|
|
504
|
+
if ((flag & (paper.FLAG_INSERT | paper.FLAG_REMOVE))) {
|
|
505
|
+
// Do not update connected links when:
|
|
506
|
+
// - the view was just inserted (added to the graph and rendered)
|
|
507
|
+
// - the view model was just removed from the graph
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
278
511
|
paper.requestConnectedLinksUpdate(view, priority, opt);
|
|
279
512
|
},
|
|
280
513
|
|
|
@@ -293,6 +526,8 @@ export const Paper = View.extend({
|
|
|
293
526
|
|
|
294
527
|
cellViewNamespace: null,
|
|
295
528
|
|
|
529
|
+
layerViewNamespace: null,
|
|
530
|
+
|
|
296
531
|
routerNamespace: null,
|
|
297
532
|
|
|
298
533
|
connectorNamespace: null,
|
|
@@ -305,7 +540,7 @@ export const Paper = View.extend({
|
|
|
305
540
|
|
|
306
541
|
connectionPointNamespace: connectionPoints,
|
|
307
542
|
|
|
308
|
-
overflow: false
|
|
543
|
+
overflow: false,
|
|
309
544
|
},
|
|
310
545
|
|
|
311
546
|
events: {
|
|
@@ -349,11 +584,13 @@ export const Paper = View.extend({
|
|
|
349
584
|
`,
|
|
350
585
|
|
|
351
586
|
svg: null,
|
|
352
|
-
viewport: null,
|
|
353
587
|
defs: null,
|
|
354
588
|
tools: null,
|
|
355
589
|
layers: null,
|
|
356
590
|
|
|
591
|
+
// deprecated, use layers element instead
|
|
592
|
+
viewport: null,
|
|
593
|
+
|
|
357
594
|
// For storing the current transformation matrix (CTM) of the paper's viewport.
|
|
358
595
|
_viewportMatrix: null,
|
|
359
596
|
// For verifying whether the CTM is up-to-date. The viewport transform attribute
|
|
@@ -364,7 +601,6 @@ export const Paper = View.extend({
|
|
|
364
601
|
// Paper Layers
|
|
365
602
|
_layers: null,
|
|
366
603
|
|
|
367
|
-
SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
|
|
368
604
|
UPDATE_DELAYING_BATCHES: ['translate'],
|
|
369
605
|
// If you interact with these elements,
|
|
370
606
|
// the default interaction such as `element move` is prevented.
|
|
@@ -387,8 +623,15 @@ export const Paper = View.extend({
|
|
|
387
623
|
// to mitigate the differences between the model and view geometry.
|
|
388
624
|
DEFAULT_FIND_BUFFER: 200,
|
|
389
625
|
|
|
390
|
-
|
|
391
|
-
|
|
626
|
+
FLAG_INSERT: 1<<30,
|
|
627
|
+
FLAG_REMOVE: 1<<29,
|
|
628
|
+
FLAG_INIT: 1<<28,
|
|
629
|
+
|
|
630
|
+
// Layers that are always present on the paper (e.g. grid, back, front, tools)
|
|
631
|
+
implicitLayers,
|
|
632
|
+
|
|
633
|
+
// Reference layer for inserting new graph layers.
|
|
634
|
+
graphLayerRefId: paperLayers.LABELS,
|
|
392
635
|
|
|
393
636
|
init: function() {
|
|
394
637
|
|
|
@@ -399,23 +642,36 @@ export const Paper = View.extend({
|
|
|
399
642
|
/* eslint-enable no-undef */
|
|
400
643
|
}
|
|
401
644
|
|
|
645
|
+
const defaultLayerViewNamespace = {
|
|
646
|
+
LayerView,
|
|
647
|
+
GraphLayerView,
|
|
648
|
+
GridLayerView,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
this.layerViewNamespace = defaultsDeep({}, options.layerViewNamespace || {}, defaultLayerViewNamespace);
|
|
652
|
+
|
|
402
653
|
const model = this.model = options.model || new Graph;
|
|
403
654
|
|
|
655
|
+
// This property tells us if we need to keep the compatibility
|
|
656
|
+
// with the v4 API and behavior.
|
|
657
|
+
this.legacyMode = !options.viewManagement;
|
|
658
|
+
|
|
404
659
|
// Layers (SVGGroups)
|
|
405
660
|
this._layers = {
|
|
406
661
|
viewsMap: {},
|
|
407
|
-
namesMap: {},
|
|
408
662
|
order: [],
|
|
409
663
|
};
|
|
410
664
|
|
|
665
|
+
// Hash of all cell views.
|
|
666
|
+
this._views = {};
|
|
667
|
+
this._viewPlaceholders = {};
|
|
668
|
+
this._idToCid = {};
|
|
669
|
+
|
|
411
670
|
this.cloneOptions();
|
|
412
671
|
this.render();
|
|
413
672
|
this._setDimensions();
|
|
414
673
|
this.startListening();
|
|
415
674
|
|
|
416
|
-
// Hash of all cell views.
|
|
417
|
-
this._views = {};
|
|
418
|
-
|
|
419
675
|
// Mouse wheel events buffer
|
|
420
676
|
this._mw_evt_buffer = {
|
|
421
677
|
event: null,
|
|
@@ -423,9 +679,7 @@ export const Paper = View.extend({
|
|
|
423
679
|
};
|
|
424
680
|
|
|
425
681
|
// Render existing cells in the graph
|
|
426
|
-
this.resetViews(model.
|
|
427
|
-
// Start the Rendering Loop
|
|
428
|
-
if (!this.isFrozen() && this.isAsync()) this.updateViewsAsync();
|
|
682
|
+
this.resetViews(model.getCells());
|
|
429
683
|
},
|
|
430
684
|
|
|
431
685
|
_resetUpdates: function() {
|
|
@@ -434,16 +688,15 @@ export const Paper = View.extend({
|
|
|
434
688
|
return this._updates = {
|
|
435
689
|
id: null,
|
|
436
690
|
priorities: [{}, {}, {}],
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
unmounted: {},
|
|
440
|
-
mounted: {},
|
|
691
|
+
unmountedList: new Deque(),
|
|
692
|
+
mountedList: new Deque(),
|
|
441
693
|
count: 0,
|
|
442
694
|
keyFrozen: false,
|
|
443
695
|
freezeKey: null,
|
|
444
696
|
sort: false,
|
|
445
697
|
disabled: false,
|
|
446
|
-
idle: false
|
|
698
|
+
idle: false,
|
|
699
|
+
freshAfterReset: true,
|
|
447
700
|
};
|
|
448
701
|
},
|
|
449
702
|
|
|
@@ -451,10 +704,13 @@ export const Paper = View.extend({
|
|
|
451
704
|
var model = this.model;
|
|
452
705
|
this.listenTo(model, 'add', this.onCellAdded)
|
|
453
706
|
.listenTo(model, 'remove', this.onCellRemoved)
|
|
454
|
-
.listenTo(model, 'change', this.onCellChange)
|
|
455
707
|
.listenTo(model, 'reset', this.onGraphReset)
|
|
456
|
-
.listenTo(model, 'sort', this.onGraphSort)
|
|
457
708
|
.listenTo(model, 'batch:stop', this.onGraphBatchStop);
|
|
709
|
+
|
|
710
|
+
this.listenTo(model, 'layer:add', this.onGraphLayerAdd)
|
|
711
|
+
.listenTo(model, 'layer:remove', this.onGraphLayerRemove)
|
|
712
|
+
.listenTo(model, 'layers:sort', this.onGraphLayerCollectionSort);
|
|
713
|
+
|
|
458
714
|
this.on('cell:highlight', this.onCellHighlight)
|
|
459
715
|
.on('cell:unhighlight', this.onCellUnhighlight)
|
|
460
716
|
.on('transform', this.update);
|
|
@@ -472,33 +728,28 @@ export const Paper = View.extend({
|
|
|
472
728
|
},
|
|
473
729
|
|
|
474
730
|
onCellRemoved: function(cell, _, opt) {
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
cell.hasChanged('layer') ||
|
|
483
|
-
(cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX)
|
|
484
|
-
) {
|
|
485
|
-
const view = this.findViewByModel(cell);
|
|
486
|
-
if (view) this.requestViewUpdate(view, view.FLAG_INSERT, view.UPDATE_PRIORITY, opt);
|
|
731
|
+
const viewLike = this._getCellViewLike(cell);
|
|
732
|
+
if (!viewLike) return;
|
|
733
|
+
if (viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
734
|
+
this._unregisterCellViewPlaceholder(viewLike);
|
|
735
|
+
} else {
|
|
736
|
+
this.requestViewUpdate(viewLike, this.FLAG_REMOVE, viewLike.UPDATE_PRIORITY, opt);
|
|
487
737
|
}
|
|
488
738
|
},
|
|
489
739
|
|
|
490
|
-
onGraphReset: function(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
this.
|
|
740
|
+
onGraphReset: function(_collection, opt) {
|
|
741
|
+
// Re-render all graph layer views
|
|
742
|
+
// but keep the implicit layer views.
|
|
743
|
+
this.renderGraphLayerViews();
|
|
744
|
+
this.resetLayerViews();
|
|
745
|
+
// Backward compatibility: reassign the `cells` property
|
|
746
|
+
// with the default layer view.
|
|
747
|
+
this.assertLayerViews();
|
|
748
|
+
this.resetViews(this.model.getCells(), opt);
|
|
498
749
|
},
|
|
499
750
|
|
|
500
751
|
onGraphBatchStop: function(data) {
|
|
501
|
-
if (this.isFrozen()) return;
|
|
752
|
+
if (this.isFrozen() || this.isIdle()) return;
|
|
502
753
|
var name = data && data.batchName;
|
|
503
754
|
var graph = this.model;
|
|
504
755
|
if (!this.isAsync()) {
|
|
@@ -507,10 +758,95 @@ export const Paper = View.extend({
|
|
|
507
758
|
this.updateViews(data);
|
|
508
759
|
}
|
|
509
760
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
761
|
+
},
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* @protected
|
|
765
|
+
* @description When a new layer is added to the graph, we create a new layer view
|
|
766
|
+
**/
|
|
767
|
+
onGraphLayerAdd: function(layer, _, opt) {
|
|
768
|
+
if (this.hasLayerView(layer.id)) return;
|
|
769
|
+
|
|
770
|
+
const layerView = this.createLayerView({
|
|
771
|
+
id: layer.id,
|
|
772
|
+
model: layer
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const layers = this.model.getLayers();
|
|
776
|
+
let before;
|
|
777
|
+
// Note: There is always at least one graph layer.
|
|
778
|
+
if (layers[layers.length - 1] === layer) {
|
|
779
|
+
// This is the last layer, so insert before the labels layer
|
|
780
|
+
before = paperLayers.LABELS;
|
|
781
|
+
} else {
|
|
782
|
+
// There is a layer after the current one, so insert before that one
|
|
783
|
+
const index = layers.indexOf(layer);
|
|
784
|
+
before = layers[index + 1].id;
|
|
513
785
|
}
|
|
786
|
+
|
|
787
|
+
this.addLayerView(layerView, { before });
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* @protected
|
|
792
|
+
* @description When a layer is removed from the graph, we remove the corresponding layer view
|
|
793
|
+
**/
|
|
794
|
+
onGraphLayerRemove: function(layer, _, opt) {
|
|
795
|
+
if (!this.hasLayerView(layer)) return;
|
|
796
|
+
|
|
797
|
+
// Request layer removal. Since the UPDATE_PRIORITY is lower
|
|
798
|
+
// than cells update priority, the cell views will be removed first.
|
|
799
|
+
this.requestLayerViewRemoval(layer);
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* @protected
|
|
804
|
+
* @description When the graph layer collection is sorted,
|
|
805
|
+
* we reorder all graph layer views.
|
|
806
|
+
**/
|
|
807
|
+
onGraphLayerCollectionSort: function(layerCollection) {
|
|
808
|
+
layerCollection.each(layer => {
|
|
809
|
+
if (!this.hasLayerView(layer)) return;
|
|
810
|
+
|
|
811
|
+
this.moveLayerView(layer, { before: this.graphLayerRefId });
|
|
812
|
+
});
|
|
813
|
+
},
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* @protected
|
|
817
|
+
* @description Resets all graph layer views.
|
|
818
|
+
*/
|
|
819
|
+
renderGraphLayerViews: function() {
|
|
820
|
+
// Remove all existing graph layer views
|
|
821
|
+
// Note: we don't use `getGraphLayerViews()` here because
|
|
822
|
+
// rendered graph layer views could be different from the ones
|
|
823
|
+
// in the graph layer collection (`onResetGraphLayerCollectionReset`).
|
|
824
|
+
this.getLayerViews().forEach(layerView => {
|
|
825
|
+
if (!layerView[GRAPH_LAYER_VIEW_MARKER]) return;
|
|
826
|
+
this._removeLayerView(layerView);
|
|
827
|
+
});
|
|
828
|
+
// Create and insert new graph layer views
|
|
829
|
+
this.model.getLayers().forEach(layer => {
|
|
830
|
+
const layerView = this.createLayerView({
|
|
831
|
+
id: layer.id,
|
|
832
|
+
model: layer
|
|
833
|
+
});
|
|
834
|
+
// Insert the layer view into the paper layers, just before the labels layer.
|
|
835
|
+
// All cell layers are positioned between the "back" and "labels" layers,
|
|
836
|
+
// with the default "cells" layer originally occupying this position.
|
|
837
|
+
this.addLayerView(layerView, { before: this.graphLayerRefId });
|
|
838
|
+
});
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* @protected
|
|
843
|
+
* @description Renders all implicit layer views.
|
|
844
|
+
*/
|
|
845
|
+
renderImplicitLayerViews: function() {
|
|
846
|
+
this.implicitLayers.forEach(layerInit => {
|
|
847
|
+
const layerView = this.createLayerView(layerInit);
|
|
848
|
+
this.addLayerView(layerView);
|
|
849
|
+
});
|
|
514
850
|
},
|
|
515
851
|
|
|
516
852
|
cloneOptions: function() {
|
|
@@ -558,6 +894,15 @@ export const Paper = View.extend({
|
|
|
558
894
|
// Return the default highlighting options into the user specified options.
|
|
559
895
|
options.highlighting = defaultsDeep({}, highlighting, defaultHighlighting);
|
|
560
896
|
}
|
|
897
|
+
// Copy and set defaults for the view management options.
|
|
898
|
+
options.viewManagement = defaults({}, options.viewManagement, {
|
|
899
|
+
// Whether to lazy initialize the cell views.
|
|
900
|
+
lazyInitialize: !!options.viewManagement, // default `true` if options.viewManagement provided
|
|
901
|
+
// Whether to add initialized cell views into the unmounted queue.
|
|
902
|
+
initializeUnmounted: false,
|
|
903
|
+
// Whether to dispose the cell views that are not visible.
|
|
904
|
+
disposeHidden: false,
|
|
905
|
+
});
|
|
561
906
|
},
|
|
562
907
|
|
|
563
908
|
children: function() {
|
|
@@ -597,121 +942,301 @@ export const Paper = View.extend({
|
|
|
597
942
|
}];
|
|
598
943
|
},
|
|
599
944
|
|
|
600
|
-
|
|
601
|
-
|
|
945
|
+
/**
|
|
946
|
+
* @public
|
|
947
|
+
* @description Checks whether the layer view exists by the given layer id or layer model.
|
|
948
|
+
* @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
|
|
949
|
+
* @return {boolean} True if the layer view exists, false otherwise.
|
|
950
|
+
*/
|
|
951
|
+
hasLayerView(layerRef) {
|
|
952
|
+
let layerId;
|
|
953
|
+
if (isString(layerRef)) {
|
|
954
|
+
layerId = layerRef;
|
|
955
|
+
} else if (layerRef) {
|
|
956
|
+
layerId = layerRef.id;
|
|
957
|
+
} else {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
return (layerId in this._layers.viewsMap);
|
|
602
961
|
},
|
|
603
962
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
963
|
+
/**
|
|
964
|
+
* @public
|
|
965
|
+
* @description Returns the layer view by the given layer id or layer model.
|
|
966
|
+
* @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
|
|
967
|
+
* @return {dia.LayerView} The layer view.
|
|
968
|
+
* @throws {Error} if the layer view is not found
|
|
969
|
+
*/
|
|
970
|
+
getLayerView(layerRef) {
|
|
609
971
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
972
|
+
let layerId;
|
|
973
|
+
if (isString(layerRef)) {
|
|
974
|
+
layerId = layerRef;
|
|
975
|
+
} else if (layerRef) {
|
|
976
|
+
layerId = layerRef.id;
|
|
977
|
+
} else {
|
|
978
|
+
throw new Error('dia.Paper: No layer provided.');
|
|
979
|
+
}
|
|
613
980
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
981
|
+
const layerView = this._layers.viewsMap[layerId];
|
|
982
|
+
if (!layerView) {
|
|
983
|
+
throw new Error(`dia.Paper: Unknown layer view "${layerId}".`);
|
|
984
|
+
}
|
|
618
985
|
|
|
619
|
-
|
|
620
|
-
const { _layers: { viewsMap, namesMap, order }} = this;
|
|
621
|
-
const layerName = this._getLayerName(layerView);
|
|
622
|
-
order.splice(order.indexOf(layerName), 1);
|
|
623
|
-
delete namesMap[layerView.cid];
|
|
624
|
-
delete viewsMap[layerName];
|
|
986
|
+
return layerView;
|
|
625
987
|
},
|
|
626
988
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
} else {
|
|
633
|
-
order.push(layerName);
|
|
634
|
-
}
|
|
635
|
-
viewsMap[layerName] = layerView;
|
|
636
|
-
namesMap[layerView.cid] = layerName;
|
|
989
|
+
/**
|
|
990
|
+
* @deprecated use `getLayerView(layerId).el` instead
|
|
991
|
+
*/
|
|
992
|
+
getLayerNode(layerId) {
|
|
993
|
+
return this.getLayerView(layerId).el;
|
|
637
994
|
},
|
|
638
995
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
996
|
+
/**
|
|
997
|
+
* @protected
|
|
998
|
+
* @description Removes the given layer view from the paper.
|
|
999
|
+
* It does not check whether the layer view is empty.
|
|
1000
|
+
* @param {dia.LayerView} layerView - The layer view to remove.
|
|
1001
|
+
*/
|
|
1002
|
+
_removeLayerView(layerView) {
|
|
1003
|
+
this._unregisterLayerView(layerView);
|
|
1004
|
+
layerView.remove();
|
|
647
1005
|
},
|
|
648
1006
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* @protected
|
|
1010
|
+
* @description Removes all layer views from the paper.
|
|
1011
|
+
* It does not check whether the layer views are empty.
|
|
1012
|
+
*/
|
|
1013
|
+
_removeLayerViews: function() {
|
|
1014
|
+
Object.values(this._layers.viewsMap).forEach(layerView => {
|
|
1015
|
+
this._removeLayerView(layerView);
|
|
1016
|
+
});
|
|
652
1017
|
},
|
|
653
1018
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1019
|
+
/**
|
|
1020
|
+
* @protected
|
|
1021
|
+
* @description Unregisters the given layer view from the paper.
|
|
1022
|
+
* @param {dia.LayerView} layerView - The layer view to unregister.
|
|
1023
|
+
*/
|
|
1024
|
+
_unregisterLayerView(layerView) {
|
|
1025
|
+
const { _layers: { viewsMap, order }} = this;
|
|
1026
|
+
const layerId = layerView.id;
|
|
1027
|
+
// Remove the layer id from the order list.
|
|
1028
|
+
const layerIndex = order.indexOf(layerId);
|
|
1029
|
+
if (layerIndex !== -1) {
|
|
1030
|
+
order.splice(layerIndex, 1);
|
|
1031
|
+
}
|
|
1032
|
+
// Unlink the layer view from the paper.
|
|
1033
|
+
layerView.unsetPaperReference();
|
|
1034
|
+
// Remove the layer view from the paper's registry.
|
|
1035
|
+
delete viewsMap[layerId];
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* @protected
|
|
1040
|
+
* @description Registers the given layer view in the paper.
|
|
1041
|
+
* @param {dia.LayerView} layerView - The layer view to register.
|
|
1042
|
+
* @throws {Error} if the layer view is not an instance of dia.LayerView
|
|
1043
|
+
* @throws {Error} if the layer view already exists in the paper
|
|
1044
|
+
*/
|
|
1045
|
+
_registerLayerView(layerView) {
|
|
1046
|
+
if (!layerView || !layerView[LAYER_VIEW_MARKER]) {
|
|
1047
|
+
throw new Error('dia.Paper: The layer view must be an instance of dia.LayerView.');
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (this.hasLayerView(layerView.id)) {
|
|
1051
|
+
throw new Error(`dia.Paper: The layer view "${layerView.id}" already exists.`);
|
|
1052
|
+
}
|
|
1053
|
+
// Link the layer view back to the paper.
|
|
1054
|
+
layerView.setPaperReference(this);
|
|
1055
|
+
// Store the layer view in the paper's registry.
|
|
1056
|
+
this._layers.viewsMap[layerView.id] = layerView;
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* @public
|
|
1061
|
+
* @description Removes the layer view by the given layer id or layer model.
|
|
1062
|
+
* @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
|
|
1063
|
+
* @throws {Error} if the layer view is not empty
|
|
1064
|
+
*/
|
|
1065
|
+
removeLayerView(layerRef) {
|
|
1066
|
+
const layerView = this.getLayerView(layerRef);
|
|
1067
|
+
if (!layerView.isEmpty()) {
|
|
1068
|
+
throw new Error('dia.Paper: The layer view is not empty.');
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
this._removeLayerView(layerView);
|
|
1072
|
+
},
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* @protected
|
|
1076
|
+
* @description Schedules the layer view removal by the given layer id or layer model.
|
|
1077
|
+
* The actual removal will be performed during the paper update cycle.
|
|
1078
|
+
* @param {string|dia.GraphLayer} layerRef - Layer id or layer model.
|
|
1079
|
+
* @param {Object} [opt] - Update options.
|
|
1080
|
+
*/
|
|
1081
|
+
requestLayerViewRemoval(layerRef, opt) {
|
|
1082
|
+
const layerView = this.getLayerView(layerRef);
|
|
1083
|
+
const { FLAG_REMOVE } = this;
|
|
1084
|
+
const { UPDATE_PRIORITY } = layerView;
|
|
1085
|
+
|
|
1086
|
+
this.requestViewUpdate(layerView, FLAG_REMOVE, UPDATE_PRIORITY, opt);
|
|
1087
|
+
},
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* @public
|
|
1091
|
+
* @internal not documented
|
|
1092
|
+
* @description Schedules the cell view insertion into the appropriate layer view.
|
|
1093
|
+
* The actual insertion will be performed during the paper update cycle.
|
|
1094
|
+
* @param {dia.Cell} cell - The cell model whose view should be inserted.
|
|
1095
|
+
* @param {Object} [opt] - Update options.
|
|
1096
|
+
*/
|
|
1097
|
+
requestCellViewInsertion(cell, opt) {
|
|
1098
|
+
const viewLike = this._getCellViewLike(cell);
|
|
1099
|
+
if (!viewLike) return;
|
|
1100
|
+
this.requestViewUpdate(viewLike, this.FLAG_INSERT, viewLike.UPDATE_PRIORITY, opt);
|
|
1101
|
+
},
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* @private
|
|
1105
|
+
* Helper method for addLayerView and moveLayerView methods
|
|
1106
|
+
*/
|
|
1107
|
+
_getBeforeLayerViewFromOptions(layerView, options) {
|
|
1108
|
+
let { before = null, index } = options;
|
|
1109
|
+
|
|
1110
|
+
if (before && index !== undefined) {
|
|
1111
|
+
throw new Error('dia.Paper: Options "before" and "index" are mutually exclusive.');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
let computedBefore;
|
|
1115
|
+
if (index !== undefined) {
|
|
1116
|
+
const { _layers: { order }} = this;
|
|
1117
|
+
if (index >= order.length) {
|
|
1118
|
+
// If index is greater than the number of layers,
|
|
1119
|
+
// return before as null (move to the end).
|
|
1120
|
+
computedBefore = null;
|
|
1121
|
+
} else if (index < 0) {
|
|
1122
|
+
// If index is negative, move to the beginning.
|
|
1123
|
+
computedBefore = order[0];
|
|
659
1124
|
} else {
|
|
660
|
-
|
|
1125
|
+
const originalIndex = order.indexOf(layerView.id);
|
|
1126
|
+
if (originalIndex !== -1 && index > originalIndex) {
|
|
1127
|
+
// If moving a layer upwards in the stack, we need to adjust the index
|
|
1128
|
+
// to account for the layer being removed from its original position.
|
|
1129
|
+
index += 1;
|
|
1130
|
+
}
|
|
1131
|
+
// Otherwise, get the layer ID at the specified index.
|
|
1132
|
+
computedBefore = order[index] || null;
|
|
661
1133
|
}
|
|
1134
|
+
} else {
|
|
1135
|
+
computedBefore = before;
|
|
662
1136
|
}
|
|
663
|
-
|
|
1137
|
+
|
|
1138
|
+
return computedBefore ? this.getLayerView(computedBefore) : null;
|
|
664
1139
|
},
|
|
665
1140
|
|
|
666
|
-
|
|
667
|
-
|
|
1141
|
+
/**
|
|
1142
|
+
* @public
|
|
1143
|
+
* @description Adds the layer view to the paper.
|
|
1144
|
+
* @param {dia.LayerView} layerView - The layer view to add.
|
|
1145
|
+
* @param {Object} [options] - Adding options.
|
|
1146
|
+
* @param {string|dia.GraphLayer} [options.before] - Layer id or layer model before
|
|
1147
|
+
*/
|
|
1148
|
+
addLayerView(layerView, options = {}) {
|
|
1149
|
+
this._registerLayerView(layerView);
|
|
1150
|
+
|
|
1151
|
+
const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
|
|
1152
|
+
this.insertLayerView(layerView, beforeLayerView);
|
|
668
1153
|
},
|
|
669
1154
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1155
|
+
/**
|
|
1156
|
+
* @public
|
|
1157
|
+
* @description Moves the layer view.
|
|
1158
|
+
* @param {Paper.LayerRef} layerRef - The layer view reference to move.
|
|
1159
|
+
* @param {Object} [options] - Moving options.
|
|
1160
|
+
* @param {Paper.LayerRef} [options.before] - Layer id or layer model before
|
|
1161
|
+
* @param {number} [options.index] - Zero-based index to which to move the layer view.
|
|
1162
|
+
*/
|
|
1163
|
+
moveLayerView(layerRef, options = {}) {
|
|
1164
|
+
const layerView = this.getLayerView(layerRef);
|
|
1165
|
+
|
|
1166
|
+
const beforeLayerView = this._getBeforeLayerViewFromOptions(layerView, options);
|
|
1167
|
+
this.insertLayerView(layerView, beforeLayerView);
|
|
676
1168
|
},
|
|
677
1169
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1170
|
+
/**
|
|
1171
|
+
* @protected
|
|
1172
|
+
* @description Inserts the layer view into the paper.
|
|
1173
|
+
* If the layer view already exists in the paper, it is moved to the new position.
|
|
1174
|
+
* @param {dia.LayerView} layerView - The layer view to insert.
|
|
1175
|
+
* @param {dia.LayerView} [before] - Layer view before
|
|
1176
|
+
* which the layer view should be inserted.
|
|
1177
|
+
*/
|
|
1178
|
+
insertLayerView(layerView, beforeLayerView) {
|
|
1179
|
+
const layerId = layerView.id;
|
|
1180
|
+
|
|
1181
|
+
const { _layers: { order }} = this;
|
|
1182
|
+
const currentLayerIndex = order.indexOf(layerId);
|
|
1183
|
+
|
|
1184
|
+
// Should the layer view be inserted before another layer view?
|
|
1185
|
+
if (beforeLayerView) {
|
|
1186
|
+
const beforeLayerViewId = beforeLayerView.id;
|
|
1187
|
+
if (layerId === beforeLayerViewId) {
|
|
1188
|
+
// The layer view is already in the right place.
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
let beforeLayerPosition = order.indexOf(beforeLayerViewId);
|
|
1193
|
+
// Remove from the `order` list if the layer view is already in the order.
|
|
1194
|
+
if (currentLayerIndex !== -1) {
|
|
1195
|
+
if (currentLayerIndex < beforeLayerPosition) {
|
|
1196
|
+
beforeLayerPosition -= 1;
|
|
1197
|
+
}
|
|
1198
|
+
order.splice(currentLayerIndex, 1);
|
|
1199
|
+
}
|
|
1200
|
+
order.splice(beforeLayerPosition, 0, layerId);
|
|
695
1201
|
this.layers.insertBefore(layerView.el, beforeLayerView.el);
|
|
1202
|
+
return;
|
|
696
1203
|
}
|
|
697
|
-
},
|
|
698
1204
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1205
|
+
// Remove from the `order` list if the layer view is already in the order.
|
|
1206
|
+
// This is needed for the case when the layer view is inserted in the new position.
|
|
1207
|
+
if (currentLayerIndex !== -1) {
|
|
1208
|
+
order.splice(currentLayerIndex, 1);
|
|
1209
|
+
}
|
|
1210
|
+
order.push(layerId);
|
|
1211
|
+
this.layers.appendChild(layerView.el);
|
|
705
1212
|
},
|
|
706
1213
|
|
|
707
|
-
|
|
708
|
-
|
|
1214
|
+
/**
|
|
1215
|
+
* @protected
|
|
1216
|
+
* @description Returns an array of layer view ids in the order they are rendered.
|
|
1217
|
+
* @returns {string[]} An array of layer view ids.
|
|
1218
|
+
*/
|
|
1219
|
+
getLayerViewOrder() {
|
|
709
1220
|
return this._layers.order.slice();
|
|
710
1221
|
},
|
|
711
1222
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1223
|
+
/**
|
|
1224
|
+
* @public
|
|
1225
|
+
* @description Returns an array of layer views in the order they are rendered.
|
|
1226
|
+
* @returns {dia.LayerView[]} An array of layer views.
|
|
1227
|
+
*/
|
|
1228
|
+
getLayerViews() {
|
|
1229
|
+
return this.getLayerViewOrder().map(id => this.getLayerView(id));
|
|
1230
|
+
},
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* @public
|
|
1234
|
+
* @description Returns an array of graph layer views in the order they are rendered.
|
|
1235
|
+
* @returns {dia.GraphLayerView[]} An array of graph layer views.
|
|
1236
|
+
*/
|
|
1237
|
+
getGraphLayerViews() {
|
|
1238
|
+
const { _layers: { viewsMap }} = this;
|
|
1239
|
+
return this.model.getLayers().map(layer => viewsMap[layer.id]);
|
|
715
1240
|
},
|
|
716
1241
|
|
|
717
1242
|
render: function() {
|
|
@@ -727,7 +1252,7 @@ export const Paper = View.extend({
|
|
|
727
1252
|
this.defs = defs;
|
|
728
1253
|
this.layers = layers;
|
|
729
1254
|
|
|
730
|
-
this.
|
|
1255
|
+
this.renderLayerViews();
|
|
731
1256
|
|
|
732
1257
|
V.ensureId(svg);
|
|
733
1258
|
|
|
@@ -749,48 +1274,86 @@ export const Paper = View.extend({
|
|
|
749
1274
|
V(this.svg).prepend(V.createSVGStyle(css));
|
|
750
1275
|
},
|
|
751
1276
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* @protected
|
|
1279
|
+
* @description Creates a layer view instance based on the provided options.
|
|
1280
|
+
* It finds the appropriate layer view constructor from the paper's
|
|
1281
|
+
* `layerViewNamespace` and instantiates it.
|
|
1282
|
+
* @param {*} options See `dia.LayerView` options.
|
|
1283
|
+
* @returns {dia.LayerView}
|
|
1284
|
+
*/
|
|
1285
|
+
createLayerView(options) {
|
|
1286
|
+
if (options == null) {
|
|
1287
|
+
throw new Error('dia.Paper: Layer view options are required.');
|
|
758
1288
|
}
|
|
759
|
-
},
|
|
760
1289
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1290
|
+
if (options.id == null) {
|
|
1291
|
+
throw new Error('dia.Paper: Layer view id is required.');
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const viewOptions = clone(options);
|
|
1295
|
+
|
|
1296
|
+
let viewConstructor;
|
|
1297
|
+
if (viewOptions.model) {
|
|
1298
|
+
const modelType = viewOptions.model.get('type') || viewOptions.model.constructor.name;
|
|
1299
|
+
const type = modelType + 'View';
|
|
1300
|
+
|
|
1301
|
+
// For backward compatibility we use the LegacyGraphLayerView for the default `cells` layer.
|
|
1302
|
+
if (this.model.layersController.legacyMode) {
|
|
1303
|
+
viewConstructor = LegacyGraphLayerView;
|
|
1304
|
+
} else {
|
|
1305
|
+
viewConstructor = this.layerViewNamespace[type] || LayerView;
|
|
1306
|
+
}
|
|
1307
|
+
} else {
|
|
1308
|
+
// Paper layers
|
|
1309
|
+
const type = viewOptions.type;
|
|
1310
|
+
viewConstructor = this.layerViewNamespace[type] || LayerView;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
return new viewConstructor(viewOptions);
|
|
1314
|
+
},
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* @protected
|
|
1318
|
+
* @description Renders all paper layer views and graph layer views.
|
|
1319
|
+
*/
|
|
1320
|
+
renderLayerViews: function() {
|
|
1321
|
+
this._removeLayerViews();
|
|
1322
|
+
// Render the paper layers.
|
|
1323
|
+
this.renderImplicitLayerViews();
|
|
1324
|
+
// Render the layers.
|
|
1325
|
+
this.renderGraphLayerViews();
|
|
1326
|
+
// Ensure that essential layer views are present.
|
|
1327
|
+
this.assertLayerViews();
|
|
1328
|
+
},
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* @protected
|
|
1332
|
+
* @description Ensures that essential layer views are present on the paper.
|
|
1333
|
+
* @throws {Error} if any of the essential layer views is missing
|
|
1334
|
+
*/
|
|
1335
|
+
assertLayerViews: function() {
|
|
1336
|
+
// Throws an exception if essential layer views are missing.
|
|
1337
|
+
const cellsLayerView = this.getLayerView(this.model.getDefaultLayer().id);
|
|
1338
|
+
const toolsLayerView = this.getLayerView(paperLayers.TOOLS);
|
|
1339
|
+
const labelsLayerView = this.getLayerView(paperLayers.LABELS);
|
|
766
1340
|
|
|
767
|
-
renderLayers: function(layers = defaultLayers) {
|
|
768
|
-
this.removeLayers();
|
|
769
|
-
layers.forEach(({ name }) => this.renderLayer(name));
|
|
770
|
-
// Throws an exception if doesn't exist
|
|
771
|
-
const cellsLayerView = this.getLayerView(LayersNames.CELLS);
|
|
772
|
-
const toolsLayerView = this.getLayerView(LayersNames.TOOLS);
|
|
773
|
-
const labelsLayerView = this.getLayerView(LayersNames.LABELS);
|
|
774
1341
|
// backwards compatibility
|
|
775
1342
|
this.tools = toolsLayerView.el;
|
|
776
1343
|
this.cells = this.viewport = cellsLayerView.el;
|
|
777
|
-
//
|
|
778
|
-
|
|
1344
|
+
// Backwards compatibility: same as `LegacyGraphLayerView` we keep
|
|
1345
|
+
// the `viewport` class on the labels layer.
|
|
779
1346
|
labelsLayerView.vel.addClass(addClassNamePrefix('viewport'));
|
|
780
|
-
cellsLayerView.el.style.webkitUserSelect = 'none';
|
|
781
|
-
cellsLayerView.el.style.userSelect = 'none';
|
|
782
1347
|
labelsLayerView.el.style.webkitUserSelect = 'none';
|
|
783
1348
|
labelsLayerView.el.style.userSelect = 'none';
|
|
784
1349
|
},
|
|
785
1350
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const { _layers: { viewsMap }} = this;
|
|
793
|
-
Object.values(viewsMap).forEach(layerView => layerView.removePivots());
|
|
1351
|
+
/**
|
|
1352
|
+
* @protected
|
|
1353
|
+
* @description Resets all layer views.
|
|
1354
|
+
*/
|
|
1355
|
+
resetLayerViews: function() {
|
|
1356
|
+
this.getLayerViews().forEach(layerView => layerView.reset());
|
|
794
1357
|
},
|
|
795
1358
|
|
|
796
1359
|
update: function() {
|
|
@@ -916,56 +1479,54 @@ export const Paper = View.extend({
|
|
|
916
1479
|
|
|
917
1480
|
clientMatrix: function() {
|
|
918
1481
|
|
|
919
|
-
return V.createSVGMatrix(this.
|
|
1482
|
+
return V.createSVGMatrix(this.layers.getScreenCTM());
|
|
920
1483
|
},
|
|
921
1484
|
|
|
922
1485
|
requestConnectedLinksUpdate: function(view, priority, opt) {
|
|
923
|
-
if (view
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
1486
|
+
if (!view || !view[CELL_VIEW_MARKER]) return;
|
|
1487
|
+
var model = view.model;
|
|
1488
|
+
var links = this.model.getConnectedLinks(model);
|
|
1489
|
+
for (var j = 0, n = links.length; j < n; j++) {
|
|
1490
|
+
var link = links[j];
|
|
1491
|
+
var linkView = this._getCellViewLike(link);
|
|
1492
|
+
if (!linkView) continue;
|
|
1493
|
+
// We do not have to update placeholder views.
|
|
1494
|
+
// They will be updated on initial render.
|
|
1495
|
+
if (linkView[CELL_VIEW_PLACEHOLDER_MARKER]) continue;
|
|
1496
|
+
var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
|
|
1497
|
+
this.scheduleViewUpdate(linkView, linkView.getFlag(LinkView.Flags.UPDATE), nextPriority, opt);
|
|
936
1498
|
}
|
|
937
1499
|
},
|
|
938
1500
|
|
|
939
1501
|
forcePostponedViewUpdate: function(view, flag) {
|
|
940
|
-
if (!view || !
|
|
941
|
-
|
|
1502
|
+
if (!view || !view[CELL_VIEW_MARKER]) return false;
|
|
1503
|
+
const model = view.model;
|
|
942
1504
|
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
|
-
}
|
|
1505
|
+
const dumpOptions = { silent: true };
|
|
1506
|
+
// LinkView is waiting for the target or the source cellView to be rendered
|
|
1507
|
+
// This can happen when the cells are not in the viewport.
|
|
1508
|
+
let sourceFlag = 0;
|
|
1509
|
+
const sourceCell = model.getSourceCell();
|
|
1510
|
+
if (sourceCell && !this.isCellVisible(sourceCell)) {
|
|
1511
|
+
const sourceView = this.findViewByModel(sourceCell);
|
|
1512
|
+
sourceFlag = this.dumpView(sourceView, dumpOptions);
|
|
1513
|
+
}
|
|
1514
|
+
let targetFlag = 0;
|
|
1515
|
+
const targetCell = model.getTargetCell();
|
|
1516
|
+
if (targetCell && !this.isCellVisible(targetCell)) {
|
|
1517
|
+
const targetView = this.findViewByModel(targetCell);
|
|
1518
|
+
targetFlag = this.dumpView(targetView, dumpOptions);
|
|
1519
|
+
}
|
|
1520
|
+
if (sourceFlag === 0 && targetFlag === 0) {
|
|
1521
|
+
// If leftover flag is 0, all view updates were done.
|
|
1522
|
+
return !this.dumpView(view, dumpOptions);
|
|
963
1523
|
}
|
|
964
1524
|
return false;
|
|
965
1525
|
},
|
|
966
1526
|
|
|
967
1527
|
requestViewUpdate: function(view, flag, priority, opt) {
|
|
968
1528
|
opt || (opt = {});
|
|
1529
|
+
// Note: `scheduleViewUpdate` wakes up the paper if it is idle.
|
|
969
1530
|
this.scheduleViewUpdate(view, flag, priority, opt);
|
|
970
1531
|
var isAsync = this.isAsync();
|
|
971
1532
|
if (this.isFrozen() || (isAsync && opt.async !== false)) return;
|
|
@@ -976,13 +1537,14 @@ export const Paper = View.extend({
|
|
|
976
1537
|
|
|
977
1538
|
scheduleViewUpdate: function(view, type, priority, opt) {
|
|
978
1539
|
const { _updates: updates, options } = this;
|
|
979
|
-
if (updates.idle) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
this.
|
|
983
|
-
}
|
|
1540
|
+
if (updates.idle && options.autoFreeze) {
|
|
1541
|
+
this.legacyMode
|
|
1542
|
+
? this.unfreeze() // Restart rendering loop without original options
|
|
1543
|
+
: this.wakeUp();
|
|
984
1544
|
}
|
|
985
|
-
const { FLAG_REMOVE, FLAG_INSERT
|
|
1545
|
+
const { FLAG_REMOVE, FLAG_INSERT } = this;
|
|
1546
|
+
const { UPDATE_PRIORITY, cid } = view;
|
|
1547
|
+
|
|
986
1548
|
let priorityUpdates = updates.priorities[priority];
|
|
987
1549
|
if (!priorityUpdates) priorityUpdates = updates.priorities[priority] = {};
|
|
988
1550
|
// Move higher priority updates to this priority
|
|
@@ -1028,20 +1590,24 @@ export const Paper = View.extend({
|
|
|
1028
1590
|
dumpView: function(view, opt = {}) {
|
|
1029
1591
|
const flag = this.dumpViewUpdate(view);
|
|
1030
1592
|
if (!flag) return 0;
|
|
1031
|
-
|
|
1032
|
-
if (shouldNotify) this.notifyBeforeRender(opt);
|
|
1593
|
+
this.notifyBeforeRender(opt);
|
|
1033
1594
|
const leftover = this.updateView(view, flag, opt);
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
this.notifyAfterRender(stats, opt);
|
|
1037
|
-
}
|
|
1595
|
+
const stats = { updated: 1, priority: view.UPDATE_PRIORITY };
|
|
1596
|
+
this.notifyAfterRender(stats, opt);
|
|
1038
1597
|
return leftover;
|
|
1039
1598
|
},
|
|
1040
1599
|
|
|
1041
1600
|
updateView: function(view, flag, opt) {
|
|
1042
1601
|
if (!view) return 0;
|
|
1043
|
-
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT
|
|
1044
|
-
|
|
1602
|
+
const { FLAG_REMOVE, FLAG_INSERT, FLAG_INIT } = this;
|
|
1603
|
+
const { model } = view;
|
|
1604
|
+
if (view[GRAPH_LAYER_VIEW_MARKER]) {
|
|
1605
|
+
if (flag & FLAG_REMOVE) {
|
|
1606
|
+
this.removeLayerView(view);
|
|
1607
|
+
return 0;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (view[CELL_VIEW_MARKER]) {
|
|
1045
1611
|
if (flag & FLAG_REMOVE) {
|
|
1046
1612
|
this.removeView(model);
|
|
1047
1613
|
return 0;
|
|
@@ -1069,57 +1635,70 @@ export const Paper = View.extend({
|
|
|
1069
1635
|
registerUnmountedView: function(view) {
|
|
1070
1636
|
var cid = view.cid;
|
|
1071
1637
|
var updates = this._updates;
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
updates.
|
|
1075
|
-
|
|
1638
|
+
if (updates.unmountedList.has(cid)) return 0;
|
|
1639
|
+
const flag = this.FLAG_INSERT;
|
|
1640
|
+
updates.unmountedList.pushTail(cid, flag);
|
|
1641
|
+
updates.mountedList.delete(cid);
|
|
1076
1642
|
return flag;
|
|
1077
1643
|
},
|
|
1078
1644
|
|
|
1079
1645
|
registerMountedView: function(view) {
|
|
1080
1646
|
var cid = view.cid;
|
|
1081
1647
|
var updates = this._updates;
|
|
1082
|
-
if (
|
|
1083
|
-
updates.
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1648
|
+
if (updates.mountedList.has(cid)) return 0;
|
|
1649
|
+
const unmountedItem = updates.unmountedList.get(cid);
|
|
1650
|
+
const flag = unmountedItem ? unmountedItem.value : 0;
|
|
1651
|
+
updates.unmountedList.delete(cid);
|
|
1652
|
+
updates.mountedList.pushTail(cid);
|
|
1087
1653
|
return flag;
|
|
1088
1654
|
},
|
|
1089
1655
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
return (cid in updates.mounted);
|
|
1656
|
+
isCellVisible: function(cellOrId) {
|
|
1657
|
+
const cid = cellOrId && this._idToCid[cellOrId.id || cellOrId];
|
|
1658
|
+
if (!cid) return false; // The view is not registered.
|
|
1659
|
+
return this.isViewMounted(cid);
|
|
1095
1660
|
},
|
|
1096
1661
|
|
|
1662
|
+
isViewMounted: function(viewOrCid) {
|
|
1663
|
+
if (!viewOrCid) return false;
|
|
1664
|
+
let cid;
|
|
1665
|
+
if (viewOrCid[CELL_VIEW_MARKER] || viewOrCid[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1666
|
+
cid = viewOrCid.cid;
|
|
1667
|
+
} else {
|
|
1668
|
+
cid = viewOrCid;
|
|
1669
|
+
}
|
|
1670
|
+
return this._updates.mountedList.has(cid);
|
|
1671
|
+
},
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* @deprecated use `updateCellsVisibility` instead.
|
|
1675
|
+
* `paper.updateCellsVisibility({ cellVisibility: () => true });`
|
|
1676
|
+
*/
|
|
1097
1677
|
dumpViews: function(opt) {
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
this.
|
|
1678
|
+
// Update cell visibility without `cellVisibility` callback i.e. make the cells visible
|
|
1679
|
+
const passingOpt = defaults({}, opt, { cellVisibility: null, viewport: null });
|
|
1680
|
+
this.updateCellsVisibility(passingOpt);
|
|
1101
1681
|
},
|
|
1102
1682
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1683
|
+
/**
|
|
1684
|
+
* Process all scheduled updates synchronously.
|
|
1685
|
+
*/
|
|
1686
|
+
updateViews: function(opt = {}) {
|
|
1105
1687
|
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 };
|
|
1688
|
+
const batchStats = this.updateViewsBatch({ ...opt, batchSize: Infinity });
|
|
1689
|
+
const stats = {
|
|
1690
|
+
updated: batchStats.updated,
|
|
1691
|
+
priority: batchStats.priority,
|
|
1692
|
+
// For backward compatibility. Will be removed in the future.
|
|
1693
|
+
batches: Number.isFinite(opt.batchSize) ? Math.ceil(batchStats.updated / opt.batchSize) : 1
|
|
1694
|
+
};
|
|
1117
1695
|
this.notifyAfterRender(stats, opt);
|
|
1118
1696
|
return stats;
|
|
1119
1697
|
},
|
|
1120
1698
|
|
|
1121
1699
|
hasScheduledUpdates: function() {
|
|
1122
|
-
const
|
|
1700
|
+
const updates = this._updates;
|
|
1701
|
+
const priorities = updates.priorities;
|
|
1123
1702
|
const priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
1124
1703
|
let i = priorityIndexes.length;
|
|
1125
1704
|
while (i > 0 && i--) {
|
|
@@ -1131,11 +1710,38 @@ export const Paper = View.extend({
|
|
|
1131
1710
|
|
|
1132
1711
|
updateViewsAsync: function(opt, data) {
|
|
1133
1712
|
opt || (opt = {});
|
|
1134
|
-
data || (data = {
|
|
1713
|
+
data || (data = {
|
|
1714
|
+
processed: 0,
|
|
1715
|
+
priority: MIN_PRIORITY,
|
|
1716
|
+
checkedUnmounted: 0,
|
|
1717
|
+
checkedMounted: 0,
|
|
1718
|
+
});
|
|
1135
1719
|
const { _updates: updates, options } = this;
|
|
1136
|
-
const id = updates
|
|
1137
|
-
|
|
1720
|
+
const { id, mountedList, unmountedList, freshAfterReset } = updates;
|
|
1721
|
+
|
|
1722
|
+
// Should we run the next batch update this frame?
|
|
1723
|
+
let runBatchUpdate = true;
|
|
1724
|
+
if (!id) {
|
|
1725
|
+
// If there's no scheduled frame, no batch update is needed.
|
|
1726
|
+
runBatchUpdate = false;
|
|
1727
|
+
} else {
|
|
1728
|
+
// Cancel any scheduled frame.
|
|
1138
1729
|
cancelFrame(id);
|
|
1730
|
+
if (freshAfterReset) {
|
|
1731
|
+
// First update after a reset.
|
|
1732
|
+
updates.freshAfterReset = false;
|
|
1733
|
+
// When `initializeUnmounted` is enabled, there are no scheduled updates.
|
|
1734
|
+
// We check whether the `mountedList` and `unmountedList` are empty.
|
|
1735
|
+
if (!this.legacyMode && mountedList.length === 0 && unmountedList.length === 0) {
|
|
1736
|
+
// No updates to process; We trigger before/after render events via `updateViews`.
|
|
1737
|
+
// Note: If `autoFreeze` is enabled, 'idle' event triggers next frame.
|
|
1738
|
+
this.updateViews();
|
|
1739
|
+
runBatchUpdate = false;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
if (runBatchUpdate) {
|
|
1139
1745
|
if (data.processed === 0 && this.hasScheduledUpdates()) {
|
|
1140
1746
|
this.notifyBeforeRender(opt);
|
|
1141
1747
|
}
|
|
@@ -1144,7 +1750,7 @@ export const Paper = View.extend({
|
|
|
1144
1750
|
mountBatchSize: MOUNT_BATCH_SIZE - stats.mounted,
|
|
1145
1751
|
unmountBatchSize: MOUNT_BATCH_SIZE - stats.unmounted
|
|
1146
1752
|
});
|
|
1147
|
-
const checkStats = this.
|
|
1753
|
+
const checkStats = this.scheduleCellsVisibilityUpdate(passingOpt);
|
|
1148
1754
|
const unmountCount = checkStats.unmounted;
|
|
1149
1755
|
const mountCount = checkStats.mounted;
|
|
1150
1756
|
let processed = data.processed;
|
|
@@ -1165,11 +1771,22 @@ export const Paper = View.extend({
|
|
|
1165
1771
|
} else {
|
|
1166
1772
|
data.processed = processed;
|
|
1167
1773
|
}
|
|
1774
|
+
data.checkedUnmounted = 0;
|
|
1775
|
+
data.checkedMounted = 0;
|
|
1168
1776
|
} else {
|
|
1169
|
-
|
|
1170
|
-
|
|
1777
|
+
data.checkedUnmounted += Math.max(passingOpt.mountBatchSize, 0);
|
|
1778
|
+
data.checkedMounted += Math.max(passingOpt.unmountBatchSize, 0);
|
|
1779
|
+
// The `scheduleCellsVisibilityUpdate` could have scheduled some insertions
|
|
1780
|
+
// (note that removals are currently done synchronously).
|
|
1781
|
+
if (options.autoFreeze && !this.hasScheduledUpdates()) {
|
|
1782
|
+
// If there are no updates scheduled and we checked all unmounted views,
|
|
1783
|
+
if (
|
|
1784
|
+
data.checkedUnmounted >= unmountedList.length &&
|
|
1785
|
+
data.checkedMounted >= mountedList.length
|
|
1786
|
+
) {
|
|
1787
|
+
// We freeze the paper and notify the idle state.
|
|
1171
1788
|
this.freeze();
|
|
1172
|
-
updates.idle =
|
|
1789
|
+
updates.idle = { wakeUpOptions: opt };
|
|
1173
1790
|
this.trigger('render:idle', opt);
|
|
1174
1791
|
}
|
|
1175
1792
|
}
|
|
@@ -1189,6 +1806,7 @@ export const Paper = View.extend({
|
|
|
1189
1806
|
},
|
|
1190
1807
|
|
|
1191
1808
|
notifyBeforeRender: function(opt = {}) {
|
|
1809
|
+
if (opt.silent) return;
|
|
1192
1810
|
let beforeFn = opt.beforeRender;
|
|
1193
1811
|
if (typeof beforeFn !== 'function') {
|
|
1194
1812
|
beforeFn = this.options.beforeRender;
|
|
@@ -1198,6 +1816,7 @@ export const Paper = View.extend({
|
|
|
1198
1816
|
},
|
|
1199
1817
|
|
|
1200
1818
|
notifyAfterRender: function(stats, opt = {}) {
|
|
1819
|
+
if (opt.silent) return;
|
|
1201
1820
|
let afterFn = opt.afterRender;
|
|
1202
1821
|
if (typeof afterFn !== 'function') {
|
|
1203
1822
|
afterFn = this.options.afterRender;
|
|
@@ -1208,6 +1827,56 @@ export const Paper = View.extend({
|
|
|
1208
1827
|
this.trigger('render:done', stats, opt);
|
|
1209
1828
|
},
|
|
1210
1829
|
|
|
1830
|
+
prioritizeCellViewMount: function(cellOrId) {
|
|
1831
|
+
if (!cellOrId) return false;
|
|
1832
|
+
const cid = this._idToCid[cellOrId.id || cellOrId];
|
|
1833
|
+
if (!cid) return false;
|
|
1834
|
+
const { unmountedList } = this._updates;
|
|
1835
|
+
if (!unmountedList.has(cid)) return false;
|
|
1836
|
+
// Move the view to the head of the mounted list
|
|
1837
|
+
unmountedList.moveToHead(cid);
|
|
1838
|
+
return true;
|
|
1839
|
+
},
|
|
1840
|
+
|
|
1841
|
+
prioritizeCellViewUnmount: function(cellOrId) {
|
|
1842
|
+
if (!cellOrId) return false;
|
|
1843
|
+
const cid = this._idToCid[cellOrId.id || cellOrId];
|
|
1844
|
+
if (!cid) return false;
|
|
1845
|
+
const { mountedList } = this._updates;
|
|
1846
|
+
if (!mountedList.has(cid)) return false;
|
|
1847
|
+
// Move the view to the head of the unmounted list
|
|
1848
|
+
mountedList.moveToHead(cid);
|
|
1849
|
+
return true;
|
|
1850
|
+
},
|
|
1851
|
+
|
|
1852
|
+
_evalCellVisibility: function(viewLike, isMounted, visibilityCallback) {
|
|
1853
|
+
if (!visibilityCallback || !viewLike.DETACHABLE) return true;
|
|
1854
|
+
if (this.legacyMode) {
|
|
1855
|
+
return visibilityCallback.call(this, viewLike, isMounted, this);
|
|
1856
|
+
}
|
|
1857
|
+
// The visibility check runs for CellView only.
|
|
1858
|
+
if (!viewLike[CELL_VIEW_MARKER] && !viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) return true;
|
|
1859
|
+
// The cellView model must be a member of this graph.
|
|
1860
|
+
if (viewLike.model.graph !== this.model) {
|
|
1861
|
+
// It could have been removed from the graph.
|
|
1862
|
+
// If the view was mounted, we keep it mounted.
|
|
1863
|
+
return isMounted;
|
|
1864
|
+
}
|
|
1865
|
+
return visibilityCallback.call(this, viewLike.model, isMounted, this);
|
|
1866
|
+
},
|
|
1867
|
+
|
|
1868
|
+
_getCellVisibilityCallback: function(opt) {
|
|
1869
|
+
const { options } = this;
|
|
1870
|
+
if (this.legacyMode) {
|
|
1871
|
+
const viewportFn = 'viewport' in opt ? opt.viewport : options.viewport;
|
|
1872
|
+
if (typeof viewportFn === 'function') return viewportFn;
|
|
1873
|
+
} else {
|
|
1874
|
+
const isVisibleFn = 'cellVisibility' in opt ? opt.cellVisibility : options.cellVisibility;
|
|
1875
|
+
if (typeof isVisibleFn === 'function') return isVisibleFn;
|
|
1876
|
+
}
|
|
1877
|
+
return null;
|
|
1878
|
+
},
|
|
1879
|
+
|
|
1211
1880
|
updateViewsBatch: function(opt) {
|
|
1212
1881
|
opt || (opt = {});
|
|
1213
1882
|
var batchSize = opt.batchSize || UPDATE_BATCH_SIZE;
|
|
@@ -1220,8 +1889,7 @@ export const Paper = View.extend({
|
|
|
1220
1889
|
var empty = true;
|
|
1221
1890
|
var options = this.options;
|
|
1222
1891
|
var priorities = updates.priorities;
|
|
1223
|
-
|
|
1224
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
1892
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1225
1893
|
var postponeViewFn = options.onViewPostponed;
|
|
1226
1894
|
if (typeof postponeViewFn !== 'function') postponeViewFn = null;
|
|
1227
1895
|
var priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
|
|
@@ -1233,33 +1901,56 @@ export const Paper = View.extend({
|
|
|
1233
1901
|
empty = false;
|
|
1234
1902
|
break main;
|
|
1235
1903
|
}
|
|
1236
|
-
var view =
|
|
1904
|
+
var view = viewsRegistry[cid];
|
|
1237
1905
|
if (!view) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1906
|
+
view = this._viewPlaceholders[cid];
|
|
1907
|
+
if (!view) {
|
|
1908
|
+
/**
|
|
1909
|
+
* This can occur when:
|
|
1910
|
+
* - the model is removed and a new model with the same id is added
|
|
1911
|
+
* - the view `initialize` method was overridden and the view was not registered
|
|
1912
|
+
* - an mvc.View scheduled an update, was removed and paper was not notified
|
|
1913
|
+
*/
|
|
1914
|
+
delete priorityUpdates[cid];
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1241
1917
|
}
|
|
1242
1918
|
var currentFlag = priorityUpdates[cid];
|
|
1243
|
-
if ((currentFlag &
|
|
1919
|
+
if ((currentFlag & this.FLAG_REMOVE) === 0) {
|
|
1244
1920
|
// We should never check a view for viewport if we are about to remove the view
|
|
1245
|
-
|
|
1246
|
-
if (
|
|
1921
|
+
const isMounted = !updates.unmountedList.has(cid);
|
|
1922
|
+
if (!this._evalCellVisibility(view, isMounted, visibilityCb)) {
|
|
1247
1923
|
// Unmount View
|
|
1248
|
-
if (
|
|
1924
|
+
if (isMounted) {
|
|
1925
|
+
// The view is currently mounted. Hide the view (detach or remove it).
|
|
1249
1926
|
this.registerUnmountedView(view);
|
|
1250
|
-
this.
|
|
1927
|
+
this._hideView(view);
|
|
1928
|
+
} else {
|
|
1929
|
+
// The view is not mounted. We can just update the unmounted list.
|
|
1930
|
+
// We ADD the current flag to the flag that was already scheduled.
|
|
1931
|
+
this._mergeUnmountedViewScheduledUpdates(cid, currentFlag);
|
|
1251
1932
|
}
|
|
1252
|
-
|
|
1933
|
+
// Delete the current update as it has been processed.
|
|
1253
1934
|
delete priorityUpdates[cid];
|
|
1254
1935
|
unmountCount++;
|
|
1255
1936
|
continue;
|
|
1256
1937
|
}
|
|
1257
1938
|
// Mount View
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1939
|
+
if (view[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1940
|
+
view = this._resolveCellViewPlaceholder(view);
|
|
1941
|
+
// Newly initialized view needs to be initialized
|
|
1942
|
+
currentFlag |= this.getCellViewInitFlag(view);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
if (!isMounted) {
|
|
1946
|
+
currentFlag |= this.FLAG_INSERT;
|
|
1260
1947
|
mountCount++;
|
|
1261
1948
|
}
|
|
1262
1949
|
currentFlag |= this.registerMountedView(view);
|
|
1950
|
+
} else if (view[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
1951
|
+
// We are trying to remove a placeholder view.
|
|
1952
|
+
// This should not occur as the placeholder should have been unregistered
|
|
1953
|
+
continue;
|
|
1263
1954
|
}
|
|
1264
1955
|
var leftoverFlag = this.updateView(view, currentFlag, opt);
|
|
1265
1956
|
if (leftoverFlag > 0) {
|
|
@@ -1286,104 +1977,125 @@ export const Paper = View.extend({
|
|
|
1286
1977
|
};
|
|
1287
1978
|
},
|
|
1288
1979
|
|
|
1980
|
+
getCellViewInitFlag: function(cellView) {
|
|
1981
|
+
return this.FLAG_INIT | cellView.getFlag(result(cellView, 'initFlag'));
|
|
1982
|
+
},
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* @ignore This method returns an array of cellViewLike objects and therefore
|
|
1986
|
+
* is meant for internal/test use only.
|
|
1987
|
+
* The view placeholders are not exposed via public API.
|
|
1988
|
+
*/
|
|
1289
1989
|
getUnmountedViews: function() {
|
|
1290
1990
|
const updates = this._updates;
|
|
1291
|
-
const
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
for (
|
|
1295
|
-
|
|
1991
|
+
const unmountedViews = new Array(updates.unmountedList.length);
|
|
1992
|
+
const unmountedCids = updates.unmountedList.keys();
|
|
1993
|
+
let i = 0;
|
|
1994
|
+
for (const cid of unmountedCids) {
|
|
1995
|
+
// If the view is a placeholder, it won't be in the global views map
|
|
1996
|
+
// If the view is not a cell view, it won't be in the viewPlaceholders map
|
|
1997
|
+
unmountedViews[i++] = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
1296
1998
|
}
|
|
1297
1999
|
return unmountedViews;
|
|
1298
2000
|
},
|
|
1299
2001
|
|
|
2002
|
+
/**
|
|
2003
|
+
* @ignore This method returns an array of cellViewLike objects and therefore
|
|
2004
|
+
* is meant for internal/test use only.
|
|
2005
|
+
* The view placeholders are not exposed via public API.
|
|
2006
|
+
*/
|
|
1300
2007
|
getMountedViews: function() {
|
|
1301
2008
|
const updates = this._updates;
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
for (
|
|
1306
|
-
mountedViews[i] =
|
|
2009
|
+
const mountedViews = new Array(updates.mountedList.length);
|
|
2010
|
+
const mountedCids = updates.mountedList.keys();
|
|
2011
|
+
let i = 0;
|
|
2012
|
+
for (const cid of mountedCids) {
|
|
2013
|
+
mountedViews[i++] = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
1307
2014
|
}
|
|
1308
2015
|
return mountedViews;
|
|
1309
2016
|
},
|
|
1310
2017
|
|
|
1311
|
-
checkUnmountedViews: function(
|
|
2018
|
+
checkUnmountedViews: function(visibilityCb, opt) {
|
|
1312
2019
|
opt || (opt = {});
|
|
1313
2020
|
var mountCount = 0;
|
|
1314
|
-
if (typeof
|
|
2021
|
+
if (typeof visibilityCb !== 'function') visibilityCb = null;
|
|
1315
2022
|
var batchSize = 'mountBatchSize' in opt ? opt.mountBatchSize : Infinity;
|
|
1316
2023
|
var updates = this._updates;
|
|
1317
|
-
var
|
|
1318
|
-
var
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
if (!
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
2024
|
+
var unmountedList = updates.unmountedList;
|
|
2025
|
+
for (var i = 0, n = Math.min(unmountedList.length, batchSize); i < n; i++) {
|
|
2026
|
+
const { key: cid } = unmountedList.peekHead();
|
|
2027
|
+
let view = viewsRegistry[cid] || this._viewPlaceholders[cid];
|
|
2028
|
+
if (!view) {
|
|
2029
|
+
// This should not occur
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
if (!this._evalCellVisibility(view, false, visibilityCb)) {
|
|
1325
2033
|
// Push at the end of all unmounted ids, so this can be check later again
|
|
1326
|
-
|
|
2034
|
+
unmountedList.rotate();
|
|
1327
2035
|
continue;
|
|
1328
2036
|
}
|
|
2037
|
+
// Remove the view from the unmounted list
|
|
2038
|
+
const { value: prevFlag } = unmountedList.popHead();
|
|
1329
2039
|
mountCount++;
|
|
1330
|
-
|
|
2040
|
+
const flag = this.registerMountedView(view) | prevFlag;
|
|
1331
2041
|
if (flag) this.scheduleViewUpdate(view, flag, view.UPDATE_PRIORITY, { mounting: true });
|
|
1332
2042
|
}
|
|
1333
|
-
// Get rid of views, that have been mounted
|
|
1334
|
-
unmountedCids.splice(0, i);
|
|
1335
2043
|
return mountCount;
|
|
1336
2044
|
},
|
|
1337
2045
|
|
|
1338
|
-
checkMountedViews: function(
|
|
2046
|
+
checkMountedViews: function(visibilityCb, opt) {
|
|
1339
2047
|
opt || (opt = {});
|
|
1340
2048
|
var unmountCount = 0;
|
|
1341
|
-
if (typeof
|
|
2049
|
+
if (typeof visibilityCb !== 'function') return unmountCount;
|
|
1342
2050
|
var batchSize = 'unmountBatchSize' in opt ? opt.unmountBatchSize : Infinity;
|
|
1343
2051
|
var updates = this._updates;
|
|
1344
|
-
|
|
1345
|
-
var
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
if (!
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
2052
|
+
const mountedList = updates.mountedList;
|
|
2053
|
+
for (var i = 0, n = Math.min(mountedList.length, batchSize); i < n; i++) {
|
|
2054
|
+
const { key: cid } = mountedList.peekHead();
|
|
2055
|
+
const view = viewsRegistry[cid];
|
|
2056
|
+
if (!view) {
|
|
2057
|
+
// A view (not a cell view) has been removed from the paper.
|
|
2058
|
+
// Remove it from the mounted list and continue.
|
|
2059
|
+
mountedList.popHead();
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
if (this._evalCellVisibility(view, true, visibilityCb)) {
|
|
1352
2063
|
// Push at the end of all mounted ids, so this can be check later again
|
|
1353
|
-
|
|
2064
|
+
mountedList.rotate();
|
|
1354
2065
|
continue;
|
|
1355
2066
|
}
|
|
2067
|
+
// Remove the view from the mounted list
|
|
2068
|
+
mountedList.popHead();
|
|
1356
2069
|
unmountCount++;
|
|
1357
2070
|
var flag = this.registerUnmountedView(view);
|
|
1358
|
-
if (flag)
|
|
2071
|
+
if (flag) {
|
|
2072
|
+
this._hideView(view);
|
|
2073
|
+
}
|
|
1359
2074
|
}
|
|
1360
|
-
// Get rid of views, that have been unmounted
|
|
1361
|
-
mountedCids.splice(0, i);
|
|
1362
2075
|
return unmountCount;
|
|
1363
2076
|
},
|
|
1364
2077
|
|
|
1365
2078
|
checkViewVisibility: function(cellView, opt = {}) {
|
|
1366
|
-
|
|
1367
|
-
if (typeof viewportFn !== 'function') viewportFn = null;
|
|
2079
|
+
const visibilityCb = this._getCellVisibilityCallback(opt);
|
|
1368
2080
|
const updates = this._updates;
|
|
1369
|
-
const {
|
|
1370
|
-
|
|
2081
|
+
const { mountedList, unmountedList } = updates;
|
|
2082
|
+
|
|
2083
|
+
const visible = this._evalCellVisibility(cellView, false, visibilityCb);
|
|
1371
2084
|
|
|
1372
2085
|
let isUnmounted = false;
|
|
1373
2086
|
let isMounted = false;
|
|
1374
2087
|
|
|
1375
|
-
if (cellView.cid
|
|
2088
|
+
if (mountedList.has(cellView.cid) && !visible) {
|
|
1376
2089
|
const flag = this.registerUnmountedView(cellView);
|
|
1377
|
-
if (flag) this.
|
|
1378
|
-
|
|
1379
|
-
updates.mountedCids.splice(i, 1);
|
|
2090
|
+
if (flag) this._hideView(cellView);
|
|
2091
|
+
mountedList.delete(cellView.cid);
|
|
1380
2092
|
isUnmounted = true;
|
|
1381
2093
|
}
|
|
1382
2094
|
|
|
1383
|
-
if (!isUnmounted && cellView.cid
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
2095
|
+
if (!isUnmounted && unmountedList.has(cellView.cid) && visible) {
|
|
2096
|
+
const unmountedItem = unmountedList.get(cellView.cid);
|
|
2097
|
+
unmountedList.delete(cellView.cid);
|
|
2098
|
+
const flag = unmountedItem.value | this.registerMountedView(cellView);
|
|
1387
2099
|
if (flag) this.scheduleViewUpdate(cellView, flag, cellView.UPDATE_PRIORITY, { mounting: true });
|
|
1388
2100
|
isMounted = true;
|
|
1389
2101
|
}
|
|
@@ -1394,25 +2106,65 @@ export const Paper = View.extend({
|
|
|
1394
2106
|
};
|
|
1395
2107
|
},
|
|
1396
2108
|
|
|
1397
|
-
|
|
1398
|
-
|
|
2109
|
+
/**
|
|
2110
|
+
* @public
|
|
2111
|
+
* Update the visibility of a single cell.
|
|
2112
|
+
*/
|
|
2113
|
+
updateCellVisibility: function(cell, opt = {}) {
|
|
2114
|
+
const cellViewLike = this._getCellViewLike(cell);
|
|
2115
|
+
if (!cellViewLike) return;
|
|
2116
|
+
const stats = this.checkViewVisibility(cellViewLike, opt);
|
|
2117
|
+
// Note: `unmounted` views are removed immediately
|
|
2118
|
+
if (stats.mounted > 0) {
|
|
2119
|
+
// Mounting is scheduled. Run the update.
|
|
2120
|
+
// Note: the view might be a placeholder.
|
|
2121
|
+
this.requireView(cell, opt);
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* @public
|
|
2127
|
+
* Update the visibility of all cells.
|
|
2128
|
+
*/
|
|
2129
|
+
updateCellsVisibility: function(opt = {}) {
|
|
2130
|
+
// Check the visibility of all cells and schedule their updates.
|
|
2131
|
+
this.scheduleCellsVisibilityUpdate(opt);
|
|
2132
|
+
// Perform the scheduled updates while avoiding re-evaluating the visibility.
|
|
2133
|
+
const keepCurrentVisibility = (_, isVisible) => isVisible;
|
|
2134
|
+
this.updateViews({ ...opt, cellVisibility: keepCurrentVisibility });
|
|
2135
|
+
},
|
|
2136
|
+
|
|
2137
|
+
/**
|
|
2138
|
+
* @protected
|
|
2139
|
+
* Run visibility checks for all cells and schedule their updates.
|
|
2140
|
+
*/
|
|
2141
|
+
scheduleCellsVisibilityUpdate(opt) {
|
|
2142
|
+
const passingOpt = defaults({}, opt, {
|
|
1399
2143
|
mountBatchSize: Infinity,
|
|
1400
2144
|
unmountBatchSize: Infinity
|
|
1401
2145
|
});
|
|
1402
|
-
|
|
1403
|
-
|
|
2146
|
+
const visibilityCb = this._getCellVisibilityCallback(passingOpt);
|
|
2147
|
+
const unmountedCount = this.checkMountedViews(visibilityCb, passingOpt);
|
|
1404
2148
|
if (unmountedCount > 0) {
|
|
1405
2149
|
// 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(
|
|
2150
|
+
var unmountedList = this._updates.unmountedList;
|
|
2151
|
+
passingOpt.mountBatchSize = Math.min(unmountedList.length - unmountedCount, passingOpt.mountBatchSize);
|
|
1408
2152
|
}
|
|
1409
|
-
|
|
2153
|
+
const mountedCount = this.checkUnmountedViews(visibilityCb, passingOpt);
|
|
1410
2154
|
return {
|
|
1411
2155
|
mounted: mountedCount,
|
|
1412
2156
|
unmounted: unmountedCount
|
|
1413
2157
|
};
|
|
1414
2158
|
},
|
|
1415
2159
|
|
|
2160
|
+
/**
|
|
2161
|
+
* @deprecated use `updateCellsVisibility` instead
|
|
2162
|
+
* This method will be renamed and made private in the future.
|
|
2163
|
+
*/
|
|
2164
|
+
checkViewport: function(opt) {
|
|
2165
|
+
return this.scheduleCellsVisibilityUpdate(opt);
|
|
2166
|
+
},
|
|
2167
|
+
|
|
1416
2168
|
freeze: function(opt) {
|
|
1417
2169
|
opt || (opt = {});
|
|
1418
2170
|
var updates = this._updates;
|
|
@@ -1428,6 +2180,10 @@ export const Paper = View.extend({
|
|
|
1428
2180
|
this.options.frozen = true;
|
|
1429
2181
|
var id = updates.id;
|
|
1430
2182
|
updates.id = null;
|
|
2183
|
+
if (!this.legacyMode) {
|
|
2184
|
+
// Make sure the `freeze()` method ends the idle state.
|
|
2185
|
+
updates.idle = false;
|
|
2186
|
+
}
|
|
1431
2187
|
if (this.isAsync() && id) cancelFrame(id);
|
|
1432
2188
|
},
|
|
1433
2189
|
|
|
@@ -1441,6 +2197,7 @@ export const Paper = View.extend({
|
|
|
1441
2197
|
updates.freezeKey = null;
|
|
1442
2198
|
// key passed, but the paper is already freezed
|
|
1443
2199
|
if (key && key === freezeKey && updates.keyFrozen) return;
|
|
2200
|
+
updates.idle = false;
|
|
1444
2201
|
if (this.isAsync()) {
|
|
1445
2202
|
this.freeze();
|
|
1446
2203
|
this.updateViewsAsync(opt);
|
|
@@ -1449,17 +2206,30 @@ export const Paper = View.extend({
|
|
|
1449
2206
|
}
|
|
1450
2207
|
this.options.frozen = updates.keyFrozen = false;
|
|
1451
2208
|
if (updates.sort) {
|
|
1452
|
-
this.
|
|
2209
|
+
this.sortLayerViews();
|
|
1453
2210
|
updates.sort = false;
|
|
1454
2211
|
}
|
|
1455
2212
|
},
|
|
1456
2213
|
|
|
2214
|
+
wakeUp: function() {
|
|
2215
|
+
if (!this.isIdle()) return;
|
|
2216
|
+
this.unfreeze(this._updates.idle.wakeUpOptions);
|
|
2217
|
+
},
|
|
2218
|
+
|
|
1457
2219
|
isAsync: function() {
|
|
1458
2220
|
return !!this.options.async;
|
|
1459
2221
|
},
|
|
1460
2222
|
|
|
1461
2223
|
isFrozen: function() {
|
|
1462
|
-
return !!this.options.frozen;
|
|
2224
|
+
return !!this.options.frozen && !this.isIdle();
|
|
2225
|
+
},
|
|
2226
|
+
|
|
2227
|
+
isIdle: function() {
|
|
2228
|
+
if (this.legacyMode) {
|
|
2229
|
+
// Not implemented in the legacy mode.
|
|
2230
|
+
return false;
|
|
2231
|
+
}
|
|
2232
|
+
return !!(this._updates && this._updates.idle);
|
|
1463
2233
|
},
|
|
1464
2234
|
|
|
1465
2235
|
isExactSorting: function() {
|
|
@@ -1471,8 +2241,8 @@ export const Paper = View.extend({
|
|
|
1471
2241
|
this.freeze();
|
|
1472
2242
|
this._updates.disabled = true;
|
|
1473
2243
|
//clean up all DOM elements/views to prevent memory leaks
|
|
1474
|
-
this.removeLayers();
|
|
1475
2244
|
this.removeViews();
|
|
2245
|
+
this._removeLayerViews();
|
|
1476
2246
|
},
|
|
1477
2247
|
|
|
1478
2248
|
getComputedSize: function() {
|
|
@@ -1720,7 +2490,17 @@ export const Paper = View.extend({
|
|
|
1720
2490
|
return this.model.getBBox() || new Rect();
|
|
1721
2491
|
}
|
|
1722
2492
|
|
|
1723
|
-
|
|
2493
|
+
const graphLayerViews = this.getGraphLayerViews();
|
|
2494
|
+
// Return an empty rectangle if there are no layers
|
|
2495
|
+
// should not happen in practice
|
|
2496
|
+
if (graphLayerViews.length === 0) {
|
|
2497
|
+
return new Rect();
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// Combine content area rectangles from all layers,
|
|
2501
|
+
// considering only graph layer views to exclude non-cell elements (e.g., grid, tools)
|
|
2502
|
+
const bbox = g.Rect.fromRectUnion(...graphLayerViews.map(view => view.vel.getBBox()));
|
|
2503
|
+
return bbox;
|
|
1724
2504
|
},
|
|
1725
2505
|
|
|
1726
2506
|
// Return the dimensions of the content bbox in the paper units (as it appears on screen).
|
|
@@ -1759,21 +2539,57 @@ export const Paper = View.extend({
|
|
|
1759
2539
|
return restrictedArea;
|
|
1760
2540
|
},
|
|
1761
2541
|
|
|
1762
|
-
|
|
2542
|
+
_resolveCellViewPlaceholder: function(placeholder) {
|
|
2543
|
+
const { model, viewClass, cid } = placeholder;
|
|
2544
|
+
const view = this._initializeCellView(viewClass, model, cid);
|
|
2545
|
+
this._registerCellView(view);
|
|
2546
|
+
this._unregisterCellViewPlaceholder(placeholder);
|
|
2547
|
+
return view;
|
|
2548
|
+
},
|
|
1763
2549
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2550
|
+
_registerCellViewPlaceholder: function(cell, cid = uniqueId('view')) {
|
|
2551
|
+
const ViewClass = this._resolveCellViewClass(cell);
|
|
2552
|
+
const placeholder = {
|
|
2553
|
+
// A tag to identify the placeholder from a CellView.
|
|
2554
|
+
[CELL_VIEW_PLACEHOLDER_MARKER]: true,
|
|
2555
|
+
cid,
|
|
2556
|
+
model: cell,
|
|
2557
|
+
DETACHABLE: true,
|
|
2558
|
+
viewClass: ViewClass,
|
|
2559
|
+
UPDATE_PRIORITY: ViewClass.prototype.UPDATE_PRIORITY,
|
|
2560
|
+
};
|
|
2561
|
+
this._viewPlaceholders[cid] = placeholder;
|
|
2562
|
+
return placeholder;
|
|
2563
|
+
},
|
|
1767
2564
|
|
|
1768
|
-
|
|
1769
|
-
|
|
2565
|
+
_registerCellView: function(cellView) {
|
|
2566
|
+
cellView.paper = this;
|
|
2567
|
+
this._views[cellView.model.id] = cellView;
|
|
2568
|
+
},
|
|
1770
2569
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
var type = cell.get('type') + 'View';
|
|
1775
|
-
var namespaceViewClass = getByPath(namespace, type, '.');
|
|
2570
|
+
_unregisterCellViewPlaceholder: function(placeholder) {
|
|
2571
|
+
delete this._viewPlaceholders[placeholder.cid];
|
|
2572
|
+
},
|
|
1776
2573
|
|
|
2574
|
+
_initializeCellView: function(ViewClass, cell, cid) {
|
|
2575
|
+
const { options } = this;
|
|
2576
|
+
const { interactive, labelsLayer } = options;
|
|
2577
|
+
return new ViewClass({
|
|
2578
|
+
cid,
|
|
2579
|
+
model: cell,
|
|
2580
|
+
interactive,
|
|
2581
|
+
labelsLayer: labelsLayer === true ? paperLayers.LABELS : labelsLayer
|
|
2582
|
+
});
|
|
2583
|
+
},
|
|
2584
|
+
|
|
2585
|
+
_resolveCellViewClass: function(cell) {
|
|
2586
|
+
const { options } = this;
|
|
2587
|
+
const { cellViewNamespace } = options;
|
|
2588
|
+
const type = cell.get('type') + 'View';
|
|
2589
|
+
const namespaceViewClass = getByPath(cellViewNamespace, type, '.');
|
|
2590
|
+
// A class taken from the paper options.
|
|
2591
|
+
let optionalViewClass;
|
|
2592
|
+
let defaultViewClass;
|
|
1777
2593
|
if (cell.isLink()) {
|
|
1778
2594
|
optionalViewClass = options.linkView;
|
|
1779
2595
|
defaultViewClass = LinkView;
|
|
@@ -1781,7 +2597,6 @@ export const Paper = View.extend({
|
|
|
1781
2597
|
optionalViewClass = options.elementView;
|
|
1782
2598
|
defaultViewClass = ElementView;
|
|
1783
2599
|
}
|
|
1784
|
-
|
|
1785
2600
|
// a) the paper options view is a class (deprecated)
|
|
1786
2601
|
// 1. search the namespace for a view
|
|
1787
2602
|
// 2. if no view was found, use view from the paper options
|
|
@@ -1789,29 +2604,54 @@ export const Paper = View.extend({
|
|
|
1789
2604
|
// 1. call the function from the paper options
|
|
1790
2605
|
// 2. if no view was return, search the namespace for a view
|
|
1791
2606
|
// 3. if no view was found, use the default
|
|
1792
|
-
|
|
2607
|
+
return (optionalViewClass.prototype instanceof ViewBase)
|
|
1793
2608
|
? namespaceViewClass || optionalViewClass
|
|
1794
2609
|
: optionalViewClass.call(this, cell) || namespaceViewClass || defaultViewClass;
|
|
2610
|
+
},
|
|
1795
2611
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
2612
|
+
// Returns a CellView instance or its placeholder for the given cell.
|
|
2613
|
+
_getCellViewLike: function(cell) {
|
|
2614
|
+
|
|
2615
|
+
let id;
|
|
2616
|
+
if (isString(cell) || isNumber(cell)) {
|
|
2617
|
+
// If the cell is a string or number, it is an id of the view.
|
|
2618
|
+
id = cell;
|
|
2619
|
+
} else if (cell) {
|
|
2620
|
+
// If the cell is an object, it should have an id property.
|
|
2621
|
+
id = cell.id;
|
|
2622
|
+
} else {
|
|
2623
|
+
// If the cell is falsy, return null.
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
const view = this._views[id];
|
|
2628
|
+
if (view) return view;
|
|
2629
|
+
|
|
2630
|
+
// If the view is not found, it may be a placeholder
|
|
2631
|
+
const cid = this._idToCid[id];
|
|
2632
|
+
if (cid) {
|
|
2633
|
+
return this._viewPlaceholders[cid];
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
return null;
|
|
1801
2637
|
},
|
|
1802
2638
|
|
|
1803
|
-
|
|
2639
|
+
createViewForModel: function(cell, cid) {
|
|
2640
|
+
return this._initializeCellView(this._resolveCellViewClass(cell), cell, cid);
|
|
2641
|
+
},
|
|
1804
2642
|
|
|
2643
|
+
removeView: function(cell) {
|
|
1805
2644
|
const { id } = cell;
|
|
1806
2645
|
const { _views, _updates } = this;
|
|
1807
2646
|
const view = _views[id];
|
|
1808
2647
|
if (view) {
|
|
1809
2648
|
var { cid } = view;
|
|
1810
|
-
const {
|
|
2649
|
+
const { mountedList, unmountedList } = _updates;
|
|
1811
2650
|
view.remove();
|
|
1812
2651
|
delete _views[id];
|
|
1813
|
-
delete
|
|
1814
|
-
delete
|
|
2652
|
+
delete this._idToCid[id];
|
|
2653
|
+
mountedList.delete(cid);
|
|
2654
|
+
unmountedList.delete(cid);
|
|
1815
2655
|
}
|
|
1816
2656
|
return view;
|
|
1817
2657
|
},
|
|
@@ -1825,7 +2665,7 @@ export const Paper = View.extend({
|
|
|
1825
2665
|
if (id in views) {
|
|
1826
2666
|
view = views[id];
|
|
1827
2667
|
if (view.model === cell) {
|
|
1828
|
-
flag =
|
|
2668
|
+
flag = this.FLAG_INSERT;
|
|
1829
2669
|
create = false;
|
|
1830
2670
|
} else {
|
|
1831
2671
|
// The view for this `id` already exist.
|
|
@@ -1835,14 +2675,42 @@ export const Paper = View.extend({
|
|
|
1835
2675
|
}
|
|
1836
2676
|
}
|
|
1837
2677
|
if (create) {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2678
|
+
const { viewManagement } = this.options;
|
|
2679
|
+
const cid = uniqueId('view');
|
|
2680
|
+
this._idToCid[cell.id] = cid;
|
|
2681
|
+
if (viewManagement.lazyInitialize) {
|
|
2682
|
+
// Register only a placeholder for the view
|
|
2683
|
+
view = this._registerCellViewPlaceholder(cell, cid);
|
|
2684
|
+
flag = this.registerUnmountedView(view);
|
|
2685
|
+
} else {
|
|
2686
|
+
// Create a new view instance
|
|
2687
|
+
view = this.createViewForModel(cell, cid);
|
|
2688
|
+
this._registerCellView(view);
|
|
2689
|
+
flag = this.registerUnmountedView(view);
|
|
2690
|
+
// The newly created view needs to be initialized
|
|
2691
|
+
flag |= this.getCellViewInitFlag(view);
|
|
2692
|
+
}
|
|
2693
|
+
if (viewManagement.initializeUnmounted) {
|
|
2694
|
+
// Save the initialization flags for later and exit early
|
|
2695
|
+
this._mergeUnmountedViewScheduledUpdates(cid, flag);
|
|
2696
|
+
return view;
|
|
2697
|
+
}
|
|
1841
2698
|
}
|
|
2699
|
+
|
|
1842
2700
|
this.requestViewUpdate(view, flag, view.UPDATE_PRIORITY, opt);
|
|
2701
|
+
|
|
1843
2702
|
return view;
|
|
1844
2703
|
},
|
|
1845
2704
|
|
|
2705
|
+
// Update the view flags in the `unmountedList` using the bitwise OR operation
|
|
2706
|
+
_mergeUnmountedViewScheduledUpdates: function(cid, flag) {
|
|
2707
|
+
const { unmountedList } = this._updates;
|
|
2708
|
+
const unmountedItem = unmountedList.get(cid);
|
|
2709
|
+
if (unmountedItem) {
|
|
2710
|
+
unmountedItem.value |= flag;
|
|
2711
|
+
}
|
|
2712
|
+
},
|
|
2713
|
+
|
|
1846
2714
|
onImageDragStart: function() {
|
|
1847
2715
|
// This is the only way to prevent image dragging in Firefox that works.
|
|
1848
2716
|
// Setting -moz-user-select: none, draggable="false" attribute or user-drag: none didn't help.
|
|
@@ -1853,78 +2721,112 @@ export const Paper = View.extend({
|
|
|
1853
2721
|
resetViews: function(cells, opt) {
|
|
1854
2722
|
opt || (opt = {});
|
|
1855
2723
|
cells || (cells = []);
|
|
2724
|
+
// Allows to unfreeze normally while in the idle state using autoFreeze option
|
|
2725
|
+
const key = (this.legacyMode ? this.options.autoFreeze : this.isIdle()) ? null : 'reset';
|
|
1856
2726
|
this._resetUpdates();
|
|
1857
2727
|
// clearing views removes any event listeners
|
|
1858
2728
|
this.removeViews();
|
|
1859
|
-
// Allows to unfreeze normally while in the idle state using autoFreeze option
|
|
1860
|
-
const key = this.options.autoFreeze ? null : 'reset';
|
|
1861
2729
|
this.freeze({ key });
|
|
1862
2730
|
for (var i = 0, n = cells.length; i < n; i++) {
|
|
1863
2731
|
this.renderView(cells[i], opt);
|
|
1864
2732
|
}
|
|
1865
2733
|
this.unfreeze({ key });
|
|
1866
|
-
this.
|
|
2734
|
+
this.sortLayerViews();
|
|
1867
2735
|
},
|
|
1868
2736
|
|
|
1869
2737
|
removeViews: function() {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2738
|
+
// Remove all views and their references from the paper.
|
|
2739
|
+
for (const id in this._views) {
|
|
2740
|
+
const view = this._views[id];
|
|
2741
|
+
if (view) {
|
|
2742
|
+
view.remove();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
1873
2745
|
this._views = {};
|
|
2746
|
+
this._viewPlaceholders = {};
|
|
2747
|
+
this._idToCid = {};
|
|
1874
2748
|
},
|
|
1875
2749
|
|
|
1876
|
-
|
|
1877
|
-
|
|
2750
|
+
sortLayerViews: function() {
|
|
1878
2751
|
if (!this.isExactSorting()) {
|
|
1879
2752
|
// noop
|
|
1880
2753
|
return;
|
|
1881
2754
|
}
|
|
1882
|
-
if (this.isFrozen()) {
|
|
2755
|
+
if (this.isFrozen() || this.isIdle()) {
|
|
1883
2756
|
// sort views once unfrozen
|
|
1884
2757
|
this._updates.sort = true;
|
|
1885
2758
|
return;
|
|
1886
2759
|
}
|
|
1887
|
-
this.
|
|
2760
|
+
this.sortLayerViewsExact();
|
|
1888
2761
|
},
|
|
1889
2762
|
|
|
1890
|
-
|
|
2763
|
+
sortLayerViewsExact: function() {
|
|
2764
|
+
this.getGraphLayerViews().forEach(view => view.sortExact());
|
|
2765
|
+
},
|
|
1891
2766
|
|
|
1892
|
-
|
|
1893
|
-
// associated model `z` attribute.
|
|
2767
|
+
insertView: function(view, isInitialInsert) {
|
|
1894
2768
|
|
|
1895
|
-
|
|
1896
|
-
|
|
2769
|
+
// layer can be null if it is added to the graph with 'dry' option
|
|
2770
|
+
const layerId = this.model.getCellLayerId(view.model);
|
|
2771
|
+
const layerView = this.getLayerView(layerId);
|
|
1897
2772
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
var zA = cellA.attributes.z || 0;
|
|
1902
|
-
var zB = cellB.attributes.z || 0;
|
|
1903
|
-
return (zA === zB) ? 0 : (zA < zB) ? -1 : 1;
|
|
1904
|
-
});
|
|
2773
|
+
layerView.insertCellView(view);
|
|
2774
|
+
|
|
2775
|
+
view.onMount(isInitialInsert);
|
|
1905
2776
|
},
|
|
1906
2777
|
|
|
1907
|
-
|
|
1908
|
-
|
|
2778
|
+
_hideView: function(viewLike) {
|
|
2779
|
+
if (!viewLike || viewLike[CELL_VIEW_PLACEHOLDER_MARKER]) {
|
|
2780
|
+
// A placeholder view was never mounted
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
if (viewLike[CELL_VIEW_MARKER]) {
|
|
2784
|
+
this._hideCellView(viewLike);
|
|
2785
|
+
} else {
|
|
2786
|
+
// A generic view that is not a cell view.
|
|
2787
|
+
viewLike.unmount();
|
|
2788
|
+
}
|
|
2789
|
+
},
|
|
1909
2790
|
|
|
1910
|
-
|
|
1911
|
-
|
|
2791
|
+
// If `cellVisibility` returns `false`, the view will be hidden using this method.
|
|
2792
|
+
_hideCellView: function(cellView) {
|
|
2793
|
+
if (this.options.viewManagement.disposeHidden) {
|
|
2794
|
+
if (this._disposeCellView(cellView)) return;
|
|
2795
|
+
}
|
|
2796
|
+
// Detach the view from the paper, but keep it in memory
|
|
2797
|
+
this._detachCellView(cellView);
|
|
2798
|
+
},
|
|
1912
2799
|
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
layerView.insertNode(el);
|
|
1920
|
-
break;
|
|
2800
|
+
_disposeCellView: function(cellView) {
|
|
2801
|
+
if (HighlighterView.has(cellView) || cellView.hasTools()) {
|
|
2802
|
+
// We currently do not dispose views which has a highlighter or tools attached
|
|
2803
|
+
// Note: Possible improvement would be to serialize highlighters/tools and
|
|
2804
|
+
// restore them on view re-mount.
|
|
2805
|
+
return false;
|
|
1921
2806
|
}
|
|
1922
|
-
|
|
2807
|
+
const cell = cellView.model;
|
|
2808
|
+
// Remove the view from the paper and dispose it
|
|
2809
|
+
cellView.remove();
|
|
2810
|
+
delete this._views[cell.id];
|
|
2811
|
+
this._registerCellViewPlaceholder(cell, cellView.cid);
|
|
2812
|
+
return true;
|
|
1923
2813
|
},
|
|
1924
2814
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2815
|
+
// Dispose (release resources) all hidden views.
|
|
2816
|
+
disposeHiddenCellViews: function() {
|
|
2817
|
+
// Only cell views can be in the unmounted list (not in the legacy mode).
|
|
2818
|
+
if (this.legacyMode) return;
|
|
2819
|
+
const unmountedCids = this._updates.unmountedList.keys();
|
|
2820
|
+
for (const cid of unmountedCids) {
|
|
2821
|
+
const cellView = viewsRegistry[cid];
|
|
2822
|
+
cellView && this._disposeCellView(cellView);
|
|
2823
|
+
}
|
|
2824
|
+
},
|
|
2825
|
+
|
|
2826
|
+
// Detach a view from the paper, but keep it in memory.
|
|
2827
|
+
_detachCellView(cellView) {
|
|
2828
|
+
cellView.unmount();
|
|
2829
|
+
cellView.onDetach();
|
|
1928
2830
|
},
|
|
1929
2831
|
|
|
1930
2832
|
// Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
|
|
@@ -1932,7 +2834,7 @@ export const Paper = View.extend({
|
|
|
1932
2834
|
findView: function($el) {
|
|
1933
2835
|
|
|
1934
2836
|
var el = isString($el)
|
|
1935
|
-
? this.
|
|
2837
|
+
? this.layers.querySelector($el)
|
|
1936
2838
|
: $el instanceof $ ? $el[0] : $el;
|
|
1937
2839
|
|
|
1938
2840
|
var id = this.findAttribute('model-id', el);
|
|
@@ -1942,11 +2844,32 @@ export const Paper = View.extend({
|
|
|
1942
2844
|
},
|
|
1943
2845
|
|
|
1944
2846
|
// 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
|
-
|
|
2847
|
+
findViewByModel: function(cellOrId) {
|
|
2848
|
+
|
|
2849
|
+
const cellViewLike = this._getCellViewLike(cellOrId);
|
|
2850
|
+
if (!cellViewLike) return undefined;
|
|
2851
|
+
if (cellViewLike[CELL_VIEW_MARKER]) {
|
|
2852
|
+
// If the view is not a placeholder, return it directly
|
|
2853
|
+
return cellViewLike;
|
|
2854
|
+
}
|
|
2855
|
+
// We do not expose placeholder views directly. We resolve them before returning.
|
|
2856
|
+
const cellView = this._resolveCellViewPlaceholder(cellViewLike);
|
|
2857
|
+
const flag = this.getCellViewInitFlag(cellView);
|
|
2858
|
+
if (this.isViewMounted(cellView)) {
|
|
2859
|
+
// The view was acting as a placeholder and is already present in the `mounted` list,
|
|
2860
|
+
// indicating that its visibility has been checked, but the update hasn't occurred yet.
|
|
2861
|
+
// Placeholders are resolved during the update routine. Since we're handling it
|
|
2862
|
+
// manually here, we must ensure the view is properly initialized on the next update.
|
|
2863
|
+
this.scheduleViewUpdate(cellView, flag, cellView.UPDATE_PRIORITY, {
|
|
2864
|
+
// It's important to run in isolation to avoid triggering the update of
|
|
2865
|
+
// connected links
|
|
2866
|
+
isolate: true
|
|
2867
|
+
});
|
|
2868
|
+
} else {
|
|
2869
|
+
// Update the flags in the `unmounted` list
|
|
2870
|
+
this._mergeUnmountedViewScheduledUpdates(cellView.cid, flag);
|
|
2871
|
+
}
|
|
2872
|
+
return cellView;
|
|
1950
2873
|
},
|
|
1951
2874
|
|
|
1952
2875
|
// Find all views at given point
|
|
@@ -1957,7 +2880,7 @@ export const Paper = View.extend({
|
|
|
1957
2880
|
var views = this.model.getElements().map(this.findViewByModel, this);
|
|
1958
2881
|
|
|
1959
2882
|
return views.filter(function(view) {
|
|
1960
|
-
return view && view.vel.getBBox({ target: this.
|
|
2883
|
+
return view && view.vel.getBBox({ target: this.layers }).containsPoint(p);
|
|
1961
2884
|
}, this);
|
|
1962
2885
|
},
|
|
1963
2886
|
|
|
@@ -1971,7 +2894,7 @@ export const Paper = View.extend({
|
|
|
1971
2894
|
var method = opt.strict ? 'containsRect' : 'intersect';
|
|
1972
2895
|
|
|
1973
2896
|
return views.filter(function(view) {
|
|
1974
|
-
return view && rect[method](view.vel.getBBox({ target: this.
|
|
2897
|
+
return view && rect[method](view.vel.getBBox({ target: this.layers }));
|
|
1975
2898
|
}, this);
|
|
1976
2899
|
},
|
|
1977
2900
|
|
|
@@ -2025,6 +2948,92 @@ export const Paper = View.extend({
|
|
|
2025
2948
|
);
|
|
2026
2949
|
},
|
|
2027
2950
|
|
|
2951
|
+
findClosestMagnetToPoint: function(point, options = {}) {
|
|
2952
|
+
let minDistance = Number.MAX_SAFE_INTEGER;
|
|
2953
|
+
let bestPriority = -Infinity;
|
|
2954
|
+
const pointer = new Point(point);
|
|
2955
|
+
|
|
2956
|
+
const radius = options.radius || Number.MAX_SAFE_INTEGER;
|
|
2957
|
+
const viewsInArea = this.findCellViewsInArea(
|
|
2958
|
+
{ x: pointer.x - radius, y: pointer.y - radius, width: 2 * radius, height: 2 * radius },
|
|
2959
|
+
options.findInAreaOptions
|
|
2960
|
+
);
|
|
2961
|
+
// Enable all connections by default
|
|
2962
|
+
const filterFn = typeof options.filter === 'function' ? options.filter : null;
|
|
2963
|
+
|
|
2964
|
+
let closestView = null;
|
|
2965
|
+
let closestMagnet = null;
|
|
2966
|
+
|
|
2967
|
+
// Note: If snapRadius is smaller than magnet size, views will not be found.
|
|
2968
|
+
viewsInArea.forEach((view) => {
|
|
2969
|
+
|
|
2970
|
+
const candidates = [];
|
|
2971
|
+
const { model } = view;
|
|
2972
|
+
// skip connecting to the element in case '.': { magnet: false } attribute present
|
|
2973
|
+
if (view.el.getAttribute('magnet') !== 'false') {
|
|
2974
|
+
|
|
2975
|
+
if (model.isLink()) {
|
|
2976
|
+
const connection = view.getConnection();
|
|
2977
|
+
candidates.push({
|
|
2978
|
+
// find distance from the closest point of a link to pointer coordinates
|
|
2979
|
+
priority: 0,
|
|
2980
|
+
distance: connection.closestPoint(pointer).squaredDistance(pointer),
|
|
2981
|
+
magnet: view.el
|
|
2982
|
+
});
|
|
2983
|
+
} else {
|
|
2984
|
+
candidates.push({
|
|
2985
|
+
// Set the priority to the level of nested elements of the model
|
|
2986
|
+
// To ensure that the embedded cells get priority over the parent cells
|
|
2987
|
+
priority: model.getAncestors().length,
|
|
2988
|
+
// find distance from the center of the model to pointer coordinates
|
|
2989
|
+
distance: model.getBBox().center().squaredDistance(pointer),
|
|
2990
|
+
magnet: view.el
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
view.$('[magnet]').toArray().forEach(magnet => {
|
|
2996
|
+
|
|
2997
|
+
const magnetBBox = view.getNodeBBox(magnet);
|
|
2998
|
+
let magnetDistance = magnetBBox.pointNearestToPoint(pointer).squaredDistance(pointer);
|
|
2999
|
+
if (magnetBBox.containsPoint(pointer)) {
|
|
3000
|
+
// Pointer sits inside this magnet.
|
|
3001
|
+
// Push its distance far into the negative range so any
|
|
3002
|
+
// "under-pointer" magnet outranks magnets that are only nearby
|
|
3003
|
+
// (positive distance) and every non-magnet candidate.
|
|
3004
|
+
// We add the original distance back to keep ordering among
|
|
3005
|
+
// overlapping magnets: the one whose border is closest to the
|
|
3006
|
+
// pointer (smaller original distance) still wins.
|
|
3007
|
+
magnetDistance = -Number.MAX_SAFE_INTEGER + magnetDistance;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
// Check if magnet is inside the snap radius.
|
|
3011
|
+
if (magnetDistance <= radius * radius) {
|
|
3012
|
+
candidates.push({
|
|
3013
|
+
// Give magnets priority over other candidates.
|
|
3014
|
+
priority: Number.MAX_SAFE_INTEGER,
|
|
3015
|
+
distance: magnetDistance,
|
|
3016
|
+
magnet
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
candidates.forEach(candidate => {
|
|
3022
|
+
const { magnet, distance, priority } = candidate;
|
|
3023
|
+
const isBetterCandidate = (priority > bestPriority) || (priority === bestPriority && distance < minDistance);
|
|
3024
|
+
if (isBetterCandidate && (!filterFn || filterFn(view, magnet))) {
|
|
3025
|
+
bestPriority = priority;
|
|
3026
|
+
minDistance = distance;
|
|
3027
|
+
closestView = view;
|
|
3028
|
+
closestMagnet = magnet;
|
|
3029
|
+
}
|
|
3030
|
+
});
|
|
3031
|
+
|
|
3032
|
+
});
|
|
3033
|
+
|
|
3034
|
+
return closestView ? { view: closestView, magnet: closestMagnet } : null;
|
|
3035
|
+
},
|
|
3036
|
+
|
|
2028
3037
|
_findInExtendedArea: function(area, findCellsFn, opt = {}) {
|
|
2029
3038
|
const {
|
|
2030
3039
|
buffer = this.DEFAULT_FIND_BUFFER,
|
|
@@ -2477,8 +3486,11 @@ export const Paper = View.extend({
|
|
|
2477
3486
|
|
|
2478
3487
|
var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
|
|
2479
3488
|
|
|
2480
|
-
|
|
3489
|
+
let view = data.sourceView;
|
|
2481
3490
|
if (view) {
|
|
3491
|
+
// The view could have been disposed during dragging
|
|
3492
|
+
// e.g. dragged outside of the viewport and hidden
|
|
3493
|
+
view = this.findViewByModel(view.model);
|
|
2482
3494
|
view.pointermove(evt, localPoint.x, localPoint.y);
|
|
2483
3495
|
} else {
|
|
2484
3496
|
this.trigger('blank:pointermove', evt, localPoint.x, localPoint.y);
|
|
@@ -2495,8 +3507,11 @@ export const Paper = View.extend({
|
|
|
2495
3507
|
|
|
2496
3508
|
var localPoint = this.snapToGrid(normalizedEvt.clientX, normalizedEvt.clientY);
|
|
2497
3509
|
|
|
2498
|
-
|
|
3510
|
+
let view = this.eventData(evt).sourceView;
|
|
2499
3511
|
if (view) {
|
|
3512
|
+
// The view could have been disposed during dragging
|
|
3513
|
+
// e.g. dragged outside of the viewport and hidden
|
|
3514
|
+
view = this.findViewByModel(view.model);
|
|
2500
3515
|
view.pointerup(normalizedEvt, localPoint.x, localPoint.y);
|
|
2501
3516
|
} else {
|
|
2502
3517
|
this.trigger('blank:pointerup', normalizedEvt, localPoint.x, localPoint.y);
|
|
@@ -2797,7 +3812,7 @@ export const Paper = View.extend({
|
|
|
2797
3812
|
return true;
|
|
2798
3813
|
}
|
|
2799
3814
|
|
|
2800
|
-
if (view && view.model && (view.model
|
|
3815
|
+
if (view && view.model && (view.model[CELL_MARKER])) {
|
|
2801
3816
|
return false;
|
|
2802
3817
|
}
|
|
2803
3818
|
|
|
@@ -2813,13 +3828,13 @@ export const Paper = View.extend({
|
|
|
2813
3828
|
options.gridSize = gridSize;
|
|
2814
3829
|
if (options.drawGrid && !options.drawGridSize) {
|
|
2815
3830
|
// Do not redraw the grid if the `drawGridSize` is set.
|
|
2816
|
-
this.getLayerView(
|
|
3831
|
+
this.getLayerView(paperLayers.GRID).renderGrid();
|
|
2817
3832
|
}
|
|
2818
3833
|
return this;
|
|
2819
3834
|
},
|
|
2820
3835
|
|
|
2821
3836
|
setGrid: function(drawGrid) {
|
|
2822
|
-
this.getLayerView(
|
|
3837
|
+
this.getLayerView(paperLayers.GRID).setGrid(drawGrid);
|
|
2823
3838
|
return this;
|
|
2824
3839
|
},
|
|
2825
3840
|
|
|
@@ -3168,202 +4183,8 @@ export const Paper = View.extend({
|
|
|
3168
4183
|
|
|
3169
4184
|
sorting: sortingTypes,
|
|
3170
4185
|
|
|
3171
|
-
Layers:
|
|
4186
|
+
Layers: paperLayers,
|
|
3172
4187
|
|
|
3173
|
-
backgroundPatterns
|
|
3174
|
-
|
|
3175
|
-
flipXy: function(img) {
|
|
3176
|
-
// d b
|
|
3177
|
-
// q p
|
|
3178
|
-
|
|
3179
|
-
var canvas = document.createElement('canvas');
|
|
3180
|
-
var imgWidth = img.width;
|
|
3181
|
-
var imgHeight = img.height;
|
|
3182
|
-
|
|
3183
|
-
canvas.width = 2 * imgWidth;
|
|
3184
|
-
canvas.height = 2 * imgHeight;
|
|
3185
|
-
|
|
3186
|
-
var ctx = canvas.getContext('2d');
|
|
3187
|
-
// top-left image
|
|
3188
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3189
|
-
// xy-flipped bottom-right image
|
|
3190
|
-
ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
|
|
3191
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3192
|
-
// x-flipped top-right image
|
|
3193
|
-
ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
|
|
3194
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3195
|
-
// y-flipped bottom-left image
|
|
3196
|
-
ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
|
|
3197
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3198
|
-
|
|
3199
|
-
return canvas;
|
|
3200
|
-
},
|
|
3201
|
-
|
|
3202
|
-
flipX: function(img) {
|
|
3203
|
-
// d b
|
|
3204
|
-
// d b
|
|
3205
|
-
|
|
3206
|
-
var canvas = document.createElement('canvas');
|
|
3207
|
-
var imgWidth = img.width;
|
|
3208
|
-
var imgHeight = img.height;
|
|
3209
|
-
|
|
3210
|
-
canvas.width = imgWidth * 2;
|
|
3211
|
-
canvas.height = imgHeight;
|
|
3212
|
-
|
|
3213
|
-
var ctx = canvas.getContext('2d');
|
|
3214
|
-
// left image
|
|
3215
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3216
|
-
// flipped right image
|
|
3217
|
-
ctx.translate(2 * imgWidth, 0);
|
|
3218
|
-
ctx.scale(-1, 1);
|
|
3219
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3220
|
-
|
|
3221
|
-
return canvas;
|
|
3222
|
-
},
|
|
3223
|
-
|
|
3224
|
-
flipY: function(img) {
|
|
3225
|
-
// d d
|
|
3226
|
-
// q q
|
|
3227
|
-
|
|
3228
|
-
var canvas = document.createElement('canvas');
|
|
3229
|
-
var imgWidth = img.width;
|
|
3230
|
-
var imgHeight = img.height;
|
|
3231
|
-
|
|
3232
|
-
canvas.width = imgWidth;
|
|
3233
|
-
canvas.height = imgHeight * 2;
|
|
3234
|
-
|
|
3235
|
-
var ctx = canvas.getContext('2d');
|
|
3236
|
-
// top image
|
|
3237
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3238
|
-
// flipped bottom image
|
|
3239
|
-
ctx.translate(0, 2 * imgHeight);
|
|
3240
|
-
ctx.scale(1, -1);
|
|
3241
|
-
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
|
|
3242
|
-
|
|
3243
|
-
return canvas;
|
|
3244
|
-
},
|
|
3245
|
-
|
|
3246
|
-
watermark: function(img, opt) {
|
|
3247
|
-
// d
|
|
3248
|
-
// d
|
|
3249
|
-
|
|
3250
|
-
opt = opt || {};
|
|
3251
|
-
|
|
3252
|
-
var imgWidth = img.width;
|
|
3253
|
-
var imgHeight = img.height;
|
|
3254
|
-
|
|
3255
|
-
var canvas = document.createElement('canvas');
|
|
3256
|
-
canvas.width = imgWidth * 3;
|
|
3257
|
-
canvas.height = imgHeight * 3;
|
|
3258
|
-
|
|
3259
|
-
var ctx = canvas.getContext('2d');
|
|
3260
|
-
var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
|
|
3261
|
-
var radians = toRad(angle);
|
|
3262
|
-
var stepX = canvas.width / 4;
|
|
3263
|
-
var stepY = canvas.height / 4;
|
|
3264
|
-
|
|
3265
|
-
for (var i = 0; i < 4; i++) {
|
|
3266
|
-
for (var j = 0; j < 4; j++) {
|
|
3267
|
-
if ((i + j) % 2 > 0) {
|
|
3268
|
-
// reset the current transformations
|
|
3269
|
-
ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
|
|
3270
|
-
ctx.rotate(radians);
|
|
3271
|
-
ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
|
|
3276
|
-
return canvas;
|
|
3277
|
-
}
|
|
3278
|
-
},
|
|
3279
|
-
|
|
3280
|
-
gridPatterns: {
|
|
3281
|
-
dot: [{
|
|
3282
|
-
color: '#AAAAAA',
|
|
3283
|
-
thickness: 1,
|
|
3284
|
-
markup: 'rect',
|
|
3285
|
-
render: function(el, opt) {
|
|
3286
|
-
V(el).attr({
|
|
3287
|
-
width: opt.thickness,
|
|
3288
|
-
height: opt.thickness,
|
|
3289
|
-
fill: opt.color
|
|
3290
|
-
});
|
|
3291
|
-
}
|
|
3292
|
-
}],
|
|
3293
|
-
fixedDot: [{
|
|
3294
|
-
color: '#AAAAAA',
|
|
3295
|
-
thickness: 1,
|
|
3296
|
-
markup: 'rect',
|
|
3297
|
-
render: function(el, opt) {
|
|
3298
|
-
V(el).attr({ fill: opt.color });
|
|
3299
|
-
},
|
|
3300
|
-
update: function(el, opt, paper) {
|
|
3301
|
-
const { sx, sy } = paper.scale();
|
|
3302
|
-
const width = sx <= 1 ? opt.thickness : opt.thickness / sx;
|
|
3303
|
-
const height = sy <= 1 ? opt.thickness : opt.thickness / sy;
|
|
3304
|
-
V(el).attr({ width, height });
|
|
3305
|
-
}
|
|
3306
|
-
}],
|
|
3307
|
-
mesh: [{
|
|
3308
|
-
color: '#AAAAAA',
|
|
3309
|
-
thickness: 1,
|
|
3310
|
-
markup: 'path',
|
|
3311
|
-
render: function(el, opt) {
|
|
3312
|
-
|
|
3313
|
-
var d;
|
|
3314
|
-
var width = opt.width;
|
|
3315
|
-
var height = opt.height;
|
|
3316
|
-
var thickness = opt.thickness;
|
|
3317
|
-
|
|
3318
|
-
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
3319
|
-
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
3320
|
-
} else {
|
|
3321
|
-
d = 'M 0 0 0 0';
|
|
3322
|
-
}
|
|
3323
|
-
|
|
3324
|
-
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
3325
|
-
}
|
|
3326
|
-
}],
|
|
3327
|
-
doubleMesh: [{
|
|
3328
|
-
color: '#AAAAAA',
|
|
3329
|
-
thickness: 1,
|
|
3330
|
-
markup: 'path',
|
|
3331
|
-
render: function(el, opt) {
|
|
3332
|
-
|
|
3333
|
-
var d;
|
|
3334
|
-
var width = opt.width;
|
|
3335
|
-
var height = opt.height;
|
|
3336
|
-
var thickness = opt.thickness;
|
|
3337
|
-
|
|
3338
|
-
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
3339
|
-
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
3340
|
-
} else {
|
|
3341
|
-
d = 'M 0 0 0 0';
|
|
3342
|
-
}
|
|
3343
|
-
|
|
3344
|
-
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
3345
|
-
}
|
|
3346
|
-
}, {
|
|
3347
|
-
color: '#000000',
|
|
3348
|
-
thickness: 3,
|
|
3349
|
-
scaleFactor: 4,
|
|
3350
|
-
markup: 'path',
|
|
3351
|
-
render: function(el, opt) {
|
|
3352
|
-
|
|
3353
|
-
var d;
|
|
3354
|
-
var width = opt.width;
|
|
3355
|
-
var height = opt.height;
|
|
3356
|
-
var thickness = opt.thickness;
|
|
3357
|
-
|
|
3358
|
-
if (width - thickness >= 0 && height - thickness >= 0) {
|
|
3359
|
-
d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
|
|
3360
|
-
} else {
|
|
3361
|
-
d = 'M 0 0 0 0';
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
|
|
3365
|
-
}
|
|
3366
|
-
}]
|
|
3367
|
-
}
|
|
4188
|
+
backgroundPatterns,
|
|
4189
|
+
gridPatterns,
|
|
3368
4190
|
});
|
|
3369
|
-
|