@vcmap/core 5.0.0-rc.2 → 5.0.0-rc.5

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.
Files changed (51) hide show
  1. package/index.d.ts +287 -95
  2. package/index.js +11 -3
  3. package/package.json +5 -10
  4. package/src/vcs/vcm/category/appBackedCategory.js +41 -0
  5. package/src/vcs/vcm/category/category.js +374 -0
  6. package/src/vcs/vcm/category/categoryCollection.js +145 -0
  7. package/src/vcs/vcm/context.js +73 -0
  8. package/src/vcs/vcm/interaction/coordinateAtPixel.js +1 -1
  9. package/src/vcs/vcm/layer/cesiumTileset.js +1 -1
  10. package/src/vcs/vcm/layer/featureStore.js +3 -3
  11. package/src/vcs/vcm/layer/featureStoreChanges.js +89 -73
  12. package/src/vcs/vcm/layer/geojson.js +3 -5
  13. package/src/vcs/vcm/layer/geojsonHelpers.js +3 -3
  14. package/src/vcs/vcm/layer/oblique/obliqueHelpers.js +2 -2
  15. package/src/vcs/vcm/layer/singleImage.js +2 -1
  16. package/src/vcs/vcm/layer/terrainHelpers.js +4 -8
  17. package/src/vcs/vcm/layer/tileProvider/mvtTileProvider.js +3 -3
  18. package/src/vcs/vcm/layer/tileProvider/staticGeojsonTileProvider.js +3 -3
  19. package/src/vcs/vcm/layer/tileProvider/urlTemplateTileProvider.js +3 -3
  20. package/src/vcs/vcm/layer/vector.js +1 -1
  21. package/src/vcs/vcm/layer/vectorHelpers.js +4 -4
  22. package/src/vcs/vcm/layer/wfs.js +5 -5
  23. package/src/vcs/vcm/maps/cameraLimiter.js +5 -5
  24. package/src/vcs/vcm/maps/map.js +26 -11
  25. package/src/vcs/vcm/maps/oblique.js +8 -8
  26. package/src/vcs/vcm/object.js +1 -1
  27. package/src/vcs/vcm/oblique/ObliqueCollection.js +83 -31
  28. package/src/vcs/vcm/oblique/ObliqueDataSet.js +67 -24
  29. package/src/vcs/vcm/oblique/ObliqueImage.js +1 -1
  30. package/src/vcs/vcm/oblique/ObliqueImageMeta.js +2 -2
  31. package/src/vcs/vcm/oblique/ObliqueProvider.js +10 -7
  32. package/src/vcs/vcm/oblique/helpers.js +12 -40
  33. package/src/vcs/vcm/oblique/parseImageJson.js +17 -9
  34. package/src/vcs/vcm/util/clipping/clippingPlaneHelper.js +4 -4
  35. package/src/vcs/vcm/util/extent.js +16 -9
  36. package/src/vcs/vcm/util/featureProvider/featureProviderHelpers.js +3 -4
  37. package/src/vcs/vcm/util/featureProvider/wmsFeatureProvider.js +11 -6
  38. package/src/vcs/vcm/util/featureconverter/extent3D.js +181 -0
  39. package/src/vcs/vcm/util/fetch.js +32 -0
  40. package/src/vcs/vcm/util/indexedCollection.js +23 -0
  41. package/src/vcs/vcm/util/layerCollection.js +10 -5
  42. package/src/vcs/vcm/util/overrideCollection.js +224 -0
  43. package/src/vcs/vcm/util/projection.js +37 -3
  44. package/src/vcs/vcm/util/style/declarativeStyleItem.js +2 -0
  45. package/src/vcs/vcm/util/style/styleFactory.js +1 -1
  46. package/src/vcs/vcm/util/style/styleItem.js +2 -0
  47. package/src/vcs/vcm/util/style/vectorStyleItem.js +2 -0
  48. package/src/vcs/vcm/util/viewpoint.js +3 -0
  49. package/src/vcs/vcm/vcsApp.js +360 -0
  50. package/src/vcs/vcm/vcsAppContextHelpers.js +108 -0
  51. package/src/vcs/vcm/util/featureconverter/extent3d.js +0 -154
@@ -1,8 +1,10 @@
1
1
  import Projection from './projection.js';
2
2
 
