@mapvx/web-js 3.4.0-dev.2 → 3.5.0-dev.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/cjs/domain/models/campaign.js +73 -0
- package/dist/cjs/domain/models/campaign.js.map +1 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/logger/sentry.js +1 -1
- package/dist/cjs/map/map.js +380 -14
- package/dist/cjs/map/map.js.map +1 -1
- package/dist/cjs/map/scene3DController.js +36 -0
- package/dist/cjs/map/scene3DController.js.map +1 -1
- package/dist/cjs/repository/repository.js +65 -4
- package/dist/cjs/repository/repository.js.map +1 -1
- package/dist/cjs/repository/requester.js +81 -9
- package/dist/cjs/repository/requester.js.map +1 -1
- package/dist/cjs/sdk.js +12 -0
- package/dist/cjs/sdk.js.map +1 -1
- package/dist/es/config/sdkConfig.d.ts +15 -0
- package/dist/es/config/sdkConfig.d.ts.map +1 -1
- package/dist/es/domain/models/campaign.d.ts +116 -0
- package/dist/es/domain/models/campaign.d.ts.map +1 -0
- package/dist/es/domain/models/campaign.js +69 -0
- package/dist/es/domain/models/campaign.js.map +1 -0
- package/dist/es/domain/models/mapConfig.d.ts +6 -0
- package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
- package/dist/es/index.d.ts +2 -0
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +2 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/logger/sentry.js +1 -1
- package/dist/es/map/map.d.ts +145 -0
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +380 -14
- package/dist/es/map/map.js.map +1 -1
- package/dist/es/map/scene3DController.d.ts +20 -0
- package/dist/es/map/scene3DController.d.ts.map +1 -1
- package/dist/es/map/scene3DController.js +36 -0
- package/dist/es/map/scene3DController.js.map +1 -1
- package/dist/es/repository/repository.d.ts +16 -1
- package/dist/es/repository/repository.d.ts.map +1 -1
- package/dist/es/repository/repository.js +65 -4
- package/dist/es/repository/repository.js.map +1 -1
- package/dist/es/repository/requester.d.ts +30 -1
- package/dist/es/repository/requester.d.ts.map +1 -1
- package/dist/es/repository/requester.js +81 -9
- package/dist/es/repository/requester.js.map +1 -1
- package/dist/es/sdk.d.ts +11 -1
- package/dist/es/sdk.d.ts.map +1 -1
- package/dist/es/sdk.js +12 -0
- package/dist/es/sdk.js.map +1 -1
- package/dist/umd/index.js +1688 -838
- package/dist/umd/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/map/map.js
CHANGED
|
@@ -63,6 +63,13 @@ const MAX_TILE_RETRIES = 5;
|
|
|
63
63
|
* often instead of once per timer tick.
|
|
64
64
|
*/
|
|
65
65
|
const TILE_RETRY_BASE_DELAY_MS = 1000;
|
|
66
|
+
/**
|
|
67
|
+
* Safety deadline for {@link InternalMapVXMap.setStyle}'s `onStyleReady` callback. The
|
|
68
|
+
* callback normally fires on the map's `idle` event, but `idle` may never arrive (the
|
|
69
|
+
* style request failed, the swap never settles). Callers hide the map behind a loading
|
|
70
|
+
* overlay until the callback arrives, so it must be released no matter what.
|
|
71
|
+
*/
|
|
72
|
+
const STYLE_READY_TIMEOUT_MS = 4000;
|
|
66
73
|
/** Fill/line layer ids for one circle render-order bucket. */
|
|
67
74
|
function circleLayerIdsFor(order) {
|
|
68
75
|
const suffix = CIRCLE_LAYER_SUFFIXES[order];
|
|
@@ -207,7 +214,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
207
214
|
* @returns A new instance of MapVXMap.
|
|
208
215
|
*/
|
|
209
216
|
constructor(mapConfig, container, token) {
|
|
210
|
-
var _a, _b, _c;
|
|
217
|
+
var _a, _b, _c, _d;
|
|
211
218
|
super();
|
|
212
219
|
/**
|
|
213
220
|
* Monotonic counter for {@link addLiveRoute} calls. Refreshes overlap (each one
|
|
@@ -221,6 +228,34 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
221
228
|
this.subPlaces = [];
|
|
222
229
|
this.currentFloor = "";
|
|
223
230
|
this.baseFilters = {};
|
|
231
|
+
/**
|
|
232
|
+
* Layer ids whose declared (base) filter has already been resolved — either captured
|
|
233
|
+
* into {@link baseFilters} or confirmed absent.
|
|
234
|
+
*
|
|
235
|
+
* `baseFilters[id] === undefined` cannot carry that information on its own: it means
|
|
236
|
+
* both "not looked at yet" and "looked at, the layer declares no filter". Without the
|
|
237
|
+
* distinction, a later pass re-reads the layer's LIVE filter — which by then is the
|
|
238
|
+
* one this class wrote — and stores our own floor/public-place filter as if it were
|
|
239
|
+
* the style's, so the layer keeps filtering by a stale floor forever.
|
|
240
|
+
*
|
|
241
|
+
* An id lands here the moment its declared filter is read from the style spec, or the
|
|
242
|
+
* moment this class writes a filter to that layer, whichever happens first. Layers
|
|
243
|
+
* added asynchronously after a `setStyle` swap are in neither situation, so their
|
|
244
|
+
* genuinely pristine live filter is still captured on first touch.
|
|
245
|
+
*/
|
|
246
|
+
this.capturedBaseFilters = new Set();
|
|
247
|
+
/**
|
|
248
|
+
* Monotonic id of the most recently STARTED style swap. Every {@link setStyle} call
|
|
249
|
+
* captures its own value and checks it again in each async continuation, so a
|
|
250
|
+
* superseded swap can neither touch the shared bookkeeping (`styleReadyTimer`,
|
|
251
|
+
* `pendingStyleSwapListener`) nor apply its obsolete style to the live map.
|
|
252
|
+
*
|
|
253
|
+
* A per-call `readied` flag is not enough: the timer and the listener slot are
|
|
254
|
+
* single-valued instance state, so two overlapping swaps otherwise clobber each
|
|
255
|
+
* other's — one caller's overlay is released by the other's failure, or an
|
|
256
|
+
* abandoned listener re-seeds filters from layer ids the current style lacks.
|
|
257
|
+
*/
|
|
258
|
+
this.styleSwapGeneration = 0;
|
|
224
259
|
this.markers = [];
|
|
225
260
|
this.circles = [];
|
|
226
261
|
this.enableHover = false;
|
|
@@ -242,15 +277,18 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
242
277
|
routeApiUrl: mapConfig.routeApiUrl,
|
|
243
278
|
healthApiUrl: mapConfig.healthApiUrl,
|
|
244
279
|
legacyApiUrl: mapConfig.legacyApiUrl,
|
|
280
|
+
portalApiUrl: mapConfig.portalApiUrl,
|
|
245
281
|
});
|
|
246
282
|
this.token = token;
|
|
283
|
+
this.authToken = mapConfig.authToken;
|
|
284
|
+
this.currentLang = (_c = mapConfig.lang) !== null && _c !== void 0 ? _c : "en";
|
|
247
285
|
this.parentPlaceId = mapConfig.parentPlaceId;
|
|
248
286
|
this.routeController = new routeController_1.RouteController(this);
|
|
249
287
|
this.userLocationMarker = undefined;
|
|
250
288
|
this.watchPositionID = undefined;
|
|
251
289
|
this.onFloorChange = mapConfig.onFloorChange;
|
|
252
290
|
this.onParentPlaceChange = mapConfig.onParentPlaceChange;
|
|
253
|
-
this.mode = (
|
|
291
|
+
this.mode = (_d = mapConfig.mode) !== null && _d !== void 0 ? _d : "2D";
|
|
254
292
|
this.scene3DConfig = mapConfig.scene3D;
|
|
255
293
|
this.initialCenter = mapConfig.center;
|
|
256
294
|
this.tileCacheConfig = (() => {
|
|
@@ -433,6 +471,17 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
433
471
|
style.layers
|
|
434
472
|
.filter((layer) => layer.id.startsWith("indoor-"))
|
|
435
473
|
.forEach((layer) => {
|
|
474
|
+
// Seed from the STYLE SPEC — the only pristine source — and do it BEFORE the
|
|
475
|
+
// setFilter below overwrites the live filter. Reading it back afterwards would
|
|
476
|
+
// return our own `publicPlaceFilter` and store it as the layer's declared one,
|
|
477
|
+
// turning `public_place` from an OR into an AND and hiding every private
|
|
478
|
+
// feature on the selected floor. Every indoor layer is covered, not just
|
|
479
|
+
// fill/line/symbol: a `circle` layer is discriminated by its filter just the
|
|
480
|
+
// same, and dropping it makes the layer repaint every feature.
|
|
481
|
+
// `filter` is absent from the union member for `background` layers, hence the
|
|
482
|
+
// structural read rather than `layer.filter`.
|
|
483
|
+
const declared = layer.filter;
|
|
484
|
+
this.captureDeclaredBaseFilter(layer.id, declared);
|
|
436
485
|
const baseFilter = this.baseFilters[layer.id];
|
|
437
486
|
if (baseFilter != null) {
|
|
438
487
|
const finalFilter = ["all", baseFilter, publicPlaceFilter];
|
|
@@ -441,20 +490,29 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
441
490
|
else {
|
|
442
491
|
this.map.setFilter(layer.id, publicPlaceFilter);
|
|
443
492
|
}
|
|
444
|
-
switch (layer.type) {
|
|
445
|
-
case "line":
|
|
446
|
-
case "symbol":
|
|
447
|
-
case "fill-extrusion":
|
|
448
|
-
case "fill":
|
|
449
|
-
if (layer.filter != null) {
|
|
450
|
-
this.baseFilters[layer.id] = layer.filter;
|
|
451
|
-
}
|
|
452
|
-
break;
|
|
453
|
-
default:
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
493
|
});
|
|
457
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Records a layer's declared (base) filter the first time the layer is seen, from an
|
|
497
|
+
* explicit value (a style-spec `filter`) when one is available, otherwise by reading
|
|
498
|
+
* the live filter — which is only pristine for layers this class has never written to.
|
|
499
|
+
*
|
|
500
|
+
* Marking happens even when no filter exists, so the layer is never revisited: on a
|
|
501
|
+
* second visit its live filter is one we produced, and storing that as the "declared"
|
|
502
|
+
* filter is what freezes a layer to a stale floor.
|
|
503
|
+
*
|
|
504
|
+
* @param layerId Id of the layer whose base filter should be resolved.
|
|
505
|
+
* @param declared Declared filter straight from the style spec, when the caller has it.
|
|
506
|
+
*/
|
|
507
|
+
captureDeclaredBaseFilter(layerId, declared) {
|
|
508
|
+
if (this.capturedBaseFilters.has(layerId))
|
|
509
|
+
return;
|
|
510
|
+
this.capturedBaseFilters.add(layerId);
|
|
511
|
+
const baseFilter = declared !== null && declared !== void 0 ? declared : this.safeGetFilter(layerId);
|
|
512
|
+
if (baseFilter != null) {
|
|
513
|
+
this.baseFilters[layerId] = baseFilter;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
458
516
|
setLayersForLanguage(lang) {
|
|
459
517
|
var _a, _b;
|
|
460
518
|
// outdoor layers
|
|
@@ -536,6 +594,14 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
536
594
|
var _a;
|
|
537
595
|
this.circles = [];
|
|
538
596
|
this.markerLayoutController.destroy();
|
|
597
|
+
// Drop any in-flight `setStyle` bookkeeping while the map still exists: the
|
|
598
|
+
// listener has to be detached before `map.remove()`, and the safety timer must not
|
|
599
|
+
// outlive the map and call `onStyleReady` for a map that is already gone.
|
|
600
|
+
this.clearPendingStyleSwapListener();
|
|
601
|
+
if (this.styleReadyTimer) {
|
|
602
|
+
clearTimeout(this.styleReadyTimer);
|
|
603
|
+
this.styleReadyTimer = undefined;
|
|
604
|
+
}
|
|
539
605
|
// Dispose the 3D scene's GPU resources before the map (and its shared WebGL context) go away —
|
|
540
606
|
// MapLibre's own teardown never calls the custom GLB layer's `onRemove`.
|
|
541
607
|
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
@@ -610,6 +676,13 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
610
676
|
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
611
677
|
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
612
678
|
this.map.setStyle(finalStyle);
|
|
679
|
+
// The new place's style declares its own filters for the same `indoor-*` layer
|
|
680
|
+
// ids. Without this reset every id is still marked captured from the previous
|
|
681
|
+
// place, so `captureDeclaredBaseFilter` early-returns for all of them and the
|
|
682
|
+
// previous place's expressions keep filtering the new style forever. (The
|
|
683
|
+
// `switch (layer.type)` block that used to re-read them on every style load was
|
|
684
|
+
// removed when capture became once-per-id, which is what made this necessary.)
|
|
685
|
+
this.resetBaseFilters();
|
|
613
686
|
this.whenStyleUpdates(finalStyle);
|
|
614
687
|
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady();
|
|
615
688
|
})
|
|
@@ -649,6 +722,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
649
722
|
}
|
|
650
723
|
setLang(lang) {
|
|
651
724
|
this.repository.setLang(lang);
|
|
725
|
+
this.currentLang = lang;
|
|
652
726
|
if (_rtl_1.rtlLanguages.includes(lang)) {
|
|
653
727
|
this.setRTLSupport();
|
|
654
728
|
}
|
|
@@ -666,6 +740,201 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
666
740
|
setParentPlace(place, updateStyle, onStyleReady) {
|
|
667
741
|
this.changeParentPlaceTo(place, updateStyle, onStyleReady);
|
|
668
742
|
}
|
|
743
|
+
setStyle(styleRef, styleTheme, onStyleReady) {
|
|
744
|
+
// Every swap gets a generation. All shared bookkeeping below is touched only while
|
|
745
|
+
// this swap is still the current one, so two overlapping calls cannot release each
|
|
746
|
+
// other's overlay or apply an obsolete style. `readied` stays per-call because each
|
|
747
|
+
// caller's callback must still fire exactly once.
|
|
748
|
+
const generation = ++this.styleSwapGeneration;
|
|
749
|
+
const isCurrent = () => this.styleSwapGeneration === generation;
|
|
750
|
+
let readied = false;
|
|
751
|
+
const reportReady = (error) => {
|
|
752
|
+
if (readied)
|
|
753
|
+
return;
|
|
754
|
+
readied = true;
|
|
755
|
+
// Only the current swap owns the shared timer; a superseded swap clearing it here
|
|
756
|
+
// would leave the live swap with no safety net (and its caller hidden forever).
|
|
757
|
+
if (isCurrent() && this.styleReadyTimer) {
|
|
758
|
+
clearTimeout(this.styleReadyTimer);
|
|
759
|
+
this.styleReadyTimer = undefined;
|
|
760
|
+
}
|
|
761
|
+
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady(error);
|
|
762
|
+
};
|
|
763
|
+
// `onStyleReady` must fire on EVERY path — success, a rejected style request, or a
|
|
764
|
+
// swap that never settles — because callers keep the map hidden behind a loading
|
|
765
|
+
// overlay until it arrives. The safety timer is therefore armed before the request
|
|
766
|
+
// instead of inside `.then()`.
|
|
767
|
+
if (this.styleReadyTimer)
|
|
768
|
+
clearTimeout(this.styleReadyTimer);
|
|
769
|
+
this.styleReadyTimer = setTimeout(reportReady, STYLE_READY_TIMEOUT_MS);
|
|
770
|
+
// A previous swap may still hold a `styledata` listener whose style never reached the
|
|
771
|
+
// loaded state. Drop it before starting a new one: it captured an obsolete style and
|
|
772
|
+
// would otherwise fire on this swap, wiping `baseFilters` and calling `setFilter`
|
|
773
|
+
// with layer ids the new style does not have.
|
|
774
|
+
this.clearPendingStyleSwapListener();
|
|
775
|
+
// `this.map` is created asynchronously (see `onMapStyleLoaded`), so a `setStyle` call
|
|
776
|
+
// made right after `createMap` can land before it exists. Fail loudly instead of
|
|
777
|
+
// letting `this.map.setStyle` throw into `.catch`, which reported READY and left the
|
|
778
|
+
// caller revealing the previous style as if the swap had succeeded.
|
|
779
|
+
if (!this.map) {
|
|
780
|
+
const error = new Error("setStyle called before the map finished initializing; wait for the map's load event");
|
|
781
|
+
this.logError(error, { functionName: "setStyle" });
|
|
782
|
+
reportReady(error);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
// When a user bearer token is present, the style is resolved through the
|
|
786
|
+
// bearer-authenticated endpoint, which authorizes by the user's client access
|
|
787
|
+
// rather than the api key's scopes — so a client's branded style can be
|
|
788
|
+
// previewed/applied without granting that place to a shared api key.
|
|
789
|
+
// Api-key consumers keep the `/api/sdk/v2/mapStyleJson` path.
|
|
790
|
+
const stylePromise = this.authToken
|
|
791
|
+
? this.repository.fetchAndParseMapStyleByBearer(this.parentPlaceId, this.authToken, styleRef, styleTheme)
|
|
792
|
+
: this.repository.fetchAndParseMapStyle(this.parentPlaceId, styleRef, styleTheme);
|
|
793
|
+
stylePromise
|
|
794
|
+
.then((style) => {
|
|
795
|
+
// A newer swap started while this style was in flight. Applying this one now
|
|
796
|
+
// would fight the newer style layer by layer, so drop it and let the newer swap
|
|
797
|
+
// own the map and its caller's callback.
|
|
798
|
+
if (!isCurrent())
|
|
799
|
+
return;
|
|
800
|
+
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
801
|
+
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
802
|
+
// Full (non-diff) swap so the previewed style completely replaces the base —
|
|
803
|
+
// a diff would keep old base layers and reappear on the next floor change.
|
|
804
|
+
this.map.setStyle(finalStyle, { diff: false });
|
|
805
|
+
// Drop the previous style's base filters SYNCHRONOUSLY, right where the layers
|
|
806
|
+
// they describe stop existing. They are keyed by layer id and persist across
|
|
807
|
+
// swaps, so any code reading them before the apply pass below (the `idle`
|
|
808
|
+
// handler's `filterByFloorKey`, a floor change racing the swap) would AND the
|
|
809
|
+
// previous style's discriminator into the new style's layers — the stale-filter
|
|
810
|
+
// washout this reset exists to prevent. Gating it on `isStyleLoaded` left exactly
|
|
811
|
+
// that window open. Fresh declared filters are re-captured lazily in
|
|
812
|
+
// `applyLayerFilters` as each indoor layer is first touched.
|
|
813
|
+
this.resetBaseFilters();
|
|
814
|
+
// Re-apply floor filters, route/circle layers and markers ONLY once the new
|
|
815
|
+
// style's sources/layers have finished loading; running them mid-reload throws
|
|
816
|
+
// (`getSource` on an undefined style) and the floor filter would not stick.
|
|
817
|
+
const applyOnceLoaded = () => {
|
|
818
|
+
// Superseded: detach and leave the map to the newer swap. Without this the
|
|
819
|
+
// listener stays attached (the slot it was tracked in has been overwritten)
|
|
820
|
+
// and re-seeds filters from layer ids the current style does not have, which
|
|
821
|
+
// makes maplibre throw and aborts the newer swap's own listener.
|
|
822
|
+
if (!isCurrent()) {
|
|
823
|
+
this.map.off("styledata", applyOnceLoaded);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (!this.hasLoadedStyle())
|
|
827
|
+
return;
|
|
828
|
+
this.map.off("styledata", applyOnceLoaded);
|
|
829
|
+
if (this.pendingStyleSwapListener === applyOnceLoaded) {
|
|
830
|
+
this.pendingStyleSwapListener = undefined;
|
|
831
|
+
}
|
|
832
|
+
this.whenStyleUpdates(finalStyle);
|
|
833
|
+
// A non-diff swap replaces every layer, so anything applied ON TOP of the
|
|
834
|
+
// style — label language, caller-requested place highlights, the 3D scene's
|
|
835
|
+
// GLB layer and hidden-layer overrides — is gone and has to be replayed.
|
|
836
|
+
// `whenStyleUpdates` covers only floor filters, routes and circles.
|
|
837
|
+
this.restoreStyleOverlays();
|
|
838
|
+
this.logEvent("setStyle");
|
|
839
|
+
};
|
|
840
|
+
this.pendingStyleSwapListener = applyOnceLoaded;
|
|
841
|
+
this.map.on("styledata", applyOnceLoaded);
|
|
842
|
+
applyOnceLoaded();
|
|
843
|
+
// Re-apply the floor filter and report ready once the map is idle. This is
|
|
844
|
+
// intentionally OUTSIDE applyOnceLoaded's `isStyleLoaded` gate: the indoor
|
|
845
|
+
// polygon layers settle a beat after the swap (so the floor filter in
|
|
846
|
+
// whenStyleUpdates misses them and the venue paints every floor at once), and
|
|
847
|
+
// `idle` only fires once the style is loaded and its first tiles rendered — so
|
|
848
|
+
// filterByFloorKey is safe here and does not depend on the sometimes-flaky
|
|
849
|
+
// isStyleLoaded check. onStyleReady lets callers reveal the map only once it is
|
|
850
|
+
// settled; the timer armed above just clears a hanging overlay if idle never fires.
|
|
851
|
+
this.map.once("idle", () => {
|
|
852
|
+
// `once` cannot be cancelled, so a superseded swap's handler still fires —
|
|
853
|
+
// filtering here keeps it from re-filtering the newer style's floors and from
|
|
854
|
+
// reporting the newer swap's caller ready ahead of time.
|
|
855
|
+
if (!isCurrent())
|
|
856
|
+
return;
|
|
857
|
+
this.filterByFloorKey(this.currentFloor);
|
|
858
|
+
reportReady();
|
|
859
|
+
});
|
|
860
|
+
})
|
|
861
|
+
.catch((error) => {
|
|
862
|
+
// A superseded swap must not tear down the listener the CURRENT swap installed.
|
|
863
|
+
if (isCurrent()) {
|
|
864
|
+
this.clearPendingStyleSwapListener();
|
|
865
|
+
}
|
|
866
|
+
this.logError(error, { functionName: "setStyle" });
|
|
867
|
+
// Release the caller immediately instead of making it wait out the safety timer:
|
|
868
|
+
// the style will never arrive, so the overlay has nothing to wait for. The error
|
|
869
|
+
// is handed to the callback so a caller can tell a failed swap from an applied
|
|
870
|
+
// one — the previous style is still on screen either way.
|
|
871
|
+
reportReady(error instanceof Error ? error : new Error(String(error)));
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Forgets every captured base filter so the next style's declared filters are read
|
|
876
|
+
* fresh from that style instead of inherited from the previous one.
|
|
877
|
+
*
|
|
878
|
+
* Both halves must move together: `baseFilters` holds the expressions and
|
|
879
|
+
* `capturedBaseFilters` records which layer ids have already been resolved, so
|
|
880
|
+
* clearing only the first would leave every id marked "already captured" and the new
|
|
881
|
+
* style's filters would never be read at all.
|
|
882
|
+
*/
|
|
883
|
+
resetBaseFilters() {
|
|
884
|
+
this.baseFilters = {};
|
|
885
|
+
this.capturedBaseFilters.clear();
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Replays everything that lives ON TOP of the map style after a full (non-diff) swap
|
|
889
|
+
* has deleted it: label language expressions, caller-requested place highlights and
|
|
890
|
+
* the 3D scene's GLB layer / hidden-layer overrides.
|
|
891
|
+
*
|
|
892
|
+
* These are deliberately not part of `whenStyleUpdates`, which restores only
|
|
893
|
+
* style-derived state. These are decisions the SDK's callers made through `setLang`,
|
|
894
|
+
* `setPlacesAsSelected`, `addBorderToPlaces` and the 3D config, and nothing else would
|
|
895
|
+
* ever restore them.
|
|
896
|
+
*/
|
|
897
|
+
restoreStyleOverlays() {
|
|
898
|
+
// Each step is isolated: the style itself has already been applied by the time we get
|
|
899
|
+
// here, so one failing restore must neither skip the others nor bubble out into
|
|
900
|
+
// `setStyle`'s `.catch`, which would report the successful swap to `onStyleReady` as
|
|
901
|
+
// a failure. `setPlacesAsSelected` / `addBorderToPlaces` throw by contract.
|
|
902
|
+
const restore = (what, step) => {
|
|
903
|
+
try {
|
|
904
|
+
step();
|
|
905
|
+
}
|
|
906
|
+
catch (error) {
|
|
907
|
+
this.logError(error, { functionName: "restoreStyleOverlays", step: what });
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
restore("language", () => this.setLayersForLanguage(this.currentLang));
|
|
911
|
+
if (this.coloredPlacesRequest) {
|
|
912
|
+
const { placeIds, color } = this.coloredPlacesRequest;
|
|
913
|
+
restore("coloredPlaces", () => this.setPlacesAsSelected(placeIds, color));
|
|
914
|
+
}
|
|
915
|
+
if (this.borderedPlacesRequest) {
|
|
916
|
+
const { placeIds, color, width } = this.borderedPlacesRequest;
|
|
917
|
+
restore("borderedPlaces", () => this.addBorderToPlaces(placeIds, color, width));
|
|
918
|
+
}
|
|
919
|
+
// The swap removed the GLB custom layer, and MapLibre called its `onRemove`, which
|
|
920
|
+
// disposes the whole three.js scene. Nothing else re-adds it, so without this every
|
|
921
|
+
// 3D model disappears permanently and `hideLayers` stops being applied.
|
|
922
|
+
restore("scene3D", () => { var _a; return (_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.reattachToStyle(); });
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Removes the `styledata` listener left by an in-flight {@link setStyle} swap, if any.
|
|
926
|
+
* Called before starting a new swap and when one fails, so a listener holding an
|
|
927
|
+
* obsolete style can never fire against a different style's layers.
|
|
928
|
+
*/
|
|
929
|
+
clearPendingStyleSwapListener() {
|
|
930
|
+
var _a;
|
|
931
|
+
if (!this.pendingStyleSwapListener)
|
|
932
|
+
return;
|
|
933
|
+
// `this.map?` — a listener can only have been installed while the map existed, but
|
|
934
|
+
// this also runs from `destroyMap` and from `setStyle` before the map is created.
|
|
935
|
+
(_a = this.map) === null || _a === void 0 ? void 0 : _a.off("styledata", this.pendingStyleSwapListener);
|
|
936
|
+
this.pendingStyleSwapListener = undefined;
|
|
937
|
+
}
|
|
669
938
|
/**
|
|
670
939
|
* Removes current parent place and updates style to default
|
|
671
940
|
*/
|
|
@@ -1182,6 +1451,15 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1182
1451
|
});
|
|
1183
1452
|
const baseFloorId = (_c = this.innerFloors.find((floor) => floor.key === floorId)) === null || _c === void 0 ? void 0 : _c.baseFloor;
|
|
1184
1453
|
const indoorLayers = layers.filter((layer) => layer.id.startsWith("indoor-"));
|
|
1454
|
+
// Resolve each indoor layer's declared (base) filter before we start folding floor
|
|
1455
|
+
// filters into it — and before cloning it into a `base-indoor-*` layer, which copies
|
|
1456
|
+
// the base filter across. Layers added asynchronously after a `setStyle` swap are
|
|
1457
|
+
// missing when `setBaseFilters` runs, so this is where their still-pristine filter
|
|
1458
|
+
// is captured; without it the layer loses its own discrimination and repaints every
|
|
1459
|
+
// feature (the washed-out / blue-outline look after changing floors). Layers already
|
|
1460
|
+
// resolved are skipped, so a filter this class wrote is never mistaken for a
|
|
1461
|
+
// declared one.
|
|
1462
|
+
indoorLayers.forEach((layer) => this.captureDeclaredBaseFilter(layer.id));
|
|
1185
1463
|
if (baseFloorId != undefined) {
|
|
1186
1464
|
const firstLayerId = (_d = indoorLayers[0]) === null || _d === void 0 ? void 0 : _d.id;
|
|
1187
1465
|
indoorLayers
|
|
@@ -1198,8 +1476,33 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1198
1476
|
this.applyLayerFilters(layer, floorId);
|
|
1199
1477
|
});
|
|
1200
1478
|
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Reads a layer's current filter, tolerating the layer having been removed or the
|
|
1481
|
+
* style being mid-swap (maplibre `getFilter` throws in those cases). Returns
|
|
1482
|
+
* `undefined` instead of throwing so base-filter capture never aborts floor filtering.
|
|
1483
|
+
*/
|
|
1484
|
+
safeGetFilter(layerId) {
|
|
1485
|
+
try {
|
|
1486
|
+
return this.map.getFilter(layerId);
|
|
1487
|
+
}
|
|
1488
|
+
catch (_a) {
|
|
1489
|
+
return undefined;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1201
1492
|
applyLayerFilters(layer, floorId) {
|
|
1202
1493
|
var _a;
|
|
1494
|
+
// Resolve the layer's declared (base) filter the first time we touch it. Indoor
|
|
1495
|
+
// layers such as `indoor-polygon-room` share one source and are told apart only by
|
|
1496
|
+
// their own filter (e.g. `["in","room",["get","tags"]]`); we must fold that into the
|
|
1497
|
+
// floor filter or the layer repaints every feature. Some of these layers are added
|
|
1498
|
+
// asynchronously after a `setStyle` swap, so they are absent when `setBaseFilters`
|
|
1499
|
+
// runs — capturing here, while their live filter is still pristine, is what keeps a
|
|
1500
|
+
// floor change from wiping the style (the washed-out, blue-outline look).
|
|
1501
|
+
// `base-indoor-*` clones inherit their base filter from the layer they were cloned
|
|
1502
|
+
// from, so they are never read back.
|
|
1503
|
+
if (!layer.id.startsWith("base-indoor-")) {
|
|
1504
|
+
this.captureDeclaredBaseFilter(layer.id);
|
|
1505
|
+
}
|
|
1203
1506
|
let baseFilter = this.baseFilters[layer.id];
|
|
1204
1507
|
const floorIdString = floorId !== null && floorId !== void 0 ? floorId : "";
|
|
1205
1508
|
const floorFilter = [
|
|
@@ -1232,6 +1535,10 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1232
1535
|
(layer.type == "fill" || layer.type === "fill-extrusion")) {
|
|
1233
1536
|
customFilters.push(["!", ["in", this.hoveredId, ["get", "ref"]]]);
|
|
1234
1537
|
}
|
|
1538
|
+
// From here on this layer's live filter is ours, never the style's declared one.
|
|
1539
|
+
// Marking it keeps any later pass from reading it back as a base filter — that is
|
|
1540
|
+
// what used to AND the previous floor into the next one and blank the layer out.
|
|
1541
|
+
this.capturedBaseFilters.add(layer.id);
|
|
1235
1542
|
if (baseFilter != null) {
|
|
1236
1543
|
const finalFilter = ["all", baseFilter, customFilters];
|
|
1237
1544
|
this.map.setFilter(layer.id, finalFilter);
|
|
@@ -1658,6 +1965,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1658
1965
|
}
|
|
1659
1966
|
setPlacesAsSelected(placeIds, color) {
|
|
1660
1967
|
try {
|
|
1968
|
+
this.coloredPlacesRequest = { placeIds: [...placeIds], color };
|
|
1661
1969
|
if (!this.hasLayer("indoor-colored-places")) {
|
|
1662
1970
|
this.createColoredPlacesLayer();
|
|
1663
1971
|
}
|
|
@@ -1669,6 +1977,9 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1669
1977
|
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
1670
1978
|
const condition = ["all", placeFilter, basicFilter];
|
|
1671
1979
|
this.map.setFilter("indoor-colored-places", condition);
|
|
1980
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
1981
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
1982
|
+
this.capturedBaseFilters.add("indoor-colored-places");
|
|
1672
1983
|
this.baseFilters["indoor-colored-places"] = placeFilter;
|
|
1673
1984
|
if (color !== undefined) {
|
|
1674
1985
|
const layer = this.map.getLayer("indoor-colored-places");
|
|
@@ -1752,8 +2063,12 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1752
2063
|
return layer !== undefined;
|
|
1753
2064
|
}
|
|
1754
2065
|
clearColoredPlaces() {
|
|
2066
|
+
this.coloredPlacesRequest = undefined;
|
|
1755
2067
|
if (this.hasLayer("indoor-colored-places")) {
|
|
1756
2068
|
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2069
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2070
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2071
|
+
this.capturedBaseFilters.add("indoor-colored-places");
|
|
1757
2072
|
this.baseFilters["indoor-colored-places"] = condition;
|
|
1758
2073
|
this.map.setFilter("indoor-colored-places", condition);
|
|
1759
2074
|
}
|
|
@@ -1948,6 +2263,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1948
2263
|
}
|
|
1949
2264
|
addBorderToPlaces(placeIds, color, width) {
|
|
1950
2265
|
try {
|
|
2266
|
+
this.borderedPlacesRequest = { placeIds: [...placeIds], color, width };
|
|
1951
2267
|
if (!this.hasLayer("indoor-bordered-places")) {
|
|
1952
2268
|
this.createBorderedPlacesLayer();
|
|
1953
2269
|
}
|
|
@@ -1959,6 +2275,9 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1959
2275
|
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
1960
2276
|
const condition = ["all", placeFilter, basicFilter];
|
|
1961
2277
|
this.map.setFilter("indoor-bordered-places", condition);
|
|
2278
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2279
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2280
|
+
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
1962
2281
|
this.baseFilters["indoor-bordered-places"] = placeFilter;
|
|
1963
2282
|
if (color !== undefined && width !== undefined) {
|
|
1964
2283
|
this.map.setPaintProperty("indoor-bordered-places", "line-color", color);
|
|
@@ -1984,8 +2303,12 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
1984
2303
|
this.map.addLayer(layer);
|
|
1985
2304
|
}
|
|
1986
2305
|
clearBorderedPlaces() {
|
|
2306
|
+
this.borderedPlacesRequest = undefined;
|
|
1987
2307
|
if (this.hasLayer("indoor-bordered-places")) {
|
|
1988
2308
|
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2309
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2310
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2311
|
+
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
1989
2312
|
this.baseFilters["indoor-bordered-places"] = condition;
|
|
1990
2313
|
this.map.setFilter("indoor-bordered-places", condition);
|
|
1991
2314
|
}
|
|
@@ -2030,6 +2353,38 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
2030
2353
|
this.failedTiles.delete(e.tile);
|
|
2031
2354
|
}
|
|
2032
2355
|
}
|
|
2356
|
+
/**
|
|
2357
|
+
* True when a style object is attached to the map. During a full `setStyle` swap the
|
|
2358
|
+
* map is momentarily style-less and any `getSource`/`getFilter` call throws
|
|
2359
|
+
* ("Cannot read properties of undefined"), so call sites that only need the style to
|
|
2360
|
+
* exist should gate on this rather than on `isStyleLoaded()`.
|
|
2361
|
+
*/
|
|
2362
|
+
hasStyle() {
|
|
2363
|
+
var _a;
|
|
2364
|
+
// `map.getStyle()` is the PUBLIC probe: it returns the serialized style, or throws
|
|
2365
|
+
// while the map is momentarily style-less mid-swap. Reading maplibre's internal
|
|
2366
|
+
// `style` field instead (it is absent from the public typings, so it needed a double
|
|
2367
|
+
// cast) would fail closed and silently forever the moment a maplibre upgrade renames
|
|
2368
|
+
// or privatizes it — disabling both the tile-retry loop and `setStyle`'s apply pass
|
|
2369
|
+
// with no throw and no log. `safeGetFilter` already uses this try/catch shape.
|
|
2370
|
+
try {
|
|
2371
|
+
return ((_a = this.map) === null || _a === void 0 ? void 0 : _a.getStyle()) != null;
|
|
2372
|
+
}
|
|
2373
|
+
catch (_b) {
|
|
2374
|
+
return false;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* True when the map has a style object attached and fully loaded. Unlike
|
|
2379
|
+
* `map.isStyleLoaded()`, this does not emit maplibre's "There is no style
|
|
2380
|
+
* added to the map" warning when the style is momentarily absent (e.g. during
|
|
2381
|
+
* a `setStyle` swap), because it checks for the style object first.
|
|
2382
|
+
*/
|
|
2383
|
+
hasLoadedStyle() {
|
|
2384
|
+
if (!this.hasStyle())
|
|
2385
|
+
return false;
|
|
2386
|
+
return this.map.isStyleLoaded() === true;
|
|
2387
|
+
}
|
|
2033
2388
|
subscribeToFailedTiles() {
|
|
2034
2389
|
if (this.retryTimer)
|
|
2035
2390
|
return;
|
|
@@ -2039,6 +2394,17 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
|
2039
2394
|
this.boundOnWebGLContextLost = this.onWebGLContextLost.bind(this);
|
|
2040
2395
|
this.boundOnWebGLContextRestored = this.onWebGLContextRestored.bind(this);
|
|
2041
2396
|
this.retryTimer = setInterval(() => {
|
|
2397
|
+
// During a full style swap (`setStyle` with `diff: false`) the map can
|
|
2398
|
+
// momentarily have no style object attached. Calling `getSource` then
|
|
2399
|
+
// throws ("Cannot read properties of undefined (reading 'getSource')").
|
|
2400
|
+
// Only the style's PRESENCE is checked here, never `isStyleLoaded()`:
|
|
2401
|
+
// that is false while any source still has tiles in flight, which is
|
|
2402
|
+
// exactly when tiles fail — gating on it would skip every tick during a
|
|
2403
|
+
// bad-network burst. The queue is left untouched so the tiles queued
|
|
2404
|
+
// mid-swap are still retried on the next tick.
|
|
2405
|
+
if (!this.hasStyle()) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2042
2408
|
if (!this.map.getSource("indoorequal")) {
|
|
2043
2409
|
this.failedTiles.clear();
|
|
2044
2410
|
return;
|