@joint/core 4.2.0-alpha.1 → 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/dist/geometry.js +1 -1
- package/dist/geometry.min.js +2 -3
- package/dist/joint.d.ts +321 -55
- package/dist/joint.js +2492 -853
- package/dist/joint.min.js +2 -3
- package/dist/joint.nowrap.js +2492 -853
- package/dist/joint.nowrap.min.js +2 -3
- package/dist/vectorizer.js +1 -1
- package/dist/vectorizer.min.js +2 -3
- package/dist/version.mjs +1 -1
- package/package.json +7 -7
- package/src/config/index.mjs +3 -1
- package/src/dia/Cell.mjs +77 -80
- package/src/dia/CellCollection.mjs +136 -0
- package/src/dia/CellView.mjs +1 -2
- package/src/dia/Element.mjs +2 -3
- 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/{PaperLayer.mjs → LayerView.mjs} +52 -17
- package/src/dia/LegacyGraphLayerView.mjs +14 -0
- package/src/dia/Paper.mjs +756 -423
- package/src/dia/ToolsView.mjs +3 -3
- package/src/dia/index.mjs +6 -1
- package/src/dia/ports.mjs +11 -2
- package/src/dia/symbols.mjs +24 -0
- package/src/mvc/Collection.mjs +19 -19
- package/src/mvc/Model.mjs +13 -10
- package/types/joint.d.ts +320 -54
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Model } from '../mvc/index.mjs';
|
|
2
|
+
import { CellCollection } from './CellCollection.mjs';
|
|
3
|
+
import { GRAPH_LAYER_MARKER } from './symbols.mjs';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_GRAPH_LAYER_TYPE = 'GraphLayer';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @class GraphLayer
|
|
9
|
+
* @description A GraphLayer is a model representing a single layer in a dia.Graph.
|
|
10
|
+
*/
|
|
11
|
+
export class GraphLayer extends Model {
|
|
12
|
+
|
|
13
|
+
[GRAPH_LAYER_MARKER] = true;
|
|
14
|
+
|
|
15
|
+
preinitialize() {
|
|
16
|
+
// This allows for propagating events from the inner `cellCollection` collection
|
|
17
|
+
// without any prefix and therefore distinguish them from the events
|
|
18
|
+
// fired by the GraphLayer model itself.
|
|
19
|
+
this.eventPrefix = 'layer:';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
defaults() {
|
|
23
|
+
return {
|
|
24
|
+
type: DEFAULT_GRAPH_LAYER_TYPE
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
initialize(attrs, options = {}) {
|
|
29
|
+
super.initialize(attrs, options);
|
|
30
|
+
|
|
31
|
+
this.cellCollection = new CellCollection([], {
|
|
32
|
+
layer: this
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Forward all events from the inner `cellCollection` collection
|
|
36
|
+
this.cellCollection.on('all', this.trigger, this);
|
|
37
|
+
// Listen to cell changes to manage z-index sorting
|
|
38
|
+
this.cellCollection.on('change', this.onCellChange, this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onCellChange(cell, opt) {
|
|
42
|
+
if (opt.sort === false || !cell.hasChanged('z')) return;
|
|
43
|
+
this.cellCollection.sort();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @public
|
|
48
|
+
* @description Returns all cells in this layer.
|
|
49
|
+
*/
|
|
50
|
+
getCells() {
|
|
51
|
+
return this.cellCollection.toArray();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { Collection } from '../mvc/index.mjs';
|
|
2
|
+
import { DEFAULT_GRAPH_LAYER_TYPE, GraphLayer } from './GraphLayer.mjs';
|
|
3
|
+
import { CELL_MARKER, GRAPH_LAYER_MARKER, GRAPH_LAYER_COLLECTION_MARKER } from './symbols.mjs';
|
|
4
|
+
import * as util from '../util/index.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @class GraphLayerCollection
|
|
8
|
+
* @description A collection of layers used in dia.Graph. It facilitates creating layers from JSON using layerNamespace.
|
|
9
|
+
*/
|
|
10
|
+
export const GraphLayerCollection = Collection.extend({
|
|
11
|
+
|
|
12
|
+
defaultLayerNamespace: {
|
|
13
|
+
GraphLayer
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @override
|
|
18
|
+
* @description Initializes the collection and sets up the layer and cell namespaces.
|
|
19
|
+
*/
|
|
20
|
+
initialize: function(_models, options = {}) {
|
|
21
|
+
const { layerNamespace, cellNamespace, graph } = options;
|
|
22
|
+
|
|
23
|
+
// Initialize the namespace that holds all available layer classes.
|
|
24
|
+
// Custom namespaces are merged with the default ones.
|
|
25
|
+
this.layerNamespace = util.assign({}, this.defaultLayerNamespace, layerNamespace);
|
|
26
|
+
|
|
27
|
+
// Initialize the namespace for all cell model classes, if provided.
|
|
28
|
+
if (cellNamespace) {
|
|
29
|
+
this.cellNamespace = cellNamespace;
|
|
30
|
+
} else {
|
|
31
|
+
/* eslint-disable no-undef */
|
|
32
|
+
this.cellNamespace = typeof joint !== 'undefined' && util.has(joint, 'shapes') ? joint.shapes : null;
|
|
33
|
+
/* eslint-enable no-undef */
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.graph = graph;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @override
|
|
41
|
+
* @description Overrides the default `model` method
|
|
42
|
+
* to create layer models based on their `type` attribute.
|
|
43
|
+
*/
|
|
44
|
+
model: function(attrs, opt) {
|
|
45
|
+
|
|
46
|
+
const collection = opt.collection;
|
|
47
|
+
const namespace = collection.layerNamespace;
|
|
48
|
+
const { type } = attrs;
|
|
49
|
+
|
|
50
|
+
// Find the model class based on the `type` attribute in the cell namespace
|
|
51
|
+
const GraphLayerClass = util.getByPath(namespace, type, '.');
|
|
52
|
+
if (!GraphLayerClass) {
|
|
53
|
+
throw new Error(`dia.Graph: Could not find layer constructor for type: '${type}'. Make sure to add the constructor to 'layerNamespace'.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new GraphLayerClass(attrs, opt);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Override to set graph reference
|
|
60
|
+
_addReference(layer, options) {
|
|
61
|
+
Collection.prototype._addReference.call(this, layer, options);
|
|
62
|
+
|
|
63
|
+
// assign graph and cellNamespace references
|
|
64
|
+
// to the added layer
|
|
65
|
+
layer.graph = this.graph;
|
|
66
|
+
layer.cellCollection.cellNamespace = this.cellNamespace;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Override to remove graph reference
|
|
70
|
+
_removeReference(layer, options) {
|
|
71
|
+
Collection.prototype._removeReference.call(this, layer, options);
|
|
72
|
+
|
|
73
|
+
// remove graph and cellNamespace references
|
|
74
|
+
// from the removed layer
|
|
75
|
+
layer.graph = null;
|
|
76
|
+
layer.cellCollection.cellNamespace = null;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @override
|
|
81
|
+
* @description Overrides the default `_prepareModel` method
|
|
82
|
+
* to set default layer type if missing.
|
|
83
|
+
*/
|
|
84
|
+
_prepareModel: function(attrs, options) {
|
|
85
|
+
if (!attrs[GRAPH_LAYER_MARKER]) {
|
|
86
|
+
// Add a mandatory `type` attribute if missing
|
|
87
|
+
if (!attrs.type) {
|
|
88
|
+
const preparedAttributes = util.clone(attrs);
|
|
89
|
+
preparedAttributes.type = DEFAULT_GRAPH_LAYER_TYPE;
|
|
90
|
+
arguments[0] = preparedAttributes;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Collection.prototype._prepareModel.apply(this, arguments);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @override
|
|
99
|
+
* @description Add an assertion to prevent direct resetting of the collection.
|
|
100
|
+
*/
|
|
101
|
+
reset(models, options) {
|
|
102
|
+
this._assertInternalCall(options);
|
|
103
|
+
return Collection.prototype.reset.apply(this, arguments);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @override
|
|
108
|
+
* @description Add an assertion to prevent direct addition of layers.
|
|
109
|
+
*/
|
|
110
|
+
add(models, options) {
|
|
111
|
+
this._assertInternalCall(options);
|
|
112
|
+
return Collection.prototype.add.apply(this, arguments);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @override
|
|
117
|
+
* @description Add an assertion to prevent direct removal of layers.
|
|
118
|
+
*/
|
|
119
|
+
remove(models, options){
|
|
120
|
+
this._assertInternalCall(options);
|
|
121
|
+
return Collection.prototype.remove.apply(this, arguments);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @override
|
|
126
|
+
* @description Overrides the default `_onModelEvent` method
|
|
127
|
+
* to distinguish between events coming from different model types.
|
|
128
|
+
*/
|
|
129
|
+
_onModelEvent(_, model) {
|
|
130
|
+
if (model && model[CELL_MARKER]) {
|
|
131
|
+
// Do not filter cell `add` and `remove` events
|
|
132
|
+
// See `mvc.Collection` for more details
|
|
133
|
+
this.trigger.apply(this, arguments);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// For other events, use the default behavior
|
|
138
|
+
Collection.prototype._onModelEvent.apply(this, arguments);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @protected
|
|
143
|
+
* @description Asserts that the collection manipulation
|
|
144
|
+
* is done via internal graph methods. Otherwise, it throws an error.
|
|
145
|
+
* This is a temporary measure until layers API is stabilized.
|
|
146
|
+
*/
|
|
147
|
+
_assertInternalCall(options) {
|
|
148
|
+
if (options && !options.graph && !options.silent) {
|
|
149
|
+
throw new Error('dia.GraphLayerCollection: direct manipulation of the collection is not supported, use graph methods instead.');
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @public
|
|
155
|
+
* @description Inserts a layer before another layer or at the end if `beforeLayerId` is null.
|
|
156
|
+
*/
|
|
157
|
+
insert(layerInit, beforeLayerId = null, options = {}) {
|
|
158
|
+
|
|
159
|
+
const id = layerInit.id;
|
|
160
|
+
if (id === beforeLayerId) {
|
|
161
|
+
// Inserting before itself is a no-op
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (beforeLayerId && !this.has(beforeLayerId)) {
|
|
166
|
+
throw new Error(`dia.GraphLayerCollection: Layer "${beforeLayerId}" does not exist`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// See if the layer is already in the collection
|
|
170
|
+
let currentIndex = -1;
|
|
171
|
+
if (this.has(id)) {
|
|
172
|
+
currentIndex = this.findIndex(l => l.id === id);
|
|
173
|
+
if ((currentIndex === this.length - 1) && !beforeLayerId) {
|
|
174
|
+
// The layer is already at the end
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Remove the layer from its current position
|
|
178
|
+
this.remove(id, { silent: true });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// At what index to insert the layer?
|
|
182
|
+
let insertAt;
|
|
183
|
+
if (!beforeLayerId) {
|
|
184
|
+
insertAt = this.length;
|
|
185
|
+
} else {
|
|
186
|
+
insertAt = this.findIndex(l => l.id === beforeLayerId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (currentIndex !== -1) {
|
|
190
|
+
// Re-insert the layer at the new position.
|
|
191
|
+
this.add(layerInit, {
|
|
192
|
+
at: insertAt,
|
|
193
|
+
silent: true
|
|
194
|
+
});
|
|
195
|
+
// Trigger `sort` event manually
|
|
196
|
+
// since we are not using collection sorting workflow
|
|
197
|
+
this.trigger('sort', this, options);
|
|
198
|
+
} else {
|
|
199
|
+
// Add to the collection and trigger an event
|
|
200
|
+
// when new layer has been added
|
|
201
|
+
this.add(layerInit, {
|
|
202
|
+
...options,
|
|
203
|
+
at: insertAt,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @public
|
|
210
|
+
* @description Finds and returns a cell by its id from all layers.
|
|
211
|
+
*/
|
|
212
|
+
getCell(cellRef) {
|
|
213
|
+
// TODO: should we create a map of cells for faster lookup?
|
|
214
|
+
for (const layer of this.models) {
|
|
215
|
+
const cell = layer.cellCollection.get(cellRef);
|
|
216
|
+
if (cell) {
|
|
217
|
+
return cell;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Backward compatibility: return undefined if cell is not found
|
|
221
|
+
return undefined;
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @public
|
|
226
|
+
* @description Returns all cells in all layers in the correct order.
|
|
227
|
+
*/
|
|
228
|
+
getCells() {
|
|
229
|
+
const layers = this.models;
|
|
230
|
+
if (layers.length === 1) {
|
|
231
|
+
// Single layer:
|
|
232
|
+
// Fast path, just return the copy of the only layer's cells
|
|
233
|
+
return layers[0].getCells();
|
|
234
|
+
}
|
|
235
|
+
// Multiple layers:
|
|
236
|
+
// Each layer has its models sorted already, so we can just concatenate
|
|
237
|
+
// them in the order of layers.
|
|
238
|
+
const cells = [];
|
|
239
|
+
for (const layer of layers) {
|
|
240
|
+
Array.prototype.push.apply(cells, layer.cellCollection.models);
|
|
241
|
+
}
|
|
242
|
+
return cells;
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @public
|
|
247
|
+
* @description Removes a cell from its current layer.
|
|
248
|
+
*/
|
|
249
|
+
removeCell(cell, options = {}) {
|
|
250
|
+
const cellCollection = cell.collection?.layer?.cellCollection;
|
|
251
|
+
if (!cellCollection) return;
|
|
252
|
+
cellCollection.remove(cell, options);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @public
|
|
257
|
+
* @description Move a cell from its current layer to a target layer.
|
|
258
|
+
*/
|
|
259
|
+
moveCellBetweenLayers(cell, targetLayerId, options = {}) {
|
|
260
|
+
|
|
261
|
+
const sourceLayer = cell.collection?.layer;
|
|
262
|
+
if (!sourceLayer) {
|
|
263
|
+
throw new Error('dia.GraphLayerCollection: cannot move a cell that is not part of any layer.');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const targetLayer = this.get(targetLayerId);
|
|
267
|
+
if (!targetLayer) {
|
|
268
|
+
throw new Error(`dia.GraphLayerCollection: cannot move cell to layer '${targetLayerId}' because such layer does not exist.`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (sourceLayer === targetLayer) {
|
|
272
|
+
// 1. The provided cell is already in the target layer
|
|
273
|
+
// 2. Implicit default layer vs. explicit default (or vice versa)
|
|
274
|
+
// No follow-up action needed
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const moveOptions = {
|
|
279
|
+
...options,
|
|
280
|
+
fromLayer: sourceLayer.id,
|
|
281
|
+
toLayer: targetLayer.id
|
|
282
|
+
};
|
|
283
|
+
// Move the cell between the two layer collections
|
|
284
|
+
sourceLayer.cellCollection.remove(cell, moveOptions);
|
|
285
|
+
targetLayer.cellCollection.add(cell, moveOptions);
|
|
286
|
+
// Trigger a single `move` event to ease distinguishing layer moves
|
|
287
|
+
// from add/remove operations
|
|
288
|
+
cell.trigger('move', cell, moveOptions);
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @public
|
|
293
|
+
* @description Adds a cell to the specified layer.
|
|
294
|
+
*/
|
|
295
|
+
addCellToLayer(cell, layerId, options = {}) {
|
|
296
|
+
const targetLayer = this.get(layerId);
|
|
297
|
+
if (!targetLayer) {
|
|
298
|
+
throw new Error(`dia.GraphLayerCollection: layer "${layerId}" does not exist.`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const addOptions = {
|
|
302
|
+
...options,
|
|
303
|
+
toLayer: targetLayer.id
|
|
304
|
+
};
|
|
305
|
+
// Add the cell to the target layer collection
|
|
306
|
+
targetLayer.cellCollection.add(cell, addOptions);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
Object.defineProperty(GraphLayerCollection.prototype, GRAPH_LAYER_COLLECTION_MARKER, {
|
|
312
|
+
value: true,
|
|
313
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { LayerView } from './LayerView.mjs';
|
|
2
|
+
import { sortElements } from '../util/index.mjs';
|
|
3
|
+
import { addClassNamePrefix } from '../util/util.mjs';
|
|
4
|
+
import { sortingTypes } from './Paper.mjs';
|
|
5
|
+
import { GRAPH_LAYER_VIEW_MARKER } from './symbols.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @class GraphLayerView
|
|
9
|
+
* @description A GraphLayerView is responsible for managing the rendering of cell views inside a layer.
|
|
10
|
+
* It listens to the corresponding GraphLayer model and updates the DOM accordingly.
|
|
11
|
+
* It uses dia.Paper sorting options to sort cell views in the DOM based on their `z` attribute.
|
|
12
|
+
*/
|
|
13
|
+
export const GraphLayerView = LayerView.extend({
|
|
14
|
+
|
|
15
|
+
SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
|
|
16
|
+
|
|
17
|
+
style: {
|
|
18
|
+
webkitUserSelect: 'none',
|
|
19
|
+
userSelect: 'none'
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
graph: null,
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
LayerView.prototype.init.apply(this, arguments);
|
|
26
|
+
this.graph = this.model.graph;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
className: function() {
|
|
30
|
+
const { id } = this.options;
|
|
31
|
+
return [
|
|
32
|
+
addClassNamePrefix(`${id}-layer`),
|
|
33
|
+
addClassNamePrefix('cells')
|
|
34
|
+
].join(' ');
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
afterPaperReferenceSet(paper) {
|
|
38
|
+
this.listenTo(this.model, 'sort', this.onCellCollectionSort);
|
|
39
|
+
this.listenTo(this.model, 'change', this.onCellChange);
|
|
40
|
+
this.listenTo(this.model, 'move', this.onCellMove);
|
|
41
|
+
this.listenTo(this.graph, 'batch:stop', this.onGraphBatchStop);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
beforePaperReferenceUnset() {
|
|
45
|
+
this.stopListening(this.model);
|
|
46
|
+
this.stopListening(this.graph);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
onCellCollectionSort() {
|
|
50
|
+
if (this.graph.hasActiveBatch(this.SORT_DELAYING_BATCHES)) return;
|
|
51
|
+
this.sort();
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
onCellMove(cell, opt = {}) {
|
|
55
|
+
// When a cell is moved from one layer to another,
|
|
56
|
+
// request insertion of its view in the new layer.
|
|
57
|
+
this.paper.requestCellViewInsertion(cell, opt);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
onCellChange(cell, opt) {
|
|
61
|
+
if (!cell.hasChanged('z')) return;
|
|
62
|
+
// Re-insert the cell view to maintain correct z-ordering
|
|
63
|
+
if (this.paper.options.sorting === sortingTypes.APPROX) {
|
|
64
|
+
this.paper.requestCellViewInsertion(cell, opt);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
onGraphBatchStop(data) {
|
|
69
|
+
const name = data && data.batchName;
|
|
70
|
+
const sortDelayingBatches = this.SORT_DELAYING_BATCHES;
|
|
71
|
+
// After certain batches, sorting may be required
|
|
72
|
+
if (sortDelayingBatches.includes(name) && !this.graph.hasActiveBatch(sortDelayingBatches)) {
|
|
73
|
+
this.sort();
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
sort() {
|
|
78
|
+
this.assertPaperReference();
|
|
79
|
+
const { paper } = this;
|
|
80
|
+
|
|
81
|
+
if (!paper.isExactSorting()) {
|
|
82
|
+
// noop
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (paper.isFrozen()) {
|
|
86
|
+
// sort views once unfrozen
|
|
87
|
+
paper._updates.sort = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.sortExact();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
sortExact() {
|
|
94
|
+
// Run insertion sort algorithm in order to efficiently sort DOM elements according to their
|
|
95
|
+
// associated model `z` attribute.
|
|
96
|
+
const cellNodes = Array.from(this.el.children).filter(node => node.getAttribute('model-id'));
|
|
97
|
+
const cellCollection = this.model.cellCollection;
|
|
98
|
+
|
|
99
|
+
sortElements(cellNodes, function(a, b) {
|
|
100
|
+
const cellA = cellCollection.get(a.getAttribute('model-id'));
|
|
101
|
+
const cellB = cellCollection.get(b.getAttribute('model-id'));
|
|
102
|
+
const zA = cellA.attributes.z || 0;
|
|
103
|
+
const zB = cellB.attributes.z || 0;
|
|
104
|
+
return (zA === zB) ? 0 : (zA < zB) ? -1 : 1;
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
insertCellView(cellView) {
|
|
109
|
+
this.assertPaperReference();
|
|
110
|
+
const { paper } = this;
|
|
111
|
+
const { el, model } = cellView;
|
|
112
|
+
|
|
113
|
+
switch (paper.options.sorting) {
|
|
114
|
+
case sortingTypes.APPROX:
|
|
115
|
+
this.insertSortedNode(el, model.get('z'));
|
|
116
|
+
break;
|
|
117
|
+
case sortingTypes.EXACT:
|
|
118
|
+
default:
|
|
119
|
+
this.insertNode(el);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
Object.defineProperty(GraphLayerView.prototype, GRAPH_LAYER_VIEW_MARKER, {
|
|
127
|
+
value: true,
|
|
128
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Listener } from '../mvc/Listener.mjs';
|
|
2
|
+
import { config } from '../config/index.mjs';
|
|
3
|
+
import { CELL_MARKER, CELL_COLLECTION_MARKER, GRAPH_LAYER_MARKER, GRAPH_LAYER_COLLECTION_MARKER } from './symbols.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @class GraphLayersController
|
|
7
|
+
* @description Coordinates interactions between the graph and its layers.
|
|
8
|
+
* Automatically moves cells between layers when the layer attribute changes.
|
|
9
|
+
*/
|
|
10
|
+
export class GraphLayersController extends Listener {
|
|
11
|
+
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options);
|
|
14
|
+
|
|
15
|
+
// Make sure there are no arguments passed to the callbacks.
|
|
16
|
+
// See the `mvc.Listener` documentation for more details.
|
|
17
|
+
this.callbackArguments = [];
|
|
18
|
+
|
|
19
|
+
const graph = options.graph;
|
|
20
|
+
if (!graph) {
|
|
21
|
+
throw new Error('GraphLayersController: "graph" option is required.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.graph = graph;
|
|
25
|
+
this.layerCollection = graph.layerCollection;
|
|
26
|
+
|
|
27
|
+
this.startListening();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
startListening() {
|
|
31
|
+
// Handle all events from the layer collection and its inner cell collections.
|
|
32
|
+
this.listenTo(this.layerCollection, 'all', this.onLayerCollectionEvent);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @description When a cell changes its layer attribute,
|
|
37
|
+
* move the cell to the target layer.
|
|
38
|
+
*/
|
|
39
|
+
onCellChange(cell, options) {
|
|
40
|
+
if (!cell.hasChanged(config.layerAttribute)) return;
|
|
41
|
+
// Move the cell to the appropriate layer
|
|
42
|
+
const targetLayerId = this.graph.getCellLayerId(cell);
|
|
43
|
+
this.layerCollection.moveCellBetweenLayers(cell, targetLayerId, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @description When a cell is removed from a layer,
|
|
48
|
+
* also remove its embeds and connected links from the graph.
|
|
49
|
+
* Note: an embedded cell might come from a different layer,
|
|
50
|
+
* so we can not use the layer's cell collection to remove it.
|
|
51
|
+
*/
|
|
52
|
+
onCellRemove(cell, options) {
|
|
53
|
+
// If the cell is being moved from one layer to another,
|
|
54
|
+
// no further action is needed.
|
|
55
|
+
if (options.fromLayer) return;
|
|
56
|
+
|
|
57
|
+
// When replacing a cell, we do not want to remove its embeds or
|
|
58
|
+
// unembed it from its parent.
|
|
59
|
+
if (options.replace) return;
|
|
60
|
+
|
|
61
|
+
// First, unembed this cell from its parent cell if there is one.
|
|
62
|
+
const parentCell = cell.getParentCell();
|
|
63
|
+
if (parentCell) {
|
|
64
|
+
parentCell.unembed(cell, options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove also all the cells, which were embedded into this cell
|
|
68
|
+
const embeddedCells = cell.getEmbeddedCells();
|
|
69
|
+
for (let i = 0, n = embeddedCells.length; i < n; i++) {
|
|
70
|
+
const embed = embeddedCells[i];
|
|
71
|
+
if (embed) {
|
|
72
|
+
this.layerCollection.removeCell(embed, options);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// When not clearing the whole graph or replacing the cell,
|
|
77
|
+
// we don't want to remove the connected links.
|
|
78
|
+
if (!options.clear) {
|
|
79
|
+
|
|
80
|
+
// Applications might provide a `disconnectLinks` option set to `true` in order to
|
|
81
|
+
// disconnect links when a cell is removed rather then removing them. The default
|
|
82
|
+
// is to remove all the associated links.
|
|
83
|
+
if (options.disconnectLinks) {
|
|
84
|
+
this.graph.disconnectLinks(cell, options);
|
|
85
|
+
} else {
|
|
86
|
+
this.graph.removeLinks(cell, options);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onLayerCollectionEvent(eventName, model) {
|
|
92
|
+
if (!model) return;
|
|
93
|
+
|
|
94
|
+
if (model[CELL_MARKER]) {
|
|
95
|
+
// First handle cell-specific cases that require custom processing,
|
|
96
|
+
// then forward the event to the graph.
|
|
97
|
+
// For example, when a cell is removed from a layer, its embeds and
|
|
98
|
+
// connected links must be removed as well. Listeners on the graph
|
|
99
|
+
// should receive removal notifications in the following order:
|
|
100
|
+
// embeds → links → cell.
|
|
101
|
+
switch (eventName) {
|
|
102
|
+
case 'change': /* ('change', cell, options) */
|
|
103
|
+
this.onCellChange.call(this, model, arguments[2]);
|
|
104
|
+
break;
|
|
105
|
+
case 'remove': /* ('remove', cell, collection, options) */
|
|
106
|
+
// When a cell is removed from a layer,
|
|
107
|
+
// ensure it is also removed from the graph.
|
|
108
|
+
this.onCellRemove.call(this, model, arguments[3]);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
// Notify the graph about cell events.
|
|
112
|
+
this.forwardCellEvent.apply(this, arguments);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (model[CELL_COLLECTION_MARKER]) {
|
|
117
|
+
this.forwardCellCollectionEvent.apply(this, arguments);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (model[GRAPH_LAYER_MARKER]) {
|
|
122
|
+
this.forwardLayerEvent.apply(this, arguments);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (model[GRAPH_LAYER_COLLECTION_MARKER]) {
|
|
127
|
+
this.forwardLayerCollectionEvent.apply(this, arguments);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
forwardLayerEvent() {
|
|
133
|
+
// Note: the layer event prefix is `layer:`
|
|
134
|
+
this.graph.trigger.apply(this.graph, arguments);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
forwardCellEvent(eventName, cell) {
|
|
138
|
+
// Moving a cell from one layer to another is an internal operation
|
|
139
|
+
// that should not be exposed at the graph level.
|
|
140
|
+
// The single `move` event is triggered instead.
|
|
141
|
+
if ((eventName === 'remove' || eventName === 'add') && arguments[3]?.fromLayer) return;
|
|
142
|
+
|
|
143
|
+
this.graph.trigger.apply(this.graph, arguments);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
forwardCellCollectionEvent(eventName) {
|
|
147
|
+
// Do not forward `layer:remove` or `layer:sort` events to the graph
|
|
148
|
+
if (eventName !== 'sort') return;
|
|
149
|
+
// Backwards compatibility:
|
|
150
|
+
// Trigger 'sort' event for cell collection 'sort' events
|
|
151
|
+
this.graph.trigger.apply(this.graph, arguments);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
forwardLayerCollectionEvent(eventName) {
|
|
155
|
+
if (eventName === 'reset') {
|
|
156
|
+
// Currently, there is no need to forward `layers:reset` event.
|
|
157
|
+
// The graph `fromJSON()` triggers a single `reset` event after
|
|
158
|
+
// resetting cells, layers and attributes.
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Forward layer collection events with `layers:` prefix.
|
|
162
|
+
// For example `layers:reset` event when the layer collection is reset
|
|
163
|
+
arguments[0] = 'layers:' + arguments[0];
|
|
164
|
+
this.graph.trigger.apply(this.graph, arguments);
|
|
165
|
+
}
|
|
166
|
+
}
|