3
3
  /**
4
- * @typedef {ProjectionOptions} ExtentOptions
5
- * @property {import("ol/extent").Extent|undefined} coordinates - if not specified, the extent of the projection is used
4
+ * @typedef {Object} ExtentOptions
5
+ * @property {string} [type]
6
+ * @property {import("ol/extent").Extent|undefined} [coordinates] - if not specified, the extent of the projection is used
7
+ * @property {ProjectionOptions} [projection] - if not specified the default projection is assumed
6
8
  * @api
7
9
  */
8
10
 
@@ -35,16 +37,17 @@ function checkExtentValidity(extent) {
35
37
  * @api
36
38
  */
37
39
  class Extent {
40
+ /**
41
+ * @type {string}
42
+ */
43
+ static get className() { return 'vcs.vcm.util.Extent'; }
44
+
38
45
  /**
39
46
  * @param {ExtentOptions=} options object
40
47
  */
41
48
  constructor(options = {}) {
42
49
  /** @type {Projection} */
43
- this.projection = new Projection({
44
- epsg: options.epsg,
45
- proj4: options.proj4,
46
- alias: options.alias,
47
- });
50
+ this.projection = new Projection(options.projection);
48
51
 
49
52
  /** @type {import("ol/extent").Extent|null} */
50
53
  this.extent = options.coordinates || this.projection.proj.getExtent();
@@ -79,7 +82,11 @@ class Extent {
79
82
  * @returns {ExtentOptions}
80
83
  */
81
84
  toJSON() {
82
- return { coordinates: this.extent.slice(), ...this.projection.toJSON() };
85
+ return {
86
+ coordinates: this.extent.slice(),
87
+ projection: this.projection.toJSON(),
88
+ type: Extent.className,
89
+ };
83
90
  }
84
91
 
85
92
  /**
@@ -112,7 +119,7 @@ class Extent {
112
119
  * @api
113
120
  */
114
121
  static validateOptions(options) {
115
- return Projection.validateOptions(options) && checkExtentValidity(options.coordinates);
122
+ return Projection.validateOptions(options.projection || {}) && checkExtentValidity(options.coordinates);
116
123
  }
117
124
 
118
125
  /**
@@ -1,6 +1,6 @@
1
1
  import { getCenter } from 'ol/extent.js';
2
2
  import Projection from '../projection.js';
3
- import { createOrUpdateFromGeometry } from '../featureconverter/extent3d.js';
3
+ import Extent3D from '../featureconverter/extent3D.js';
4
4
 
5
5
  /**
6
6
  * @param {VectorClickedObject} feature
@@ -32,9 +32,8 @@ export function getGenericFeatureFromProvidedFeature(feature, layer) {
32
32
 
33
33
  let heightOffset = clickedPosition.height;
34
34
  if (!isModel) {
35
- const extent = createOrUpdateFromGeometry(geometry);
36
- const max = Number.isFinite(extent[5]) ? extent[5] : 0;
37
- heightOffset = max;
35
+ const extent = Extent3D.fromGeometry(geometry);
36
+ heightOffset = Number.isFinite(extent.maxZ) ? extent.maxZ : 0;
38
37
  }
39
38
  const relativeToGround = !isModel && feature.get('olcs_altitudeMode') === 'relativeToGround';
40
39
 
@@ -1,4 +1,3 @@
1
- import axios from 'axios';
2
1
  import GML2 from 'ol/format/GML2.js';
3
2
  import WFS from 'ol/format/WFS.js';
4
3
  import GeoJSON from 'ol/format/GeoJSON.js';
@@ -10,6 +9,7 @@ import AbstractFeatureProvider from './abstractFeatureProvider.js';
10
9
  import Projection, { mercatorProjection } from '../projection.js';
11
10
  import { getWMSSource } from '../../layer/wmsHelpers.js';
12
11
  import Extent from '../extent.js';
12
+ import { requestJson } from '../fetch.js';
13
13
 
14
14
  /**
15
15
  * @typedef {AbstractFeatureProviderOptions} WMSFeatureProviderOptions
@@ -155,12 +155,11 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
155
155
  }
156
156
 
157
157
  /**
158
- * @param {import("axios").AxiosResponse<*>} response
158
+ * @param {import("ol/format/GeoJSON").GeoJSONObject} data
159
159
  * @param {import("ol/coordinate").Coordinate} coordinate
160
160
  * @returns {Array<import("ol").Feature<import("ol/geom/Geometry").default>>}
161
161
  */
162
- featureResponseCallback(response, coordinate) {
163
- const { data } = response;
162
+ featureResponseCallback(data, coordinate) {
164
163
  /** @type {Array<import("ol").Feature<import("ol/geom/Geometry").default>>} */
165
164
  let features;
166
165
 
@@ -211,8 +210,14 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
211
210
  );
212
211
 
213
212
  if (url) {
214
- const response = await axios.get(url);
215
- return this.featureResponseCallback(response, coordinate)
213
+ let data;
214
+ try {
215
+ data = await requestJson(url);
216
+ } catch (ex) {
217
+ this.getLogger().error(`Failed fetching WMS FeatureInfo ${url}`);
218
+ return [];
219
+ }
220
+ return this.featureResponseCallback(data, coordinate)
216
221
  .map(f => this.getProviderFeature(f));
217
222
  }
218
223
  return [];
@@ -0,0 +1,181 @@
1
+ import GeometryType from 'ol/geom/GeometryType.js';
2
+ import { check } from '@vcsuite/check';
3
+
4
+
5
+ class Extent3D {
6
+ /**
7
+ * @param {Array<number>} array
8
+ * @returns {Extent3D}
9
+ */
10
+ static fromArray(array) {
11
+ check(array, [Number]);
12
+ check(array.length, 6);
13
+ return new Extent3D(array[0], array[1], array[2], array[3], array[4], array[5]);
14
+ }
15
+
16
+ /**
17
+ * @param {import("ol/geom").Geometry} geometry
18
+ * @returns {Extent3D}
19
+ */
20
+ static fromGeometry(geometry) {
21
+ const extent = new Extent3D();
22
+ extent.extendWithGeometry(geometry);
23
+ return extent;
24
+ }
25
+
26
+ /**
27
+ * @param {VectorHeightInfo} heightInfo
28
+ * @returns {Extent3D}
29
+ */
30
+ static fromHeightInfo(heightInfo) {
31
+ const extent = new Extent3D();
32
+ extent.extendWithHeightInfo(heightInfo);
33
+ return extent;
34
+ }
35
+
36
+ /**
37
+ * @param {number} minX
38
+ * @param {number} minY
39
+ * @param {number} minZ
40
+ * @param {number} maxX
41
+ * @param {number} maxY
42
+ * @param {number} maxZ
43
+ */
44
+ constructor(minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity) {
45
+ /**
46
+ * @type {number}
47
+ */
48
+ this.minX = minX;
49
+ /**
50
+ * @type {number}
51
+ */
52
+ this.minY = minY;
53
+ /**
54
+ * @type {number}
55
+ */
56
+ this.minZ = minZ;
57
+ /**
58
+ * @type {number}
59
+ */
60
+ this.maxX = maxX;
61
+ /**
62
+ * @type {number}
63
+ */
64
+ this.maxY = maxY;
65
+ /**
66
+ * @type {number}
67
+ */
68
+ this.maxZ = maxZ;
69
+ }
70
+
71
+ /**
72
+ * @param {import("ol/geom").Geometry} geometry
73
+ */
74
+ extendWithGeometry(geometry) {
75
+ if (geometry.getType() === GeometryType.GEOMETRY_COLLECTION) {
76
+ /** @type {import("ol/geom/GeometryCollection").default} */ (geometry)
77
+ .getGeometriesArray().forEach((geom) => { this.extendWithGeometry(geom); });
78
+ } else if (geometry.getType() === GeometryType.CIRCLE) {
79
+ const flatCoordinates = /** @type {import("ol/geom/Circle").default} */ (geometry).getFlatCoordinates();
80
+ const stride = /** @type {import("ol/geom/Circle").default} */ (geometry).getStride();
81
+ const radius = flatCoordinates[stride] - flatCoordinates[0];
82
+ this.extendXY(
83
+ flatCoordinates[0] - radius,
84
+ flatCoordinates[1] - radius,
85
+ );
86
+ this.extendXY(
87
+ flatCoordinates[0] + radius,
88
+ flatCoordinates[1] + radius,
89
+ );
90
+ if (stride > 2) {
91
+ this.extendZ(flatCoordinates[2]);
92
+ }
93
+ } else {
94
+ const flatCoordinates = /** @type {import("ol/geom/SimpleGeometry").default} */ (geometry).getFlatCoordinates();
95
+ const stride = /** @type {import("ol/geom/SimpleGeometry").default} */ (geometry).getStride();
96
+ this.extendFlatCoordinates(flatCoordinates, stride);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @param {VectorHeightInfo} heightInfo
102
+ */
103
+ extendWithHeightInfo(heightInfo) {
104
+ if (heightInfo.extruded) {
105
+ const calculatedFeatureMaxHeight =
106
+ heightInfo.groundLevel + heightInfo.storeyHeightsAboveGround.reduce((accumulator, currentValue) => {
107
+ return accumulator + currentValue;
108
+ }, 0);
109
+ this.extendZ(calculatedFeatureMaxHeight);
110
+ const calculatedFeatureMinHeight =
111
+ heightInfo.groundLevel - heightInfo.storeyHeightsBelowGround.reduce((accumulator, currentValue) => {
112
+ return accumulator + currentValue;
113
+ }, 0);
114
+ this.extendZ(calculatedFeatureMinHeight);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @param {number} x
120
+ * @param {number} y
121
+ * @param {number} z
122
+ */
123
+ extendXYZ(x, y, z) {
124
+ this.minX = Math.min(this.minX, x);
125
+ this.minY = Math.min(this.minY, y);
126
+ this.minZ = Math.min(this.minZ, z);
127
+ this.maxX = Math.max(this.maxX, x);
128
+ this.maxY = Math.max(this.maxY, y);
129
+ this.maxZ = Math.max(this.maxZ, z);
130
+ }
131
+
132
+ /**
133
+ * @param {number} x
134
+ * @param {number} y
135
+ */
136
+ extendXY(x, y) {
137
+ this.minX = Math.min(this.minX, x);
138
+ this.minY = Math.min(this.minY, y);
139
+ this.maxX = Math.max(this.maxX, x);
140
+ this.maxY = Math.max(this.maxY, y);
141
+ }
142
+
143
+ /**
144
+ * @param {number} z
145
+ */
146
+ extendZ(z) {
147
+ this.minZ = Math.min(this.minZ, z);
148
+ this.maxZ = Math.max(this.maxZ, z);
149
+ }
150
+
151
+ /**
152
+ * @param {Array<number>} flatCoordinates
153
+ * @param {number} stride
154
+ */
155
+ extendFlatCoordinates(flatCoordinates, stride) {
156
+ const { length } = flatCoordinates;
157
+ for (let offset = 0; offset < length; offset += stride) {
158
+ if (stride > 2) {
159
+ this.extendXYZ(flatCoordinates[offset], flatCoordinates[offset + 1], flatCoordinates[offset + 2]);
160
+ } else {
161
+ this.extendXY(flatCoordinates[offset], flatCoordinates[offset + 1]);
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @returns {import("ol/extent").Extent}
168
+ */
169
+ to2D() {
170
+ return [this.minX, this.minY, this.maxX, this.maxY];
171
+ }
172
+
173
+ /**
174
+ * @returns {Array<number>}
175
+ */
176
+ toArray() {
177
+ return [this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ];
178
+ }
179
+ }
180
+
181
+ export default Extent3D;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @param {string} url
3
+ * @param {RequestInit=} init
4
+ * @returns {Promise<Response>}
5
+ */
6
+ export async function requestUrl(url, init) {
7
+ const response = await fetch(url, init);
8
+ if (!response.ok) {
9
+ throw new Error(`Failed fetching url ${url} with status: ${response.status}`);
10
+ }
11
+ return response;
12
+ }
13
+
14
+ /**
15
+ * @param {string} url
16
+ * @param {RequestInit=} init
17
+ * @returns {Promise<any>}
18
+ */
19
+ export async function requestJson(url, init) {
20
+ const response = await requestUrl(url, init);
21
+ return response.json();
22
+ }
23
+
24
+ /**
25
+ * @param {string} url
26
+ * @param {RequestInit=} init
27
+ * @returns {Promise<ArrayBuffer>}
28
+ */
29
+ export async function requestArrayBuffer(url, init) {
30
+ const response = await requestUrl(url, init);
31
+ return response.arrayBuffer();
32
+ }
@@ -42,8 +42,22 @@ class IndexedCollection extends Collection {
42
42
  * @api
43
43
  */
44
44
  this.moved = new VcsEvent();
45
+
46
+ /**
47
+ * @type {symbol}
48
+ * @private
49
+ */
50
+ this._previousIndexSymbol = Symbol('previousIndex');
45
51
  }
46
52
 
53
+ /**
54
+ * Get the symbol which is attached to an item prior to its removal. If an item is removed, the current index of the item
55
+ * is set on the item with this symbol.
56
+ * @type {symbol}
57
+ * @readonly
58
+ */
59
+ get previousIndexSymbol() { return this._previousIndexSymbol; }
60
+
47
61
  /**
48
62
  * Returns an item at index.
49
63
  * @param {number} index
@@ -76,6 +90,15 @@ class IndexedCollection extends Collection {
76
90
  return null;
77
91
  }
78
92
 
93
+ /**
94
+ * @inheritDoc
95
+ * @param {T} item
96
+ */
97
+ remove(item) {
98
+ item[this._previousIndexSymbol] = this._array.indexOf(item);
99
+ super.remove(item);
100
+ }
101
+
79
102
  /**
80
103
  * @param {T} item
81
104
  * @param {number} itemIndex
@@ -44,9 +44,6 @@ class LayerCollection extends IndexedCollection {
44
44
  this._layerEventListeners = {};
45
45
 
46
46
  /**
47
- * A symbol to describe the local z index of a layer. The local z index must not equal the layers z index, but is
48
- * always consistent in comparison to the neighbouring layers. If a layer is moved other then by z index, the collection
49
- * ensures consistency by setting a new local z index if needed.
50
47
  * @type {symbol}
51
48
  * @private
52
49
  */
@@ -67,6 +64,15 @@ class LayerCollection extends IndexedCollection {
67
64
  this.exclusiveManager = new ExclusiveManager();
68
65
  }
69
66
 
67
+ /**
68
+ * A symbol to describe the local z index of a layer. The local z index must not equal the layers z index, but is
69
+ * always consistent in comparison to the neighbouring layers. If a layer is moved other then by z index, the collection
70
+ * ensures consistency by setting a new local z index if needed.
71
+ * @type {symbol}
72
+ * @readonly
73
+ */
74
+ get zIndexSymbol() { return this._zIndexSymbol; }
75
+
70
76
  /**
71
77
  * @param {import("@vcmap/core").Layer} layer
72
78
  * @private
@@ -116,10 +122,9 @@ class LayerCollection extends IndexedCollection {
116
122
  _zIndexChanged(layer) {
117
123
  const currentIndex = this.indexOf(layer);
118
124
  if (currentIndex > -1) {
119
- const increased = layer[this._zIndexSymbol] < layer.zIndex;
120
125
  layer[this._zIndexSymbol] = layer.zIndex;
121
126
  let zIndexPosition = this._findZIndexPosition(layer.zIndex);
122
- if (increased && zIndexPosition > 0) {
127
+ if (zIndexPosition > 0 && zIndexPosition > currentIndex) {
123
128
  zIndexPosition -= 1; // remove self from count
124
129
  }
125
130
  zIndexPosition = zIndexPosition != null ? zIndexPosition : this._array.length - 1;
@@ -0,0 +1,224 @@
1
+ /* eslint no-underscore-dangle: ["error", { "allow": ["_array"] }] */
2
+ // eslint-disable-next-line max-classes-per-file
3
+ import { check } from '@vcsuite/check';
4
+ import { getLogger as getLoggerByName } from '@vcsuite/logger';
5
+ import { contextIdSymbol } from '../vcsAppContextHelpers.js';
6
+ import Collection from './collection.js';
7
+ import VcsEvent from '../event/vcsEvent.js';
8
+
9
+ /**
10
+ * @returns {import("@vcsuite/logger").Logger}
11
+ */
12
+ function getLogger() {
13
+ return getLoggerByName('OverrideCollection');
14
+ }
15
+
16
+ /**
17
+ * The override collection adds the ability to override a unique item and re-creating it, should the override
18
+ * be removed. This does change some flow of called events. 1) if you override an item, removed is not called for the
19
+ * removed current item. 2) added can be called more the once for the same unique id. 3) replaced is called for items
20
+ * which where replaced. replaced is called after added has been called for the item.
21
+ * @interface OverrideCollectionInterface
22
+ * @property {import("@vcmap/core").VcsEvent<T>} replaced - replaced is called after added
23
+ * @property {function(T):T|null} override - returns the overriden item or null if the item could not be inserted (this would be the result of a race condition)
24
+ * @property {Map<string, Array<Object>>} shadowMap
25
+ * @property {function(Array<Object>, string):Promise<void>} parseItems
26
+ * @property {function(string):Promise<void>} removeContext
27
+ * @property {function(string):Array<Object>} serializeContext
28
+ * @template {*} T
29
+ */
30
+
31
+ /**
32
+ * A symbol added to override collections.
33
+ * @type {symbol}
34
+ */
35
+ export const isOverrideCollection = Symbol('OverrideCollection');
36
+
37
+ /**
38
+ * @param {Collection<T>} collection
39
+ * @param {function():string} getDynamicContextId - function to get the current dynamic context id
40
+ * @param {(function(T):Object)=} serializeItem - optional function to serialize an item, defaults to returning item.toJSON or item: i => (i.toJSON || i)
41
+ * @param {(function(Object):(T|Promise<T>))=} deserializeItem - optional desirialization function. defaults to returning the passed object: i => i
42
+ * @param {*=} ctor - optional constructor to validate deserialized items against. if passed, deserializeItem must be an instance of ctor.
43
+ * @param {(function(T, (T|null|undefined), (number|undefined)):number|null)=} determineShadowIndex - return the index where a shadow should be inserted. only has relevance, if the collection is indexed. previous and current index may be null.
44
+ * @template {*} T
45
+ * @returns {OverrideCollection<T>}
46
+ */
47
+ function makeOverrideCollection(
48
+ collection,
49
+ getDynamicContextId,
50
+ serializeItem,
51
+ deserializeItem,
52
+ ctor,
53
+ determineShadowIndex,
54
+ ) {
55
+ check(collection, Collection);
56
+
57
+ const overrideCollection = /** @type {OverrideCollection<T>} */ (collection);
58
+ if (overrideCollection[isOverrideCollection]) {
59
+ throw new Error('Cannot transform collection, collection already is an OverrideCollection');
60
+ }
61
+ overrideCollection[isOverrideCollection] = true;
62
+
63
+ const deserialize = deserializeItem || (i => i);
64
+ // @ts-ignore
65
+ const serialize = serializeItem || (i => (i.toJSON ? i.toJSON() : i));
66
+ const getShadowIndex = determineShadowIndex || ((item, shadow, currentIndex) => currentIndex);
67
+
68
+ /**
69
+ * @type {Map<string, Array<Object>>}
70
+ */
71
+ overrideCollection.shadowMap = new Map();
72
+
73
+ /**
74
+ * @param {T} item
75
+ * @returns {T|null}
76
+ */
77
+ overrideCollection.override = function override(item) {
78
+ let shadow;
79
+ let index;
80
+ const itemId = item[overrideCollection.uniqueKey];
81
+
82
+ if (overrideCollection.hasKey(itemId)) {
83
+ shadow = overrideCollection.getByKey(itemId);
84
+ // @ts-ignore
85
+ index = overrideCollection._array.indexOf(shadow); // faking remove to not call removed
86
+ if (index > -1) {
87
+ // @ts-ignore
88
+ overrideCollection._array.splice(index, 1);
89
+ }
90
+ if (!overrideCollection.shadowMap.has(itemId)) {
91
+ overrideCollection.shadowMap.set(itemId, []);
92
+ }
93
+ const shadowsArray = overrideCollection.shadowMap.get(itemId);
94
+ const serializedShadow = serialize(shadow);
95
+ // @ts-ignore
96
+ if (shadow.destroy) {
97
+ // @ts-ignore
98
+ shadow.destroy();
99
+ }
100
+ serializedShadow[contextIdSymbol] = shadow[contextIdSymbol];
101
+ shadowsArray.push(serializedShadow);
102
+ }
103
+
104
+ const usedIndex = shadow ? getShadowIndex(shadow, item, index) : null;
105
+ // @ts-ignore
106
+ if (overrideCollection.add(item, usedIndex) >= 0) {
107
+ if (shadow) {
108
+ overrideCollection.replaced.raiseEvent(item);
109
+ }
110
+ return item;
111
+ }
112
+ return null;
113
+ };
114
+
115
+ /**
116
+ * @param {Array<Object>} configArray
117
+ * @param {string} contextId
118
+ * @returns {Promise<void>}
119
+ */
120
+ overrideCollection.parseItems = async function parseItems(configArray, contextId) {
121
+ if (Array.isArray(configArray)) {
122
+ const instanceArray = await Promise.all(configArray.map(async (config) => {
123
+ const item = await deserialize(config);
124
+ if (!item || (ctor && !(item instanceof ctor))) {
125
+ getLogger().warning(`Could not load item ${config[overrideCollection.uniqueKey]} of type ${config.type}`);
126
+ return null;
127
+ }
128
+ item[contextIdSymbol] = contextId;
129
+ return item;
130
+ }));
131
+ instanceArray
132
+ .filter(i => i)
133
+ .forEach((i) => { overrideCollection.override(i); });
134
+ }
135
+ };
136
+
137
+ overrideCollection.removed.addEventListener(async (item) => {
138
+ const itemId = item[overrideCollection.uniqueKey];
139
+
140
+ if (overrideCollection.shadowMap.has(itemId)) {
141
+ const serializedShadow = overrideCollection.shadowMap.get(itemId).pop();
142
+ if (serializedShadow) {
143
+ const reincarnation = await deserialize(serializedShadow);
144
+ reincarnation[contextIdSymbol] = serializedShadow[contextIdSymbol];
145
+ // @ts-ignore
146
+ const index = getShadowIndex(reincarnation, item, item[overrideCollection.previousIndexSymbol]);
147
+ // @ts-ignore
148
+ overrideCollection.add(reincarnation, index);
149
+ }
150
+
151
+ if (overrideCollection.shadowMap.get(itemId).length === 0) {
152
+ overrideCollection.shadowMap.delete(itemId);
153
+ }
154
+ }
155
+ });
156
+
157
+ overrideCollection.added.addEventListener((item) => {
158
+ if (!item[contextIdSymbol]) {
159
+ item[contextIdSymbol] = getDynamicContextId();
160
+ }
161
+ });
162
+
163
+ /**
164
+ * @param {string} contextId
165
+ * @returns {Promise<void>}
166
+ */
167
+ overrideCollection.removeContext = async function removeContext(contextId) {
168
+ overrideCollection.shadowMap.forEach((shadowsArray, name) => {
169
+ const newShadowsArray = shadowsArray.filter(c => c[contextIdSymbol] !== contextId);
170
+ if (newShadowsArray.length === 0) {
171
+ overrideCollection.shadowMap.delete(name);
172
+ } else if (newShadowsArray.length !== shadowsArray.length) {
173
+ overrideCollection.shadowMap.set(name, newShadowsArray);
174
+ }
175
+ });
176
+
177
+ await Promise.all([...overrideCollection]
178
+ .filter(item => item[contextIdSymbol] === contextId)
179
+ .map(async (item) => {
180
+ overrideCollection.remove(item);
181
+ // @ts-ignore
182
+ if (item.destroy) {
183
+ // @ts-ignore
184
+ item.destroy();
185
+ }
186
+ }));
187
+ };
188
+
189
+ /**
190
+ * @type {VcsEvent<T>}
191
+ */
192
+ overrideCollection.replaced = new VcsEvent();
193
+
194
+ /**
195
+ * @param {string} contextId
196
+ * @returns {Array<Object>}
197
+ */
198
+ overrideCollection.serializeContext = function serializeContext(contextId) {
199
+ return [...overrideCollection]
200
+ .map((item) => {
201
+ if (item[contextIdSymbol] === contextId) {
202
+ return serialize(item);
203
+ }
204
+ if (overrideCollection.shadowMap.has(item[overrideCollection.uniqueKey])) {
205
+ return overrideCollection.shadowMap.get(item[overrideCollection.uniqueKey])
206
+ .find(i => i[contextIdSymbol] === contextId);
207
+ }
208
+ return null;
209
+ })
210
+ .filter(i => i);
211
+ };
212
+
213
+ const originalDestroy = overrideCollection.destroy.bind(overrideCollection);
214
+
215
+ overrideCollection.destroy = function destroy() {
216
+ originalDestroy();
217
+ overrideCollection.shadowMap.clear();
218
+ overrideCollection.replaced.destroy();
219
+ };
220
+
221
+ return overrideCollection;
222
+ }
223
+
224
+ export default makeOverrideCollection;