@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.
@@ -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
+ }