@geolibre/core 3.1.0 → 3.2.0
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/index.d.mts +591 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1186 -54
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, createExpression } from "@maplibre/maplibre-gl-style-spec";
|
|
1
|
+
import { Color, createExpression, featureFilter, latest, normalizePropertyExpression } from "@maplibre/maplibre-gl-style-spec";
|
|
2
2
|
import { create } from "zustand";
|
|
3
3
|
import { temporal } from "zundo";
|
|
4
4
|
import { v4 } from "uuid";
|
|
@@ -4346,6 +4346,86 @@ function extrusionColorValue(style) {
|
|
|
4346
4346
|
return parseJsonExpression(styleValue(style, "extrusionAdvancedStyleEnabled") ? styleValue(style, "extrusionColorExpression") : "") ?? flat;
|
|
4347
4347
|
}
|
|
4348
4348
|
//#endregion
|
|
4349
|
+
//#region src/style-layer-evaluator.ts
|
|
4350
|
+
const GEOMETRY_TYPE = {
|
|
4351
|
+
Point: 1,
|
|
4352
|
+
LineString: 2,
|
|
4353
|
+
Polygon: 3
|
|
4354
|
+
};
|
|
4355
|
+
/**
|
|
4356
|
+
* Compile a MapLibre style layer's filter and properties for evaluation
|
|
4357
|
+
* outside MapLibre, for a renderer that draws the layer itself (the ArcGIS
|
|
4358
|
+
* renderer drawing a plugin's own GeoJSON overlay, say). Uses the style spec's
|
|
4359
|
+
* own evaluator, so data-driven and zoom expressions, legacy functions and
|
|
4360
|
+
* defaults behave as they do on MapLibre. Each property compiles once.
|
|
4361
|
+
*
|
|
4362
|
+
* @param layer - The style layer (its `type`, `filter`, `paint`, `layout`).
|
|
4363
|
+
* @returns The layer's evaluator.
|
|
4364
|
+
*/
|
|
4365
|
+
function createStyleLayerEvaluator(layer) {
|
|
4366
|
+
const root = `layers[${layer.id ?? ""}]`;
|
|
4367
|
+
let filter;
|
|
4368
|
+
try {
|
|
4369
|
+
const compiled = featureFilter(layer.filter, `${root}.filter`);
|
|
4370
|
+
filter = (zoom, feature) => compiled.filter({ zoom }, feature);
|
|
4371
|
+
} catch {
|
|
4372
|
+
filter = () => false;
|
|
4373
|
+
}
|
|
4374
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
4375
|
+
const compile = (group, name) => {
|
|
4376
|
+
const key = `${group}/${name}`;
|
|
4377
|
+
if (compiled.has(key)) return compiled.get(key);
|
|
4378
|
+
const spec = latest[`${group}_${layer.type}`]?.[name];
|
|
4379
|
+
let evaluate = null;
|
|
4380
|
+
if (spec) {
|
|
4381
|
+
const raw = layer[group]?.[name];
|
|
4382
|
+
try {
|
|
4383
|
+
const expression = normalizePropertyExpression(raw ?? spec.default, `${root}.${group}.${name}`, spec);
|
|
4384
|
+
evaluate = (globals, feature) => expression.evaluate(globals, feature);
|
|
4385
|
+
} catch {
|
|
4386
|
+
evaluate = null;
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
compiled.set(key, evaluate);
|
|
4390
|
+
return evaluate;
|
|
4391
|
+
};
|
|
4392
|
+
const input = (feature) => ({
|
|
4393
|
+
type: GEOMETRY_TYPE[feature.kind],
|
|
4394
|
+
id: feature.id,
|
|
4395
|
+
properties: feature.properties ?? {}
|
|
4396
|
+
});
|
|
4397
|
+
return {
|
|
4398
|
+
passes: (zoom, feature) => {
|
|
4399
|
+
try {
|
|
4400
|
+
return filter(zoom, input(feature));
|
|
4401
|
+
} catch {
|
|
4402
|
+
return false;
|
|
4403
|
+
}
|
|
4404
|
+
},
|
|
4405
|
+
value: (group, name, zoom, feature) => {
|
|
4406
|
+
const evaluate = compile(group, name);
|
|
4407
|
+
if (!evaluate) return void 0;
|
|
4408
|
+
let result;
|
|
4409
|
+
try {
|
|
4410
|
+
result = evaluate({ zoom }, input(feature));
|
|
4411
|
+
} catch {
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
if (result instanceof Color) {
|
|
4415
|
+
const [r, g, b, a] = result.rgb;
|
|
4416
|
+
return [
|
|
4417
|
+
Math.round(r * 255),
|
|
4418
|
+
Math.round(g * 255),
|
|
4419
|
+
Math.round(b * 255),
|
|
4420
|
+
a
|
|
4421
|
+
];
|
|
4422
|
+
}
|
|
4423
|
+
if (result && typeof result === "object" && "sections" in result) return String(result);
|
|
4424
|
+
return result;
|
|
4425
|
+
}
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4428
|
+
//#endregion
|
|
4349
4429
|
//#region src/document-locale.ts
|
|
4350
4430
|
/**
|
|
4351
4431
|
* The document language, so formatted numbers and dates follow the UI locale.
|
|
@@ -4645,6 +4725,20 @@ function supportsBridgedOpacity(layerId) {
|
|
|
4645
4725
|
function rendererAppliesOpacity(layer, renderer) {
|
|
4646
4726
|
return (renderer === "arcgis" || renderer === "cesium") && layer.type === "zarr";
|
|
4647
4727
|
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Whether `layer` is an Add Vector Layer layer GeoLibre adopted
|
|
4730
|
+
* (`maplibre-gl-vector-adopted`, ADOPTED_VECTOR_SOURCE_KIND in
|
|
4731
|
+
* @geolibre/plugins) that has no features yet. A project saves a URL- or
|
|
4732
|
+
* path-backed adopted layer without them, and the vector control reads them
|
|
4733
|
+
* again on reopen. Until then its `source.url` may name GeoParquet or
|
|
4734
|
+
* GeoPackage, so a renderer must wait rather than load that URL as GeoJSON.
|
|
4735
|
+
*
|
|
4736
|
+
* @param layer - A store layer.
|
|
4737
|
+
* @returns True while the adopted layer awaits its features.
|
|
4738
|
+
*/
|
|
4739
|
+
function isAdoptedVectorAwaitingFeatures(layer) {
|
|
4740
|
+
return layer.metadata.sourceKind === "maplibre-gl-vector-adopted" && !layer.geojson;
|
|
4741
|
+
}
|
|
4648
4742
|
//#endregion
|
|
4649
4743
|
//#region src/attribute-form.ts
|
|
4650
4744
|
/** Look up the configuration for a field, if any. */
|
|
@@ -7340,6 +7434,134 @@ function scrubPrintLayoutForRemovedLayers(config, layerIds) {
|
|
|
7340
7434
|
return clearBlocksForMissingLayers(config, (id) => removed.has(id));
|
|
7341
7435
|
}
|
|
7342
7436
|
//#endregion
|
|
7437
|
+
//#region src/store/session-slice.ts
|
|
7438
|
+
/** Reserved Identify target for querying every visible queryable layer at once. */
|
|
7439
|
+
const IDENTIFY_ALL_LAYERS_ID = "__geolibre_identify_all_layers__";
|
|
7440
|
+
/**
|
|
7441
|
+
* Resolve an Identify target against the layers that exist.
|
|
7442
|
+
*
|
|
7443
|
+
* Shared by the scripting `setIdentify` command and by project load, which
|
|
7444
|
+
* applies a project's saved `interaction.identify` (issue #2688).
|
|
7445
|
+
*
|
|
7446
|
+
* @param target - A layer id, `"all"`, a list of layer ids, or null/undefined.
|
|
7447
|
+
* @param layerIds - Ids of the layers currently in the project.
|
|
7448
|
+
* @returns The store fields to set. Ids that name no layer are dropped; a list
|
|
7449
|
+
* that keeps one id becomes a single-layer target, and one that keeps several
|
|
7450
|
+
* becomes the all-layers mode restricted to those ids.
|
|
7451
|
+
*/
|
|
7452
|
+
function resolveIdentifyTarget(target, layerIds) {
|
|
7453
|
+
const off = {
|
|
7454
|
+
identifyLayerId: null,
|
|
7455
|
+
identifyLayerIds: null
|
|
7456
|
+
};
|
|
7457
|
+
if (target === null || target === void 0) return off;
|
|
7458
|
+
if (target === "all") return {
|
|
7459
|
+
identifyLayerId: IDENTIFY_ALL_LAYERS_ID,
|
|
7460
|
+
identifyLayerIds: null
|
|
7461
|
+
};
|
|
7462
|
+
const known = new Set(layerIds);
|
|
7463
|
+
if (typeof target === "string") return known.has(target) ? {
|
|
7464
|
+
identifyLayerId: target,
|
|
7465
|
+
identifyLayerIds: null
|
|
7466
|
+
} : off;
|
|
7467
|
+
const ids = [...new Set(target)].filter((id) => known.has(id));
|
|
7468
|
+
if (ids.length === 0) return off;
|
|
7469
|
+
if (ids.length === 1) return {
|
|
7470
|
+
identifyLayerId: ids[0],
|
|
7471
|
+
identifyLayerIds: null
|
|
7472
|
+
};
|
|
7473
|
+
return {
|
|
7474
|
+
identifyLayerId: IDENTIFY_ALL_LAYERS_ID,
|
|
7475
|
+
identifyLayerIds: ids
|
|
7476
|
+
};
|
|
7477
|
+
}
|
|
7478
|
+
/**
|
|
7479
|
+
* Whether the all-layers Identify mode may query a layer.
|
|
7480
|
+
*
|
|
7481
|
+
* @param layerId - The candidate layer's id.
|
|
7482
|
+
* @param identifyLayerIds - The store's `identifyLayerIds` restriction.
|
|
7483
|
+
* @returns True when there is no restriction or the layer is in it.
|
|
7484
|
+
*/
|
|
7485
|
+
function identifyAllIncludes(layerId, identifyLayerIds) {
|
|
7486
|
+
return !identifyLayerIds || identifyLayerIds.includes(layerId);
|
|
7487
|
+
}
|
|
7488
|
+
/**
|
|
7489
|
+
* Drop removed layers from the Identify state.
|
|
7490
|
+
*
|
|
7491
|
+
* @param state - The current Identify fields.
|
|
7492
|
+
* @param removed - Ids of the layers being removed.
|
|
7493
|
+
* @returns The Identify fields with those layers gone. A restricted all-layers
|
|
7494
|
+
* mode left with one layer narrows to it, and with none turns Identify off.
|
|
7495
|
+
*/
|
|
7496
|
+
function identifyStateWithoutLayers(state, removed) {
|
|
7497
|
+
if (state.identifyLayerId !== null && removed.has(state.identifyLayerId)) return {
|
|
7498
|
+
identifyLayerId: null,
|
|
7499
|
+
identifyLayerIds: null
|
|
7500
|
+
};
|
|
7501
|
+
const unchanged = {
|
|
7502
|
+
identifyLayerId: state.identifyLayerId,
|
|
7503
|
+
identifyLayerIds: state.identifyLayerIds
|
|
7504
|
+
};
|
|
7505
|
+
if (!state.identifyLayerIds) return unchanged;
|
|
7506
|
+
const ids = state.identifyLayerIds.filter((id) => !removed.has(id));
|
|
7507
|
+
if (ids.length === state.identifyLayerIds.length) return unchanged;
|
|
7508
|
+
if (ids.length === 0) return {
|
|
7509
|
+
identifyLayerId: null,
|
|
7510
|
+
identifyLayerIds: null
|
|
7511
|
+
};
|
|
7512
|
+
if (ids.length === 1) return {
|
|
7513
|
+
identifyLayerId: ids[0],
|
|
7514
|
+
identifyLayerIds: null
|
|
7515
|
+
};
|
|
7516
|
+
return {
|
|
7517
|
+
identifyLayerId: state.identifyLayerId,
|
|
7518
|
+
identifyLayerIds: ids
|
|
7519
|
+
};
|
|
7520
|
+
}
|
|
7521
|
+
const createSessionSlice = (set) => ({
|
|
7522
|
+
hoverTooltipsEnabled: true,
|
|
7523
|
+
setHoverTooltipsEnabled: (enabled) => set({ hoverTooltipsEnabled: enabled }),
|
|
7524
|
+
selectedLayerId: null,
|
|
7525
|
+
selectedFeatureId: null,
|
|
7526
|
+
selectedFeatureIds: [],
|
|
7527
|
+
identifyLayerId: null,
|
|
7528
|
+
identifyLayerIds: null,
|
|
7529
|
+
pointerCoords: null,
|
|
7530
|
+
pointerElevation: null,
|
|
7531
|
+
cameraAltitude: null,
|
|
7532
|
+
gpsStatus: null,
|
|
7533
|
+
attributeFilter: "",
|
|
7534
|
+
setPointerCoords: (coords) => set(coords ? { pointerCoords: coords } : {
|
|
7535
|
+
pointerCoords: null,
|
|
7536
|
+
pointerElevation: null
|
|
7537
|
+
}),
|
|
7538
|
+
setPointerElevation: (elevation) => set({ pointerElevation: elevation }),
|
|
7539
|
+
setCameraAltitude: (altitude) => set({ cameraAltitude: altitude }),
|
|
7540
|
+
setGpsStatus: (fix) => set({ gpsStatus: fix }),
|
|
7541
|
+
selectLayer: (id) => set({
|
|
7542
|
+
selectedLayerId: id,
|
|
7543
|
+
selectedFeatureId: null,
|
|
7544
|
+
selectedFeatureIds: []
|
|
7545
|
+
}),
|
|
7546
|
+
selectFeature: (id) => set({
|
|
7547
|
+
selectedFeatureId: id,
|
|
7548
|
+
selectedFeatureIds: id === null ? [] : [id]
|
|
7549
|
+
}),
|
|
7550
|
+
selectFeatures: (ids, anchorId) => set({
|
|
7551
|
+
selectedFeatureIds: ids,
|
|
7552
|
+
selectedFeatureId: anchorId != null && ids.includes(anchorId) ? anchorId : ids.at(-1) ?? null
|
|
7553
|
+
}),
|
|
7554
|
+
setIdentifyLayer: (id) => set({
|
|
7555
|
+
identifyLayerId: id,
|
|
7556
|
+
identifyLayerIds: null
|
|
7557
|
+
}),
|
|
7558
|
+
setIdentifyState: ({ identifyLayerId, identifyLayerIds }) => set({
|
|
7559
|
+
identifyLayerId,
|
|
7560
|
+
identifyLayerIds
|
|
7561
|
+
}),
|
|
7562
|
+
setAttributeFilter: (filter) => set({ attributeFilter: filter })
|
|
7563
|
+
});
|
|
7564
|
+
//#endregion
|
|
7343
7565
|
//#region src/store/layer-removal.ts
|
|
7344
7566
|
/**
|
|
7345
7567
|
* Reference scrubbing shared by the actions that delete layers (`removeLayer`
|
|
@@ -7485,7 +7707,10 @@ const createLayerGroupsSlice = (set, get) => ({
|
|
|
7485
7707
|
selectedLayerId: selectionRemoved ? layers[layers.length - 1]?.id ?? null : s.selectedLayerId,
|
|
7486
7708
|
selectedFeatureId: selectionRemoved ? null : s.selectedFeatureId,
|
|
7487
7709
|
selectedFeatureIds: selectionRemoved ? [] : s.selectedFeatureIds,
|
|
7488
|
-
|
|
7710
|
+
...removeChildren ? identifyStateWithoutLayers(s, removedIds) : {
|
|
7711
|
+
identifyLayerId: s.identifyLayerId,
|
|
7712
|
+
identifyLayerIds: s.identifyLayerIds
|
|
7713
|
+
},
|
|
7489
7714
|
ui: removeChildren ? {
|
|
7490
7715
|
...s.ui,
|
|
7491
7716
|
selectByExpressionLayerId: s.ui.selectByExpressionLayerId !== null && removedIds.has(s.ui.selectByExpressionLayerId) ? null : s.ui.selectByExpressionLayerId,
|
|
@@ -8342,7 +8567,7 @@ const RASTER_APPEARANCE_STATE_KEYS = [
|
|
|
8342
8567
|
"stretch",
|
|
8343
8568
|
"gamma"
|
|
8344
8569
|
];
|
|
8345
|
-
function isPlainObject$
|
|
8570
|
+
function isPlainObject$3(value) {
|
|
8346
8571
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8347
8572
|
}
|
|
8348
8573
|
/**
|
|
@@ -8379,11 +8604,11 @@ function extractCopiedLayerStyle(layer) {
|
|
|
8379
8604
|
};
|
|
8380
8605
|
const rasterState = layer.metadata.rasterState;
|
|
8381
8606
|
const rasterSymbology = layer.metadata.rasterSymbology;
|
|
8382
|
-
const hasRasterSymbology = isPlainObject$
|
|
8607
|
+
const hasRasterSymbology = isPlainObject$3(rasterSymbology);
|
|
8383
8608
|
return {
|
|
8384
8609
|
kind,
|
|
8385
8610
|
sourceName: layer.name,
|
|
8386
|
-
rasterState: isPlainObject$
|
|
8611
|
+
rasterState: isPlainObject$3(rasterState) ? structuredClone(rasterState) : void 0,
|
|
8387
8612
|
hasRasterSymbology,
|
|
8388
8613
|
...hasRasterSymbology ? { rasterSymbology: structuredClone(rasterSymbology) } : {}
|
|
8389
8614
|
};
|
|
@@ -8400,7 +8625,7 @@ function extractCopiedLayerStyle(layer) {
|
|
|
8400
8625
|
function applyCopiedLayerStyle(target, copied) {
|
|
8401
8626
|
if (copyableLayerStyleKind(target) !== copied.kind) return null;
|
|
8402
8627
|
if (copied.kind === "vector") return { style: structuredClone(copied.style) };
|
|
8403
|
-
const targetState = isPlainObject$
|
|
8628
|
+
const targetState = isPlainObject$3(target.metadata.rasterState) ? target.metadata.rasterState : {};
|
|
8404
8629
|
const source = copied.rasterState ?? {};
|
|
8405
8630
|
const expectedRescaleLength = rescaleEntryCount(targetState);
|
|
8406
8631
|
const mergedState = { ...targetState };
|
|
@@ -8427,6 +8652,202 @@ function rescaleEntryCount(state) {
|
|
|
8427
8652
|
return 1;
|
|
8428
8653
|
}
|
|
8429
8654
|
//#endregion
|
|
8655
|
+
//#region src/layer-style-file.ts
|
|
8656
|
+
/** `type` tag identifying a layer styles file. */
|
|
8657
|
+
const LAYER_STYLES_FILE_TYPE = "geolibre-layer-styles";
|
|
8658
|
+
/** Format version written by {@link serializeLayerStylesFile}. */
|
|
8659
|
+
const LAYER_STYLES_FILE_VERSION = 1;
|
|
8660
|
+
function isPlainObject$2(value) {
|
|
8661
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8662
|
+
}
|
|
8663
|
+
/**
|
|
8664
|
+
* Capture the style of every layer that has one. Layers without copyable
|
|
8665
|
+
* symbology (basemap tiles, 3D tiles, ...) are skipped. Layers that share a
|
|
8666
|
+
* name are all written, in stack order; the first one wins when the file is
|
|
8667
|
+
* applied (see {@link findLayerStyleEntryIndex}).
|
|
8668
|
+
*
|
|
8669
|
+
* @param layers - The layers to export, in store (bottom-to-top) order.
|
|
8670
|
+
* @returns The style entries.
|
|
8671
|
+
*/
|
|
8672
|
+
function extractLayerStyleEntries(layers) {
|
|
8673
|
+
const entries = [];
|
|
8674
|
+
for (const layer of layers) {
|
|
8675
|
+
const copied = extractCopiedLayerStyle(layer);
|
|
8676
|
+
if (!copied) continue;
|
|
8677
|
+
if (copied.kind === "vector") entries.push({
|
|
8678
|
+
layerName: layer.name,
|
|
8679
|
+
kind: "vector",
|
|
8680
|
+
style: copied.style
|
|
8681
|
+
});
|
|
8682
|
+
else entries.push({
|
|
8683
|
+
layerName: layer.name,
|
|
8684
|
+
kind: "raster",
|
|
8685
|
+
...copied.rasterState ? { rasterState: copied.rasterState } : {},
|
|
8686
|
+
...isPlainObject$2(copied.rasterSymbology) ? { rasterSymbology: copied.rasterSymbology } : {}
|
|
8687
|
+
});
|
|
8688
|
+
}
|
|
8689
|
+
return entries;
|
|
8690
|
+
}
|
|
8691
|
+
/**
|
|
8692
|
+
* Serialize style entries into the JSON written by Export Layer Styles.
|
|
8693
|
+
*
|
|
8694
|
+
* @param entries - The entries to write.
|
|
8695
|
+
* @returns Pretty-printed file content.
|
|
8696
|
+
*/
|
|
8697
|
+
function serializeLayerStylesFile(entries) {
|
|
8698
|
+
const file = {
|
|
8699
|
+
type: LAYER_STYLES_FILE_TYPE,
|
|
8700
|
+
version: 1,
|
|
8701
|
+
styles: [...entries]
|
|
8702
|
+
};
|
|
8703
|
+
return JSON.stringify(file, null, 2);
|
|
8704
|
+
}
|
|
8705
|
+
const RASTER_STRETCHES = [
|
|
8706
|
+
"linear",
|
|
8707
|
+
"log",
|
|
8708
|
+
"sqrt"
|
|
8709
|
+
];
|
|
8710
|
+
const RASTER_NODATA_MODES = ["off", "auto"];
|
|
8711
|
+
function isFiniteNumber(value) {
|
|
8712
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
8713
|
+
}
|
|
8714
|
+
/**
|
|
8715
|
+
* Validators for the raster appearance keys a style file may carry, by the
|
|
8716
|
+
* types `maplibre-gl-raster`'s `RasterLayerState` declares for them.
|
|
8717
|
+
*/
|
|
8718
|
+
const RASTER_APPEARANCE_VALIDATORS = {
|
|
8719
|
+
colormap: (value) => typeof value === "string" && value.length > 0,
|
|
8720
|
+
reversed: (value) => typeof value === "boolean",
|
|
8721
|
+
rescale: (value) => value === null || Array.isArray(value) && value.length > 0 && value.every((range) => Array.isArray(range) && range.length === 2 && isFiniteNumber(range[0]) && isFiniteNumber(range[1])),
|
|
8722
|
+
nodata: (value) => isFiniteNumber(value) || RASTER_NODATA_MODES.includes(value),
|
|
8723
|
+
stretch: (value) => RASTER_STRETCHES.includes(value),
|
|
8724
|
+
gamma: (value) => isFiniteNumber(value) && value > 0
|
|
8725
|
+
};
|
|
8726
|
+
/**
|
|
8727
|
+
* Keep only the raster appearance keys a style applies, each with a value of
|
|
8728
|
+
* the type the raster renderer expects. The data selection (`mode`, `bands`,
|
|
8729
|
+
* ...) is dropped too: a style never applies it.
|
|
8730
|
+
*/
|
|
8731
|
+
function sanitizeRasterAppearanceState(state) {
|
|
8732
|
+
const out = {};
|
|
8733
|
+
for (const key of RASTER_APPEARANCE_STATE_KEYS) if (key in state && RASTER_APPEARANCE_VALIDATORS[key](state[key])) out[key] = structuredClone(state[key]);
|
|
8734
|
+
return out;
|
|
8735
|
+
}
|
|
8736
|
+
/**
|
|
8737
|
+
* Coerce untrusted entries (a hand-edited file, or a copy kept in settings)
|
|
8738
|
+
* into clean ones. An entry without a name or a known kind is dropped; a
|
|
8739
|
+
* vector style is sanitized and completed against the default style, and a
|
|
8740
|
+
* raster state keeps only well-typed appearance keys, so applying an entry
|
|
8741
|
+
* never hands a renderer an unknown, missing or wrong-typed field.
|
|
8742
|
+
*
|
|
8743
|
+
* @param value - The raw `styles` array.
|
|
8744
|
+
* @returns The usable entries.
|
|
8745
|
+
*/
|
|
8746
|
+
function normalizeLayerStyleEntries(value) {
|
|
8747
|
+
if (!Array.isArray(value)) return [];
|
|
8748
|
+
const entries = [];
|
|
8749
|
+
for (const item of value) {
|
|
8750
|
+
if (!isPlainObject$2(item)) continue;
|
|
8751
|
+
const layerName = typeof item.layerName === "string" ? item.layerName : "";
|
|
8752
|
+
if (!layerName.trim()) continue;
|
|
8753
|
+
if (item.kind === "vector") {
|
|
8754
|
+
if (!isPlainObject$2(item.style)) continue;
|
|
8755
|
+
entries.push({
|
|
8756
|
+
layerName,
|
|
8757
|
+
kind: "vector",
|
|
8758
|
+
style: {
|
|
8759
|
+
...structuredClone(DEFAULT_LAYER_STYLE),
|
|
8760
|
+
...sanitizeLayerStylePatch(item.style)
|
|
8761
|
+
}
|
|
8762
|
+
});
|
|
8763
|
+
} else if (item.kind === "raster") entries.push({
|
|
8764
|
+
layerName,
|
|
8765
|
+
kind: "raster",
|
|
8766
|
+
...isPlainObject$2(item.rasterState) ? { rasterState: sanitizeRasterAppearanceState(item.rasterState) } : {},
|
|
8767
|
+
...isPlainObject$2(item.rasterSymbology) ? { rasterSymbology: structuredClone(item.rasterSymbology) } : {}
|
|
8768
|
+
});
|
|
8769
|
+
}
|
|
8770
|
+
return entries;
|
|
8771
|
+
}
|
|
8772
|
+
/**
|
|
8773
|
+
* Parse a file written by {@link serializeLayerStylesFile}.
|
|
8774
|
+
*
|
|
8775
|
+
* @param json - The file content.
|
|
8776
|
+
* @returns The normalized entries.
|
|
8777
|
+
* @throws Error when the content is not a layer styles file, comes from a
|
|
8778
|
+
* newer format version, or holds no usable entries.
|
|
8779
|
+
*/
|
|
8780
|
+
function parseLayerStylesFile(json) {
|
|
8781
|
+
let parsed;
|
|
8782
|
+
try {
|
|
8783
|
+
parsed = JSON.parse(json);
|
|
8784
|
+
} catch {
|
|
8785
|
+
throw new Error("Not a valid layer styles file (invalid JSON).");
|
|
8786
|
+
}
|
|
8787
|
+
if (!isPlainObject$2(parsed) || parsed.type !== "geolibre-layer-styles") throw new Error("Not a valid layer styles file.");
|
|
8788
|
+
if (parsed.version !== 1) throw new Error("Unsupported layer styles file version.");
|
|
8789
|
+
const entries = normalizeLayerStyleEntries(parsed.styles);
|
|
8790
|
+
if (entries.length === 0) throw new Error("The layer styles file holds no usable styles.");
|
|
8791
|
+
return entries;
|
|
8792
|
+
}
|
|
8793
|
+
function nameKey(name) {
|
|
8794
|
+
return name.trim().toLocaleLowerCase();
|
|
8795
|
+
}
|
|
8796
|
+
/**
|
|
8797
|
+
* Find the entry for a layer name: an exact match first, then one that differs
|
|
8798
|
+
* only in case or surrounding whitespace, so `Roads` still picks up a style
|
|
8799
|
+
* saved for `roads`. The first match in file order wins.
|
|
8800
|
+
*
|
|
8801
|
+
* @param entries - The style entries.
|
|
8802
|
+
* @param layerName - The layer name to look up.
|
|
8803
|
+
* @param kind - When given, only entries of this style family are considered,
|
|
8804
|
+
* so a raster style saved under a shared name does not shadow the vector one.
|
|
8805
|
+
* @returns The matching entry's index in `entries`, or -1.
|
|
8806
|
+
*/
|
|
8807
|
+
function findLayerStyleEntryIndex(entries, layerName, kind) {
|
|
8808
|
+
const ofKind = (entry) => !kind || entry.kind === kind;
|
|
8809
|
+
const exact = entries.findIndex((entry) => ofKind(entry) && entry.layerName === layerName);
|
|
8810
|
+
if (exact >= 0) return exact;
|
|
8811
|
+
const key = nameKey(layerName);
|
|
8812
|
+
return entries.findIndex((entry) => ofKind(entry) && nameKey(entry.layerName) === key);
|
|
8813
|
+
}
|
|
8814
|
+
function toCopiedLayerStyle(entry) {
|
|
8815
|
+
if (entry.kind === "vector") return {
|
|
8816
|
+
kind: "vector",
|
|
8817
|
+
sourceName: entry.layerName,
|
|
8818
|
+
style: entry.style
|
|
8819
|
+
};
|
|
8820
|
+
return {
|
|
8821
|
+
kind: "raster",
|
|
8822
|
+
sourceName: entry.layerName,
|
|
8823
|
+
rasterState: entry.rasterState,
|
|
8824
|
+
hasRasterSymbology: entry.rasterSymbology !== void 0,
|
|
8825
|
+
...entry.rasterSymbology !== void 0 ? { rasterSymbology: entry.rasterSymbology } : {}
|
|
8826
|
+
};
|
|
8827
|
+
}
|
|
8828
|
+
/**
|
|
8829
|
+
* Build the patch that restyles a layer from the entry matching its name, with
|
|
8830
|
+
* the same semantics as pasting a copied style: a vector style replaces the
|
|
8831
|
+
* whole style bag, a raster style merges its appearance keys and keeps the
|
|
8832
|
+
* layer's own band selection, and layer opacity is left alone.
|
|
8833
|
+
*
|
|
8834
|
+
* @param layer - The layer to restyle.
|
|
8835
|
+
* @param entries - The style entries.
|
|
8836
|
+
* @returns The matched entry and patch, or `null` when no entry of the layer's
|
|
8837
|
+
* style family matches its name (or the layer has no stylable symbology).
|
|
8838
|
+
*/
|
|
8839
|
+
function matchLayerStyleEntry(layer, entries) {
|
|
8840
|
+
const kind = copyableLayerStyleKind(layer);
|
|
8841
|
+
if (!kind) return null;
|
|
8842
|
+
const entryIndex = findLayerStyleEntryIndex(entries, layer.name, kind);
|
|
8843
|
+
if (entryIndex < 0) return null;
|
|
8844
|
+
const patch = applyCopiedLayerStyle(layer, toCopiedLayerStyle(entries[entryIndex]));
|
|
8845
|
+
return patch ? {
|
|
8846
|
+
entryIndex,
|
|
8847
|
+
patch
|
|
8848
|
+
} : null;
|
|
8849
|
+
}
|
|
8850
|
+
//#endregion
|
|
8430
8851
|
//#region src/store/layers-slice.ts
|
|
8431
8852
|
/**
|
|
8432
8853
|
* The layer types {@link AppState.addTileLayer} accepts. Each renders through
|
|
@@ -8476,7 +8897,7 @@ const createLayersSlice = (set, get) => ({
|
|
|
8476
8897
|
selectedLayerId: s.selectedLayerId === id ? s.layers.find((l) => l.id !== id)?.id ?? null : s.selectedLayerId,
|
|
8477
8898
|
selectedFeatureId: s.selectedLayerId === id ? null : s.selectedFeatureId,
|
|
8478
8899
|
selectedFeatureIds: s.selectedLayerId === id ? [] : s.selectedFeatureIds,
|
|
8479
|
-
|
|
8900
|
+
...identifyStateWithoutLayers(s, /* @__PURE__ */ new Set([id])),
|
|
8480
8901
|
ui: {
|
|
8481
8902
|
...s.ui,
|
|
8482
8903
|
selectByExpressionLayerId: s.ui.selectByExpressionLayerId === id ? null : s.ui.selectByExpressionLayerId,
|
|
@@ -8551,6 +8972,29 @@ const createLayersSlice = (set, get) => ({
|
|
|
8551
8972
|
get().updateLayer(id, patch);
|
|
8552
8973
|
return true;
|
|
8553
8974
|
},
|
|
8975
|
+
applyLayerStyleEntries: (entries, layerIds) => {
|
|
8976
|
+
if (entries.length === 0) return [];
|
|
8977
|
+
const scope = layerIds ? new Set(layerIds) : null;
|
|
8978
|
+
const applied = [];
|
|
8979
|
+
const layers = get().layers.map((layer) => {
|
|
8980
|
+
if (scope && !scope.has(layer.id)) return layer;
|
|
8981
|
+
const match = matchLayerStyleEntry(layer, entries);
|
|
8982
|
+
if (!match) return layer;
|
|
8983
|
+
applied.push({
|
|
8984
|
+
layerId: layer.id,
|
|
8985
|
+
entryIndex: match.entryIndex
|
|
8986
|
+
});
|
|
8987
|
+
return {
|
|
8988
|
+
...layer,
|
|
8989
|
+
...match.patch
|
|
8990
|
+
};
|
|
8991
|
+
});
|
|
8992
|
+
if (applied.length > 0) set({
|
|
8993
|
+
layers,
|
|
8994
|
+
isDirty: true
|
|
8995
|
+
});
|
|
8996
|
+
return applied;
|
|
8997
|
+
},
|
|
8554
8998
|
reorderLayer: (id, direction) => set((s) => {
|
|
8555
8999
|
const idx = s.layers.findIndex((l) => l.id === id);
|
|
8556
9000
|
if (idx < 0) return s;
|
|
@@ -9043,6 +9487,82 @@ function createCesiumIonLayer(options) {
|
|
|
9043
9487
|
};
|
|
9044
9488
|
}
|
|
9045
9489
|
//#endregion
|
|
9490
|
+
//#region src/header-references.ts
|
|
9491
|
+
/**
|
|
9492
|
+
* `${NAME}` references in request header values.
|
|
9493
|
+
*
|
|
9494
|
+
* A header such as `Authorization: Bearer ${TILES_TOKEN}` is saved as typed
|
|
9495
|
+
* and resolved from the project's Environment Variables when a request is
|
|
9496
|
+
* made, so the secret itself never sits on the layer. This module is pure (no
|
|
9497
|
+
* store import): `credentials.ts` depends on it, and the store depends on
|
|
9498
|
+
* `credentials.ts`.
|
|
9499
|
+
*/
|
|
9500
|
+
/** `${NAME}` where NAME follows the environment-variable name rule. */
|
|
9501
|
+
const HEADER_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
9502
|
+
const HAS_HEADER_REFERENCE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
9503
|
+
/**
|
|
9504
|
+
* Optional auth scheme word, whitespace, one reference, nothing else. Kept in
|
|
9505
|
+
* lockstep with `_HEADER_REFERENCE_ONLY` in `python/src/geolibre/project.py`.
|
|
9506
|
+
*/
|
|
9507
|
+
const REFERENCE_ONLY = /^(?:[A-Za-z][A-Za-z0-9._-]*\s+)?\$\{[A-Za-z_][A-Za-z0-9_]*\}$/;
|
|
9508
|
+
const warnedMissing = /* @__PURE__ */ new Set();
|
|
9509
|
+
function hasHeaderReferences(headers) {
|
|
9510
|
+
if (!headers) return false;
|
|
9511
|
+
return Object.values(headers).some((value) => typeof value === "string" && HAS_HEADER_REFERENCE.test(value));
|
|
9512
|
+
}
|
|
9513
|
+
/**
|
|
9514
|
+
* Whether `value` carries no secret of its own: at most a scheme word such as
|
|
9515
|
+
* `Bearer` followed by a single `${NAME}`. Redaction keeps such values.
|
|
9516
|
+
*/
|
|
9517
|
+
function isHeaderReferenceOnly(value) {
|
|
9518
|
+
return REFERENCE_ONLY.test(value.trim());
|
|
9519
|
+
}
|
|
9520
|
+
/**
|
|
9521
|
+
* Whether credential-bearing request headers may be sent to `url`: HTTPS, or
|
|
9522
|
+
* plain HTTP to loopback so a local dev server still works. The scheme is read
|
|
9523
|
+
* off a parsed URL, so an unusually cased `HTTPS://` is not misread as
|
|
9524
|
+
* plaintext; a relative or unparseable URL is refused.
|
|
9525
|
+
*/
|
|
9526
|
+
function allowsCredentialHeaders(url) {
|
|
9527
|
+
try {
|
|
9528
|
+
const { protocol, hostname } = new URL(url);
|
|
9529
|
+
if (protocol === "https:") return true;
|
|
9530
|
+
return protocol === "http:" && (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]");
|
|
9531
|
+
} catch {
|
|
9532
|
+
return false;
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
/**
|
|
9536
|
+
* `headers` with every `${NAME}` replaced by `values[NAME]`. A header that
|
|
9537
|
+
* references an unset (or empty) variable is omitted rather than sent with a
|
|
9538
|
+
* hole in it. Returns the input unchanged when nothing references a variable,
|
|
9539
|
+
* and `undefined` when no header remains.
|
|
9540
|
+
*/
|
|
9541
|
+
function resolveHeaderReferences(headers, values) {
|
|
9542
|
+
if (!headers) return void 0;
|
|
9543
|
+
if (!Object.values(headers).some((value) => typeof value === "string" && value.includes("${"))) return headers;
|
|
9544
|
+
const resolved = {};
|
|
9545
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
9546
|
+
if (typeof value !== "string") continue;
|
|
9547
|
+
let missing = null;
|
|
9548
|
+
const next = value.replace(HEADER_REFERENCE, (_match, variable) => {
|
|
9549
|
+
const replacement = Object.hasOwn(values, variable) ? values[variable] : "";
|
|
9550
|
+
if (!replacement) missing ??= variable;
|
|
9551
|
+
return replacement;
|
|
9552
|
+
});
|
|
9553
|
+
if (missing !== null) {
|
|
9554
|
+
const warnKey = `${name}:${missing}`;
|
|
9555
|
+
if (!warnedMissing.has(warnKey)) {
|
|
9556
|
+
warnedMissing.add(warnKey);
|
|
9557
|
+
console.warn(`[GeoLibre] Request header "${name}" references unset environment variable ${missing}; the header is not sent.`);
|
|
9558
|
+
}
|
|
9559
|
+
continue;
|
|
9560
|
+
}
|
|
9561
|
+
resolved[name] = next;
|
|
9562
|
+
}
|
|
9563
|
+
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
9564
|
+
}
|
|
9565
|
+
//#endregion
|
|
9046
9566
|
//#region src/credentials.ts
|
|
9047
9567
|
/**
|
|
9048
9568
|
* Project fields whose values are credentials. Keep this registry as the
|
|
@@ -9095,7 +9615,8 @@ const PUBLISHABLE_PLUGIN_SETTINGS = {
|
|
|
9095
9615
|
"maplibre-gl-swipe": null,
|
|
9096
9616
|
"maplibre-gl-components": ["legend", "colorbar"],
|
|
9097
9617
|
"maplibre-gl-time-slider": null,
|
|
9098
|
-
"gods-eye-view": null
|
|
9618
|
+
"gods-eye-view": null,
|
|
9619
|
+
"geolibre-point-cloud-annotation": null
|
|
9099
9620
|
};
|
|
9100
9621
|
/**
|
|
9101
9622
|
* Hash a credential value to a short, stable 64-bit token: two independent
|
|
@@ -9234,6 +9755,16 @@ function redactConfigurationValue(value, path, accumulator, depth = 0) {
|
|
|
9234
9755
|
const result = {};
|
|
9235
9756
|
for (const [key, nested] of Object.entries(value)) {
|
|
9236
9757
|
const nestedPath = path ? `${path}.${key}` : key;
|
|
9758
|
+
const normalizedKey = normalizeCredentialName(key);
|
|
9759
|
+
if ((normalizedKey === "requestheaders" || normalizedKey === "headers") && isPlainObject$1(nested)) {
|
|
9760
|
+
const kept = {};
|
|
9761
|
+
const removed = {};
|
|
9762
|
+
for (const [name, headerValue] of Object.entries(nested)) if (typeof headerValue === "string" && isHeaderReferenceOnly(headerValue)) kept[name] = headerValue;
|
|
9763
|
+
else removed[name] = headerValue;
|
|
9764
|
+
if (Object.keys(removed).length > 0) recordRedaction(accumulator, nestedPath, removed);
|
|
9765
|
+
if (Object.keys(kept).length > 0) result[key] = kept;
|
|
9766
|
+
continue;
|
|
9767
|
+
}
|
|
9237
9768
|
if (isCredentialFieldName(key)) {
|
|
9238
9769
|
recordRedaction(accumulator, nestedPath, nested);
|
|
9239
9770
|
continue;
|
|
@@ -9288,14 +9819,14 @@ function redactProjectCredentials(project) {
|
|
|
9288
9819
|
if (redactedMapboxStyleUrl !== mapboxStyleUrl) recordRedaction(accumulator, "preferences.map.mapboxStyleUrl", mapboxStyleUrl);
|
|
9289
9820
|
const preferences = project.preferences ? {
|
|
9290
9821
|
...project.preferences,
|
|
9291
|
-
environmentVariables: [],
|
|
9822
|
+
environmentVariables: (project.preferences.environmentVariables ?? []).filter((variable) => variable.secret === false),
|
|
9292
9823
|
geocoding,
|
|
9293
9824
|
...redactedMapboxStyleUrl !== mapboxStyleUrl ? { map: {
|
|
9294
9825
|
...project.preferences.map,
|
|
9295
9826
|
mapboxStyleUrl: redactedMapboxStyleUrl
|
|
9296
9827
|
} } : {}
|
|
9297
9828
|
} : project.preferences;
|
|
9298
|
-
const populatedEnvironmentVariables = project.preferences?.environmentVariables?.filter((variable) => variable.
|
|
9829
|
+
const populatedEnvironmentVariables = project.preferences?.environmentVariables?.filter((variable) => variable.secret !== false && variable.value !== "") ?? [];
|
|
9299
9830
|
if (populatedEnvironmentVariables.length > 0) recordRedaction(accumulator, "preferences.environmentVariables", populatedEnvironmentVariables, populatedEnvironmentVariables.length);
|
|
9300
9831
|
const geocodingApiKeys = project.preferences?.geocoding?.apiKeys ?? {};
|
|
9301
9832
|
if (Object.keys(geocodingApiKeys).length > 0) recordRedaction(accumulator, "preferences.geocoding.apiKeys", geocodingApiKeys, Object.keys(geocodingApiKeys).length);
|
|
@@ -10280,6 +10811,7 @@ function parseProject(json) {
|
|
|
10280
10811
|
const { mapLayout, secondaryMapViews } = resolveMapGrid(normalizeMapLayout(data.mapLayout), normalizeSecondaryMapViews(data.secondaryMapViews), { mapView });
|
|
10281
10812
|
const styleLibrary = normalizeStyleLibraryEntries(data.styleLibrary);
|
|
10282
10813
|
const parsedComments = normalizeProjectComments(data.comments);
|
|
10814
|
+
const parsedInteraction = normalizeProjectInteraction(data.interaction);
|
|
10283
10815
|
return {
|
|
10284
10816
|
version: data.version,
|
|
10285
10817
|
name: data.name,
|
|
@@ -10309,6 +10841,7 @@ function parseProject(json) {
|
|
|
10309
10841
|
...normalizePrimaryRenderer(data.primaryRenderer) ? { primaryRenderer: normalizePrimaryRenderer(data.primaryRenderer) } : {},
|
|
10310
10842
|
...styleLibrary.length > 0 ? { styleLibrary } : {},
|
|
10311
10843
|
...parsedComments.length > 0 ? { comments: parsedComments } : {},
|
|
10844
|
+
...parsedInteraction ? { interaction: parsedInteraction } : {},
|
|
10312
10845
|
metadata: data.metadata ?? {}
|
|
10313
10846
|
};
|
|
10314
10847
|
}
|
|
@@ -10969,6 +11502,34 @@ function normalizeDashboardColumns(value) {
|
|
|
10969
11502
|
if (typeof value !== "number" || !Number.isFinite(value)) return 2;
|
|
10970
11503
|
return Math.max(1, Math.min(6, Math.trunc(value)));
|
|
10971
11504
|
}
|
|
11505
|
+
/**
|
|
11506
|
+
* Coerce an untrusted `interaction` block into a {@link ProjectInteraction}.
|
|
11507
|
+
*
|
|
11508
|
+
* Layer ids are kept even when no such layer exists: the block is replayed
|
|
11509
|
+
* against whatever layers the project has when it opens, and the loader drops
|
|
11510
|
+
* the ones it cannot find. Control names are kept as written for the same
|
|
11511
|
+
* reason; the app ignores names it does not know.
|
|
11512
|
+
*
|
|
11513
|
+
* @param value - Raw `interaction` value from the project JSON or the store.
|
|
11514
|
+
* @returns The normalized block, or null when it carries nothing to apply.
|
|
11515
|
+
*/
|
|
11516
|
+
function normalizeProjectInteraction(value) {
|
|
11517
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
11518
|
+
const raw = value;
|
|
11519
|
+
const result = {};
|
|
11520
|
+
if (raw.identify === null) result.identify = null;
|
|
11521
|
+
else if (typeof raw.identify === "string" && raw.identify) result.identify = raw.identify;
|
|
11522
|
+
else if (Array.isArray(raw.identify)) {
|
|
11523
|
+
const ids = [...new Set(raw.identify.filter((id) => typeof id === "string" && !!id))];
|
|
11524
|
+
if (ids.length > 0) result.identify = ids;
|
|
11525
|
+
}
|
|
11526
|
+
if (raw.controls && typeof raw.controls === "object" && !Array.isArray(raw.controls)) {
|
|
11527
|
+
const controls = {};
|
|
11528
|
+
for (const [name, shown] of Object.entries(raw.controls)) if (name && typeof shown === "boolean") controls[name] = shown;
|
|
11529
|
+
if (Object.keys(controls).length > 0) result.controls = controls;
|
|
11530
|
+
}
|
|
11531
|
+
return "identify" in result || result.controls ? result : null;
|
|
11532
|
+
}
|
|
10972
11533
|
function normalizeProjectPreferences(preferences) {
|
|
10973
11534
|
if (!preferences || typeof preferences !== "object") return DEFAULT_PROJECT_PREFERENCES;
|
|
10974
11535
|
const candidate = preferences;
|
|
@@ -11055,7 +11616,8 @@ function normalizeEnvironmentVariable(variable) {
|
|
|
11055
11616
|
return {
|
|
11056
11617
|
key,
|
|
11057
11618
|
value: typeof candidate.value === "string" ? candidate.value : "",
|
|
11058
|
-
enabled: normalizeBoolean(candidate.enabled, true)
|
|
11619
|
+
enabled: normalizeBoolean(candidate.enabled, true),
|
|
11620
|
+
...candidate.secret === false ? { secret: false } : {}
|
|
11059
11621
|
};
|
|
11060
11622
|
}
|
|
11061
11623
|
const PROJECT_PLUGIN_CONTROL_POSITIONS = /* @__PURE__ */ new Set([
|
|
@@ -11251,6 +11813,7 @@ function projectFromStore(state) {
|
|
|
11251
11813
|
const processingHistory = normalizeProcessingHistory(state.processingHistory);
|
|
11252
11814
|
const widgets = normalizeWidgets(state.widgets);
|
|
11253
11815
|
const comments = normalizeProjectComments(state.comments);
|
|
11816
|
+
const interaction = normalizeProjectInteraction(state.interaction);
|
|
11254
11817
|
const dashboardColumns = state.dashboardColumns === void 0 ? 2 : normalizeDashboardColumns(state.dashboardColumns);
|
|
11255
11818
|
const layerGroups = state.layerGroups ?? [];
|
|
11256
11819
|
const { mapLayout, secondaryMapViews } = resolveMapGrid(normalizeMapLayout(state.mapLayout), normalizeSecondaryMapViews(state.secondaryMapViews), { mapView: state.mapView });
|
|
@@ -11286,6 +11849,7 @@ function projectFromStore(state) {
|
|
|
11286
11849
|
...normalizePrimaryRenderer(state.primaryRenderer) ? { primaryRenderer: normalizePrimaryRenderer(state.primaryRenderer) } : {},
|
|
11287
11850
|
...styleLibrary.length > 0 ? { styleLibrary } : {},
|
|
11288
11851
|
...comments.length > 0 ? { comments } : {},
|
|
11852
|
+
...interaction ? { interaction } : {},
|
|
11289
11853
|
metadata: state.metadata
|
|
11290
11854
|
};
|
|
11291
11855
|
}
|
|
@@ -11327,6 +11891,10 @@ function prepareLayerForSave(layer) {
|
|
|
11327
11891
|
const { geojson: _geojson, ...rest } = layer;
|
|
11328
11892
|
layer = rest;
|
|
11329
11893
|
}
|
|
11894
|
+
if (layer.geojson && layer.metadata.sourceKind === "maplibre-gl-vector-adopted" && hasRestorableSourceUrl(layer)) {
|
|
11895
|
+
const { geojson: _geojson, ...rest } = layer;
|
|
11896
|
+
layer = rest;
|
|
11897
|
+
}
|
|
11330
11898
|
if (layer.geojson && layer.metadata.localFileReloadable === true) {
|
|
11331
11899
|
const { geojson: _geojson, ...rest } = layer;
|
|
11332
11900
|
layer = rest;
|
|
@@ -11447,6 +12015,7 @@ function applyProjectToStore(project) {
|
|
|
11447
12015
|
primaryRenderer: normalizePrimaryRenderer(project.primaryRenderer) ?? "maplibre",
|
|
11448
12016
|
projectStyleLibrary: normalizeStyleLibraryEntries(project.styleLibrary),
|
|
11449
12017
|
comments: scrubbedComments,
|
|
12018
|
+
projectInteraction: normalizeProjectInteraction(project.interaction),
|
|
11450
12019
|
metadata: project.metadata
|
|
11451
12020
|
};
|
|
11452
12021
|
}
|
|
@@ -11789,6 +12358,7 @@ const createProjectContentSlice = (set) => ({
|
|
|
11789
12358
|
widgets: [],
|
|
11790
12359
|
dashboardColumns: 2,
|
|
11791
12360
|
comments: [],
|
|
12361
|
+
projectInteraction: null,
|
|
11792
12362
|
addComment: (comment) => set((s) => {
|
|
11793
12363
|
if (s.comments.some((c) => c.id === comment.id)) return s;
|
|
11794
12364
|
return {
|
|
@@ -12368,6 +12938,7 @@ const createProjectSlice = (set, get) => ({
|
|
|
12368
12938
|
const applied = applyProjectToStore(createEmptyProject(options.name, options));
|
|
12369
12939
|
set((s) => ({
|
|
12370
12940
|
...applied,
|
|
12941
|
+
hoverTooltipsEnabled: true,
|
|
12371
12942
|
projectPath: null,
|
|
12372
12943
|
projectGeneration: s.projectGeneration + 1,
|
|
12373
12944
|
isDirty: false,
|
|
@@ -12375,6 +12946,7 @@ const createProjectSlice = (set, get) => ({
|
|
|
12375
12946
|
selectedFeatureId: null,
|
|
12376
12947
|
selectedFeatureIds: [],
|
|
12377
12948
|
identifyLayerId: null,
|
|
12949
|
+
identifyLayerIds: null,
|
|
12378
12950
|
copiedLayerStyle: null,
|
|
12379
12951
|
pointerCoords: null,
|
|
12380
12952
|
pointerElevation: null,
|
|
@@ -12405,13 +12977,14 @@ const createProjectSlice = (set, get) => ({
|
|
|
12405
12977
|
const selectedLayerId = project.selectedLayerId === null ? null : typeof project.selectedLayerId === "string" && applied.layers.some((layer) => layer.id === project.selectedLayerId) ? project.selectedLayerId : applied.layers[0]?.id ?? null;
|
|
12406
12978
|
set((s) => ({
|
|
12407
12979
|
...applied,
|
|
12980
|
+
hoverTooltipsEnabled: true,
|
|
12408
12981
|
projectPath: path,
|
|
12409
12982
|
projectGeneration: s.projectGeneration + 1,
|
|
12410
12983
|
isDirty: false,
|
|
12411
12984
|
selectedLayerId,
|
|
12412
12985
|
selectedFeatureId: null,
|
|
12413
12986
|
selectedFeatureIds: [],
|
|
12414
|
-
|
|
12987
|
+
...resolveIdentifyTarget(applied.projectInteraction?.identify, applied.layers.map((layer) => layer.id)),
|
|
12415
12988
|
copiedLayerStyle: null,
|
|
12416
12989
|
pointerCoords: null,
|
|
12417
12990
|
pointerElevation: null,
|
|
@@ -12441,43 +13014,6 @@ const createProjectSlice = (set, get) => ({
|
|
|
12441
13014
|
}
|
|
12442
13015
|
});
|
|
12443
13016
|
//#endregion
|
|
12444
|
-
//#region src/store/session-slice.ts
|
|
12445
|
-
/** Reserved Identify target for querying every visible queryable layer at once. */
|
|
12446
|
-
const IDENTIFY_ALL_LAYERS_ID = "__geolibre_identify_all_layers__";
|
|
12447
|
-
const createSessionSlice = (set) => ({
|
|
12448
|
-
selectedLayerId: null,
|
|
12449
|
-
selectedFeatureId: null,
|
|
12450
|
-
selectedFeatureIds: [],
|
|
12451
|
-
identifyLayerId: null,
|
|
12452
|
-
pointerCoords: null,
|
|
12453
|
-
pointerElevation: null,
|
|
12454
|
-
cameraAltitude: null,
|
|
12455
|
-
gpsStatus: null,
|
|
12456
|
-
attributeFilter: "",
|
|
12457
|
-
setPointerCoords: (coords) => set(coords ? { pointerCoords: coords } : {
|
|
12458
|
-
pointerCoords: null,
|
|
12459
|
-
pointerElevation: null
|
|
12460
|
-
}),
|
|
12461
|
-
setPointerElevation: (elevation) => set({ pointerElevation: elevation }),
|
|
12462
|
-
setCameraAltitude: (altitude) => set({ cameraAltitude: altitude }),
|
|
12463
|
-
setGpsStatus: (fix) => set({ gpsStatus: fix }),
|
|
12464
|
-
selectLayer: (id) => set({
|
|
12465
|
-
selectedLayerId: id,
|
|
12466
|
-
selectedFeatureId: null,
|
|
12467
|
-
selectedFeatureIds: []
|
|
12468
|
-
}),
|
|
12469
|
-
selectFeature: (id) => set({
|
|
12470
|
-
selectedFeatureId: id,
|
|
12471
|
-
selectedFeatureIds: id === null ? [] : [id]
|
|
12472
|
-
}),
|
|
12473
|
-
selectFeatures: (ids, anchorId) => set({
|
|
12474
|
-
selectedFeatureIds: ids,
|
|
12475
|
-
selectedFeatureId: anchorId != null && ids.includes(anchorId) ? anchorId : ids.at(-1) ?? null
|
|
12476
|
-
}),
|
|
12477
|
-
setIdentifyLayer: (id) => set({ identifyLayerId: id }),
|
|
12478
|
-
setAttributeFilter: (filter) => set({ attributeFilter: filter })
|
|
12479
|
-
});
|
|
12480
|
-
//#endregion
|
|
12481
13017
|
//#region src/store/ui-slice.ts
|
|
12482
13018
|
const createUiSlice = (set) => ({
|
|
12483
13019
|
ui: {
|
|
@@ -12825,20 +13361,36 @@ var ElevationFetchError = class extends Error {
|
|
|
12825
13361
|
}
|
|
12826
13362
|
};
|
|
12827
13363
|
/**
|
|
13364
|
+
* Normalize longitude into [-180, 180] degrees (handles unwrapped coordinates
|
|
13365
|
+
* from antimeridian cross-overs and world-copy pannings).
|
|
13366
|
+
*/
|
|
13367
|
+
function normalizeLongitude(lng) {
|
|
13368
|
+
if (!Number.isFinite(lng)) return 0;
|
|
13369
|
+
return ((lng + 180) % 360 + 360) % 360 - 180;
|
|
13370
|
+
}
|
|
13371
|
+
/**
|
|
13372
|
+
* Clamp latitude into [-90, 90] degrees.
|
|
13373
|
+
*/
|
|
13374
|
+
function clampLatitude(lat) {
|
|
13375
|
+
if (!Number.isFinite(lat)) return 0;
|
|
13376
|
+
return Math.min(90, Math.max(-90, lat));
|
|
13377
|
+
}
|
|
13378
|
+
/**
|
|
12828
13379
|
* Fetch elevations (in meters) for an ordered list of coordinates.
|
|
12829
13380
|
*
|
|
12830
13381
|
* @param points - Coordinates as `[lng, lat]`, at most {@link MAX_POINTS_PER_REQUEST}
|
|
12831
13382
|
* @param fetchImpl - Optional `fetch` implementation; defaults to the global `fetch`
|
|
12832
13383
|
* @returns The elevation in meters for each input coordinate, in the same order
|
|
12833
|
-
* @throws {ElevationFetchError} On too many points, a
|
|
12834
|
-
* response, a malformed body, or a length mismatch
|
|
13384
|
+
* @throws {ElevationFetchError} On too many points, a non-finite coordinate, a
|
|
13385
|
+
* network error, a non-2xx response, a malformed body, or a length mismatch
|
|
12835
13386
|
*/
|
|
12836
13387
|
async function fetchElevations(points, fetchImpl) {
|
|
12837
13388
|
if (points.length === 0) return [];
|
|
12838
13389
|
if (points.length > 100) throw new ElevationFetchError(`Too many points: ${points.length} (max 100).`);
|
|
13390
|
+
if (points.some(([lng, lat]) => !Number.isFinite(lng) || !Number.isFinite(lat))) throw new ElevationFetchError("Invalid coordinate: longitude and latitude must be finite.");
|
|
12839
13391
|
const doFetch = fetchImpl ?? ((url, init) => fetch(url, init));
|
|
12840
|
-
const latitudes = points.map((p) => p[1].toFixed(6)).join(",");
|
|
12841
|
-
const longitudes = points.map((p) => p[0].toFixed(6)).join(",");
|
|
13392
|
+
const latitudes = points.map((p) => clampLatitude(p[1]).toFixed(6)).join(",");
|
|
13393
|
+
const longitudes = points.map((p) => normalizeLongitude(p[0]).toFixed(6)).join(",");
|
|
12842
13394
|
const url = `${ENDPOINT}?latitude=${latitudes}&longitude=${longitudes}`;
|
|
12843
13395
|
const controller = new AbortController();
|
|
12844
13396
|
const timeoutId = setTimeout(() => controller.abort(), ELEVATION_REQUEST_TIMEOUT_MS);
|
|
@@ -13059,6 +13611,408 @@ function formatPixelValue(value) {
|
|
|
13059
13611
|
return String(Number(value.toPrecision(6)));
|
|
13060
13612
|
}
|
|
13061
13613
|
//#endregion
|
|
13614
|
+
//#region src/s3.ts
|
|
13615
|
+
/** SigV4 allows a presigned URL to live at most seven days. */
|
|
13616
|
+
const S3_MAX_PRESIGN_SECONDS = 604800;
|
|
13617
|
+
const S3_SCHEME = /^s3a?:\/\//i;
|
|
13618
|
+
const VIRTUAL_HOST = /^(.+)\.s3(?:[.-](?:dualstack\.)?([a-z0-9-]+))?\.amazonaws\.com(?:\.cn)?$/i;
|
|
13619
|
+
const PATH_HOST = /^s3(?:[.-](?:dualstack\.)?([a-z0-9-]+))?\.amazonaws\.com(?:\.cn)?$/i;
|
|
13620
|
+
function regionFromHost(value) {
|
|
13621
|
+
if (!value || value === "external-1" || value === "dualstack") return void 0;
|
|
13622
|
+
return value.toLowerCase();
|
|
13623
|
+
}
|
|
13624
|
+
/** Static-website endpoints serve HTML, not the REST API a signature targets. */
|
|
13625
|
+
function isWebsiteRegion(value) {
|
|
13626
|
+
return Boolean(value && /^website/i.test(value));
|
|
13627
|
+
}
|
|
13628
|
+
function decodeKey(path) {
|
|
13629
|
+
try {
|
|
13630
|
+
return decodeURIComponent(path);
|
|
13631
|
+
} catch {
|
|
13632
|
+
return path;
|
|
13633
|
+
}
|
|
13634
|
+
}
|
|
13635
|
+
/**
|
|
13636
|
+
* Parses an `s3://bucket/key` URI or an AWS S3 HTTPS object URL
|
|
13637
|
+
* (virtual-hosted or path-style, any region) into its bucket and key.
|
|
13638
|
+
* Query strings and fragments are ignored, so a presigned URL parses to the
|
|
13639
|
+
* object it signs.
|
|
13640
|
+
*
|
|
13641
|
+
* @param url The URL to parse.
|
|
13642
|
+
* @returns The bucket and key, or null when the URL is not an S3 object URL.
|
|
13643
|
+
*/
|
|
13644
|
+
function parseS3Url(url) {
|
|
13645
|
+
const trimmed = url.trim();
|
|
13646
|
+
if (S3_SCHEME.test(trimmed)) {
|
|
13647
|
+
const rest = trimmed.replace(S3_SCHEME, "").split(/[?#]/)[0];
|
|
13648
|
+
const slash = rest.indexOf("/");
|
|
13649
|
+
const bucket = slash === -1 ? rest : rest.slice(0, slash);
|
|
13650
|
+
const key = slash === -1 ? "" : rest.slice(slash + 1);
|
|
13651
|
+
return isSafeBucketName(bucket) ? {
|
|
13652
|
+
bucket,
|
|
13653
|
+
key
|
|
13654
|
+
} : null;
|
|
13655
|
+
}
|
|
13656
|
+
let parsed;
|
|
13657
|
+
try {
|
|
13658
|
+
parsed = new URL(trimmed);
|
|
13659
|
+
} catch {
|
|
13660
|
+
return null;
|
|
13661
|
+
}
|
|
13662
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
13663
|
+
const host = parsed.hostname.toLowerCase();
|
|
13664
|
+
const path = parsed.pathname.replace(/^\//, "");
|
|
13665
|
+
const virtual = host.match(VIRTUAL_HOST);
|
|
13666
|
+
if (virtual && !PATH_HOST.test(host)) {
|
|
13667
|
+
if (isWebsiteRegion(virtual[2])) return null;
|
|
13668
|
+
const region = regionFromHost(virtual[2]);
|
|
13669
|
+
return {
|
|
13670
|
+
bucket: virtual[1],
|
|
13671
|
+
key: decodeKey(path),
|
|
13672
|
+
...region ? { region } : {}
|
|
13673
|
+
};
|
|
13674
|
+
}
|
|
13675
|
+
const pathStyle = host.match(PATH_HOST);
|
|
13676
|
+
if (pathStyle) {
|
|
13677
|
+
if (isWebsiteRegion(pathStyle[1])) return null;
|
|
13678
|
+
const slash = path.indexOf("/");
|
|
13679
|
+
const bucket = slash === -1 ? path : path.slice(0, slash);
|
|
13680
|
+
if (!bucket) return null;
|
|
13681
|
+
const region = regionFromHost(pathStyle[1]);
|
|
13682
|
+
return {
|
|
13683
|
+
bucket: decodeKey(bucket),
|
|
13684
|
+
key: slash === -1 ? "" : decodeKey(path.slice(slash + 1)),
|
|
13685
|
+
...region ? { region } : {}
|
|
13686
|
+
};
|
|
13687
|
+
}
|
|
13688
|
+
return null;
|
|
13689
|
+
}
|
|
13690
|
+
/**
|
|
13691
|
+
* Bucket names are letters, digits, dots, dashes, and underscores (the last
|
|
13692
|
+
* two outside AWS's own rules, for S3-compatible stores). Anything else, a
|
|
13693
|
+
* backslash or `@` say, could move a virtual-hosted URL's authority to
|
|
13694
|
+
* another host before a signature is attached, so it is refused outright.
|
|
13695
|
+
*/
|
|
13696
|
+
const SAFE_BUCKET_NAME = /^[A-Za-z0-9._-]{1,255}$/;
|
|
13697
|
+
function isSafeBucketName(bucket) {
|
|
13698
|
+
return SAFE_BUCKET_NAME.test(bucket);
|
|
13699
|
+
}
|
|
13700
|
+
/** Whether `url` is an `s3://` URI (as opposed to an HTTPS object URL). */
|
|
13701
|
+
function isS3Uri(url) {
|
|
13702
|
+
return S3_SCHEME.test(url.trim());
|
|
13703
|
+
}
|
|
13704
|
+
/** Formats a location as an `s3://bucket/key` URI. */
|
|
13705
|
+
function formatS3Uri(location) {
|
|
13706
|
+
return `s3://${location.bucket}/${location.key}`;
|
|
13707
|
+
}
|
|
13708
|
+
/**
|
|
13709
|
+
* URI-encodes a key the way SigV4 canonicalizes an S3 path: every byte except
|
|
13710
|
+
* the unreserved set and `/` is percent-encoded, once.
|
|
13711
|
+
*/
|
|
13712
|
+
function encodeS3Key(key) {
|
|
13713
|
+
return key.split("/").map((segment) => encodeURIComponent(segment).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)).join("/");
|
|
13714
|
+
}
|
|
13715
|
+
/** A custom endpoint's path, without a trailing slash ("" for none). */
|
|
13716
|
+
function endpointPathPrefix(base) {
|
|
13717
|
+
return base.pathname.replace(/\/+$/, "");
|
|
13718
|
+
}
|
|
13719
|
+
/**
|
|
13720
|
+
* Builds the HTTPS URL of an object.
|
|
13721
|
+
*
|
|
13722
|
+
* AWS buckets whose name contains a dot are addressed path-style, since the
|
|
13723
|
+
* `*.s3.amazonaws.com` wildcard certificate covers one label only.
|
|
13724
|
+
*
|
|
13725
|
+
* @param location The bucket and key.
|
|
13726
|
+
* @param config The endpoint; without one the global AWS endpoint is used.
|
|
13727
|
+
* @returns The unsigned object URL.
|
|
13728
|
+
*/
|
|
13729
|
+
function s3ObjectHttpsUrl(location, config = {}) {
|
|
13730
|
+
if (location.bucket && !isSafeBucketName(location.bucket)) throw new Error(`Invalid S3 bucket name: ${location.bucket}`);
|
|
13731
|
+
const key = encodeS3Key(location.key);
|
|
13732
|
+
const endpoint = config.endpoint?.trim().replace(/\/+$/, "");
|
|
13733
|
+
if (!location.bucket) {
|
|
13734
|
+
if (endpoint) {
|
|
13735
|
+
const base = new URL(endpoint.includes("://") ? endpoint : `https://${endpoint}`);
|
|
13736
|
+
return `${base.protocol}//${base.host}${endpointPathPrefix(base)}/`;
|
|
13737
|
+
}
|
|
13738
|
+
const serviceRegion = config.region || location.region;
|
|
13739
|
+
return serviceRegion && serviceRegion !== "us-east-1" ? `https://s3.${serviceRegion}.amazonaws.com/` : "https://s3.amazonaws.com/";
|
|
13740
|
+
}
|
|
13741
|
+
if (endpoint) {
|
|
13742
|
+
const base = new URL(endpoint.includes("://") ? endpoint : `https://${endpoint}`);
|
|
13743
|
+
const prefix = endpointPathPrefix(base);
|
|
13744
|
+
return config.pathStyle ? `${base.protocol}//${base.host}${prefix}/${encodeS3Key(location.bucket)}/${key}` : `${base.protocol}//${location.bucket}.${base.host}${prefix}/${key}`;
|
|
13745
|
+
}
|
|
13746
|
+
const region = config.region || location.region;
|
|
13747
|
+
const host = region && region !== "us-east-1" ? `s3.${region}.amazonaws.com` : "s3.amazonaws.com";
|
|
13748
|
+
if (config.pathStyle || location.bucket.includes(".")) return `https://${host}/${encodeS3Key(location.bucket)}/${key}`;
|
|
13749
|
+
return `https://${location.bucket}.${host}/${key}`;
|
|
13750
|
+
}
|
|
13751
|
+
const encoder = new TextEncoder();
|
|
13752
|
+
function toHex(buffer) {
|
|
13753
|
+
return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13754
|
+
}
|
|
13755
|
+
async function sha256Hex(value) {
|
|
13756
|
+
return toHex(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
|
|
13757
|
+
}
|
|
13758
|
+
async function hmac(key, value) {
|
|
13759
|
+
const cryptoKey = await crypto.subtle.importKey("raw", key, {
|
|
13760
|
+
name: "HMAC",
|
|
13761
|
+
hash: "SHA-256"
|
|
13762
|
+
}, false, ["sign"]);
|
|
13763
|
+
return crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(value));
|
|
13764
|
+
}
|
|
13765
|
+
/** RFC 3986 encoding, as SigV4 requires for query names and values. */
|
|
13766
|
+
function encodeRfc3986(value) {
|
|
13767
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
13768
|
+
}
|
|
13769
|
+
function amzDate(date) {
|
|
13770
|
+
const stamp = date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
13771
|
+
return {
|
|
13772
|
+
stamp,
|
|
13773
|
+
day: stamp.slice(0, 8)
|
|
13774
|
+
};
|
|
13775
|
+
}
|
|
13776
|
+
/**
|
|
13777
|
+
* Presigns an S3 request with AWS Signature Version 4 in the query string.
|
|
13778
|
+
* Runs on WebCrypto, so it works in the browser, the Tauri webview, and Node.
|
|
13779
|
+
*
|
|
13780
|
+
* The URL authorizes exactly one method: a GET URL cannot answer a HEAD, so
|
|
13781
|
+
* readers that probe with HEAD need a GET range request instead.
|
|
13782
|
+
*
|
|
13783
|
+
* @param options The object URL, region, credentials, and lifetime.
|
|
13784
|
+
* @returns The presigned URL.
|
|
13785
|
+
*/
|
|
13786
|
+
async function presignS3Url(options) {
|
|
13787
|
+
const { credentials, region } = options;
|
|
13788
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
13789
|
+
const url = new URL(options.url);
|
|
13790
|
+
const { stamp, day } = amzDate(options.now ?? /* @__PURE__ */ new Date());
|
|
13791
|
+
const scope = `${day}/${region}/s3/aws4_request`;
|
|
13792
|
+
const expiresIn = Math.max(1, Math.min(S3_MAX_PRESIGN_SECONDS, Math.floor(options.expiresIn)));
|
|
13793
|
+
const params = [];
|
|
13794
|
+
url.searchParams.forEach((value, name) => {
|
|
13795
|
+
if (!name.toLowerCase().startsWith("x-amz-")) params.push([name, value]);
|
|
13796
|
+
});
|
|
13797
|
+
params.push(["X-Amz-Algorithm", "AWS4-HMAC-SHA256"], ["X-Amz-Credential", `${credentials.accessKeyId}/${scope}`], ["X-Amz-Date", stamp], ["X-Amz-Expires", String(expiresIn)], ["X-Amz-SignedHeaders", "host"]);
|
|
13798
|
+
if (credentials.sessionToken) params.push(["X-Amz-Security-Token", credentials.sessionToken]);
|
|
13799
|
+
const canonicalQuery = params.map(([name, value]) => [encodeRfc3986(name), encodeRfc3986(value)]).sort(([a, av], [b, bv]) => a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0).map(([name, value]) => `${name}=${value}`).join("&");
|
|
13800
|
+
const canonicalPath = url.pathname || "/";
|
|
13801
|
+
const stringToSign = [
|
|
13802
|
+
"AWS4-HMAC-SHA256",
|
|
13803
|
+
stamp,
|
|
13804
|
+
scope,
|
|
13805
|
+
await sha256Hex([
|
|
13806
|
+
method,
|
|
13807
|
+
canonicalPath,
|
|
13808
|
+
canonicalQuery,
|
|
13809
|
+
`host:${url.host}\n`,
|
|
13810
|
+
"host",
|
|
13811
|
+
"UNSIGNED-PAYLOAD"
|
|
13812
|
+
].join("\n"))
|
|
13813
|
+
].join("\n");
|
|
13814
|
+
const signature = toHex(await hmac(await hmac(await hmac(await hmac(await hmac(encoder.encode(`AWS4${credentials.secretAccessKey}`), day), region), "s3"), "aws4_request"), stringToSign));
|
|
13815
|
+
return `${url.origin}${canonicalPath}?${canonicalQuery}&X-Amz-Signature=${signature}`;
|
|
13816
|
+
}
|
|
13817
|
+
let activeSigner = null;
|
|
13818
|
+
/** Presigned URL → the unsigned URL it was minted for. Bounded. */
|
|
13819
|
+
const sourceBySignedHref = /* @__PURE__ */ new Map();
|
|
13820
|
+
const SIGNED_SOURCE_LIMIT = 500;
|
|
13821
|
+
/** Installs (or with null, removes) the app's S3 signer. */
|
|
13822
|
+
function registerS3UrlSigner(signer) {
|
|
13823
|
+
activeSigner = signer;
|
|
13824
|
+
}
|
|
13825
|
+
/**
|
|
13826
|
+
* Whether reading `url` needs the S3 signer: it names an S3 object in a bucket
|
|
13827
|
+
* a configured connection covers.
|
|
13828
|
+
*/
|
|
13829
|
+
function isCredentialedS3Url(url) {
|
|
13830
|
+
if (!activeSigner) return false;
|
|
13831
|
+
const location = parseS3Url(url);
|
|
13832
|
+
return Boolean(location && activeSigner.covers(location.bucket));
|
|
13833
|
+
}
|
|
13834
|
+
/** Records that `signedHref` reads `sourceUrl`, so a layer can keep the latter. */
|
|
13835
|
+
function rememberSignedSource(signedHref, sourceUrl) {
|
|
13836
|
+
sourceBySignedHref.delete(signedHref);
|
|
13837
|
+
if (sourceBySignedHref.size >= SIGNED_SOURCE_LIMIT) {
|
|
13838
|
+
const oldest = sourceBySignedHref.keys().next().value;
|
|
13839
|
+
if (oldest !== void 0) sourceBySignedHref.delete(oldest);
|
|
13840
|
+
}
|
|
13841
|
+
sourceBySignedHref.set(signedHref, sourceUrl);
|
|
13842
|
+
}
|
|
13843
|
+
/** Removes the SigV4 query parameters (credential, token, signature) from a URL. */
|
|
13844
|
+
function stripPresignParameters(url) {
|
|
13845
|
+
try {
|
|
13846
|
+
const parsed = new URL(url);
|
|
13847
|
+
for (const name of [...parsed.searchParams.keys()]) if (name.toLowerCase().startsWith("x-amz-")) parsed.searchParams.delete(name);
|
|
13848
|
+
return parsed.href;
|
|
13849
|
+
} catch {
|
|
13850
|
+
return url;
|
|
13851
|
+
}
|
|
13852
|
+
}
|
|
13853
|
+
function unsignedSourceUrl(url) {
|
|
13854
|
+
if (url === void 0) return void 0;
|
|
13855
|
+
const known = sourceBySignedHref.get(url);
|
|
13856
|
+
if (known !== void 0) return known;
|
|
13857
|
+
if (!/[?&]x-amz-signature=/i.test(url)) return url;
|
|
13858
|
+
const location = parseS3Url(url);
|
|
13859
|
+
return location && !/[?#]/.test(location.key) ? formatS3Uri(location) : stripPresignParameters(url);
|
|
13860
|
+
}
|
|
13861
|
+
/**
|
|
13862
|
+
* Resolves a data URL into one a plain HTTP reader can fetch:
|
|
13863
|
+
*
|
|
13864
|
+
* - an S3 object in a bucket a configured connection covers → a presigned URL;
|
|
13865
|
+
* - any other `s3://` URI → the public HTTPS object URL (anonymous read);
|
|
13866
|
+
* - anything else → unchanged.
|
|
13867
|
+
*
|
|
13868
|
+
* @param url The URL a layer or query names.
|
|
13869
|
+
* @param signal Aborts a pending credential lookup.
|
|
13870
|
+
* @returns The URL to hand the reader.
|
|
13871
|
+
* @throws When the bucket is covered but its credentials cannot be resolved.
|
|
13872
|
+
*/
|
|
13873
|
+
async function resolveReadableUrl(url, signal) {
|
|
13874
|
+
const location = parseS3Url(url);
|
|
13875
|
+
if (!location) return url;
|
|
13876
|
+
if (activeSigner?.covers(location.bucket)) {
|
|
13877
|
+
const signed = await activeSigner.presign({
|
|
13878
|
+
bucket: location.bucket,
|
|
13879
|
+
key: location.key,
|
|
13880
|
+
region: location.region
|
|
13881
|
+
}, signal);
|
|
13882
|
+
if (signed) {
|
|
13883
|
+
rememberSignedSource(signed.href, url);
|
|
13884
|
+
return signed.href;
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
return isS3Uri(url) ? s3ObjectHttpsUrl(location) : url;
|
|
13888
|
+
}
|
|
13889
|
+
/** The installed signer, for the S3 browser. */
|
|
13890
|
+
function getS3UrlSigner() {
|
|
13891
|
+
return activeSigner;
|
|
13892
|
+
}
|
|
13893
|
+
function decodeXmlText(value) {
|
|
13894
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code))).replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCodePoint(parseInt(code, 16))).replace(/&/g, "&");
|
|
13895
|
+
}
|
|
13896
|
+
function xmlTag(block, tag) {
|
|
13897
|
+
const match = block.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
|
|
13898
|
+
return match ? decodeXmlText(match[1]) : void 0;
|
|
13899
|
+
}
|
|
13900
|
+
function xmlBlocks(xml, tag) {
|
|
13901
|
+
return Array.from(xml.matchAll(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "g")), (m) => m[1]);
|
|
13902
|
+
}
|
|
13903
|
+
/**
|
|
13904
|
+
* Parses a ListObjectsV2 XML body. S3's listing XML is flat and predictable,
|
|
13905
|
+
* so this reads it with patterns rather than an XML DOM, which keeps it usable
|
|
13906
|
+
* in workers and Node tests.
|
|
13907
|
+
*/
|
|
13908
|
+
function parseS3ListObjects(xml) {
|
|
13909
|
+
const prefixes = xmlBlocks(xml, "CommonPrefixes").map((block) => xmlTag(block, "Prefix")).filter((value) => Boolean(value));
|
|
13910
|
+
const objects = xmlBlocks(xml, "Contents").flatMap((block) => {
|
|
13911
|
+
const key = xmlTag(block, "Key");
|
|
13912
|
+
if (key === void 0) return [];
|
|
13913
|
+
const lastModified = xmlTag(block, "LastModified");
|
|
13914
|
+
return [{
|
|
13915
|
+
key,
|
|
13916
|
+
size: Number(xmlTag(block, "Size") ?? 0),
|
|
13917
|
+
...lastModified ? { lastModified } : {}
|
|
13918
|
+
}];
|
|
13919
|
+
});
|
|
13920
|
+
const truncated = xmlTag(xml, "IsTruncated") === "true";
|
|
13921
|
+
const token = xmlTag(xml, "NextContinuationToken");
|
|
13922
|
+
return {
|
|
13923
|
+
prefixes,
|
|
13924
|
+
objects,
|
|
13925
|
+
...truncated && token ? { nextContinuationToken: token } : {}
|
|
13926
|
+
};
|
|
13927
|
+
}
|
|
13928
|
+
/** The error code and message of an S3 XML error body, when it is one. */
|
|
13929
|
+
function parseS3Error(xml) {
|
|
13930
|
+
const block = xmlBlocks(xml, "Error")[0];
|
|
13931
|
+
if (block === void 0) return null;
|
|
13932
|
+
const endpoint = xmlTag(block, "Endpoint");
|
|
13933
|
+
const region = xmlTag(block, "Region") ?? (endpoint ? parseS3Url(`https://${endpoint}/`)?.region : void 0);
|
|
13934
|
+
return {
|
|
13935
|
+
code: xmlTag(block, "Code") ?? "Error",
|
|
13936
|
+
message: xmlTag(block, "Message") ?? "",
|
|
13937
|
+
...region ? { region } : {}
|
|
13938
|
+
};
|
|
13939
|
+
}
|
|
13940
|
+
/** Bucket names from a ListBuckets (`GET /` on the service) XML body. */
|
|
13941
|
+
function parseS3BucketList(xml) {
|
|
13942
|
+
return xmlBlocks(xml, "Bucket").map((block) => xmlTag(block, "Name")).filter((name) => Boolean(name));
|
|
13943
|
+
}
|
|
13944
|
+
/**
|
|
13945
|
+
* How browsers word a request they blocked or could not complete, plus the
|
|
13946
|
+
* readers that already guess at CORS themselves (maplibre-gl-lidar's message
|
|
13947
|
+
* tells the user to use a file picker, which is wrong for a bucket).
|
|
13948
|
+
*/
|
|
13949
|
+
const NETWORK_FAILURE = /failed to fetch|networkerror|load failed|network request failed|err_failed|cross-origin|\bcors\b/i;
|
|
13950
|
+
function errorText(error) {
|
|
13951
|
+
const parts = [];
|
|
13952
|
+
let current = error;
|
|
13953
|
+
for (let depth = 0; current && depth < 4; depth += 1) {
|
|
13954
|
+
parts.push(current instanceof Error ? `${current.name}: ${current.message}` : String(current));
|
|
13955
|
+
current = current instanceof Error ? current.cause : null;
|
|
13956
|
+
}
|
|
13957
|
+
return parts.join(" | ");
|
|
13958
|
+
}
|
|
13959
|
+
/** A read the bucket's CORS configuration blocked. */
|
|
13960
|
+
var S3CorsError = class extends Error {
|
|
13961
|
+
bucket;
|
|
13962
|
+
origin;
|
|
13963
|
+
constructor(message, bucket, origin, cause) {
|
|
13964
|
+
super(message, { cause });
|
|
13965
|
+
this.name = "S3CorsError";
|
|
13966
|
+
this.bucket = bucket;
|
|
13967
|
+
this.origin = origin;
|
|
13968
|
+
}
|
|
13969
|
+
};
|
|
13970
|
+
/**
|
|
13971
|
+
* Explains why reading an S3 object failed when the browser only said
|
|
13972
|
+
* "Failed to fetch". A browser reports a request the bucket's CORS rules
|
|
13973
|
+
* blocked exactly like a network outage, so this repeats the request twice:
|
|
13974
|
+
* normally, where success means the failure was transient; then in `no-cors`
|
|
13975
|
+
* mode, which CORS cannot block. Only when the first fails and the second gets
|
|
13976
|
+
* an answer is the bucket's CORS configuration the cause.
|
|
13977
|
+
*
|
|
13978
|
+
* @param url The `s3://` URI or object URL that was being read.
|
|
13979
|
+
* @param error What the reader threw.
|
|
13980
|
+
* @param translate Turns the message into the UI language.
|
|
13981
|
+
* @returns An {@link S3CorsError} naming the bucket and the origin to allow,
|
|
13982
|
+
* or `error` unchanged when it is not a CORS refusal of an S3 read.
|
|
13983
|
+
*/
|
|
13984
|
+
async function explainS3ReadError(url, error, translate) {
|
|
13985
|
+
const location = parseS3Url(url);
|
|
13986
|
+
if (!location || error instanceof S3CorsError) return error;
|
|
13987
|
+
if (!NETWORK_FAILURE.test(errorText(error))) return error;
|
|
13988
|
+
if (typeof fetch !== "function" || typeof window === "undefined") return error;
|
|
13989
|
+
const controller = new AbortController();
|
|
13990
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
13991
|
+
try {
|
|
13992
|
+
const href = await resolveReadableUrl(url);
|
|
13993
|
+
const probe = {
|
|
13994
|
+
signal: controller.signal,
|
|
13995
|
+
cache: "no-store"
|
|
13996
|
+
};
|
|
13997
|
+
if (await fetch(href, probe).then(() => true, () => false)) return error;
|
|
13998
|
+
await fetch(href, {
|
|
13999
|
+
...probe,
|
|
14000
|
+
mode: "no-cors"
|
|
14001
|
+
});
|
|
14002
|
+
} catch {
|
|
14003
|
+
return error;
|
|
14004
|
+
} finally {
|
|
14005
|
+
controller.abort();
|
|
14006
|
+
clearTimeout(timer);
|
|
14007
|
+
}
|
|
14008
|
+
const origin = window.location.origin;
|
|
14009
|
+
const fallback = `The bucket "${location.bucket}" does not allow reads from ${origin}: its CORS configuration is missing or does not list this origin. Add a CORS rule that allows GET and HEAD from this origin and exposes Content-Length, Content-Range, and ETag (see Settings > Cloud Storage).`;
|
|
14010
|
+
return new S3CorsError(translate ? translate("s3Browser.corsError", fallback, {
|
|
14011
|
+
bucket: location.bucket,
|
|
14012
|
+
origin
|
|
14013
|
+
}) : fallback, location.bucket, origin, error);
|
|
14014
|
+
}
|
|
14015
|
+
//#endregion
|
|
13062
14016
|
//#region src/storymap-sample.ts
|
|
13063
14017
|
const SAMPLE_ASSET_BASE = "https://opengeos.org/maplibre-gl-storymaps/assets";
|
|
13064
14018
|
/**
|
|
@@ -14465,6 +15419,184 @@ function persistedThreeDTilesRequestHeaders(url, headers) {
|
|
|
14465
15419
|
return nonEmptyRecord(stripGoogleMapsApiKeyHeader(headers));
|
|
14466
15420
|
}
|
|
14467
15421
|
//#endregion
|
|
15422
|
+
//#region src/project-credential-refs.ts
|
|
15423
|
+
/**
|
|
15424
|
+
* Credentials a project refers to by name rather than carrying inline.
|
|
15425
|
+
*
|
|
15426
|
+
* On the desktop app the geocoding API keys and secret environment variables
|
|
15427
|
+
* of a project live in the OS keychain, keyed device-wide by provider id /
|
|
15428
|
+
* variable name. The project file keeps the names with empty values. Request
|
|
15429
|
+
* headers never go there: they reference variables as `${NAME}` instead (see
|
|
15430
|
+
* `resolveProjectHeaderReferences`). This module is the pure half of that
|
|
15431
|
+
* scheme: the account names, the overlay that fills empty values from the
|
|
15432
|
+
* stored ones, and the split that moves values out of a project before it is
|
|
15433
|
+
* written. The app installs the lookup; without one (web, Jupyter) every
|
|
15434
|
+
* function here is the identity, so values stay in the project file.
|
|
15435
|
+
*
|
|
15436
|
+
* A non-empty value in the project always wins over the stored one: a file
|
|
15437
|
+
* that still carries plaintext (older files, the web build) is used as-is for
|
|
15438
|
+
* the session.
|
|
15439
|
+
*/
|
|
15440
|
+
function geocodingApiKeyAccount(providerId) {
|
|
15441
|
+
return `project.geocoding.apiKey.${providerId}`;
|
|
15442
|
+
}
|
|
15443
|
+
function environmentVariableAccount(key) {
|
|
15444
|
+
return `project.env.${key}`;
|
|
15445
|
+
}
|
|
15446
|
+
/** Rows are secret unless explicitly marked `secret: false`. */
|
|
15447
|
+
function isSecretEnvironmentVariable(variable) {
|
|
15448
|
+
return variable.secret !== false;
|
|
15449
|
+
}
|
|
15450
|
+
let projectCredentialLookup = null;
|
|
15451
|
+
/** Install (or clear, with `null`) the source of stored project credentials. */
|
|
15452
|
+
function setProjectCredentialLookup(lookup) {
|
|
15453
|
+
projectCredentialLookup = lookup;
|
|
15454
|
+
}
|
|
15455
|
+
/** The stored value for `account`, or `undefined` when none (or no lookup). */
|
|
15456
|
+
function lookupProjectCredential(account) {
|
|
15457
|
+
const value = projectCredentialLookup?.(account);
|
|
15458
|
+
return value ? value : void 0;
|
|
15459
|
+
}
|
|
15460
|
+
/** Fill empty geocoding API keys from the stored ones. */
|
|
15461
|
+
function overlayStoredGeocodingApiKeys(geocoding) {
|
|
15462
|
+
let apiKeys = null;
|
|
15463
|
+
for (const { id } of GEOCODING_PROVIDERS) {
|
|
15464
|
+
if (geocoding.apiKeys[id]?.trim()) continue;
|
|
15465
|
+
const stored = lookupProjectCredential(geocodingApiKeyAccount(id));
|
|
15466
|
+
if (!stored) continue;
|
|
15467
|
+
apiKeys ??= { ...geocoding.apiKeys };
|
|
15468
|
+
apiKeys[id] = stored;
|
|
15469
|
+
}
|
|
15470
|
+
return apiKeys ? {
|
|
15471
|
+
...geocoding,
|
|
15472
|
+
apiKeys
|
|
15473
|
+
} : geocoding;
|
|
15474
|
+
}
|
|
15475
|
+
/** Fill empty secret environment variable values from the stored ones. */
|
|
15476
|
+
function overlayStoredEnvironmentVariables(variables) {
|
|
15477
|
+
let changed = false;
|
|
15478
|
+
const overlaid = variables.map((variable) => {
|
|
15479
|
+
if (!isSecretEnvironmentVariable(variable) || variable.value !== "") return variable;
|
|
15480
|
+
const key = variable.key.trim();
|
|
15481
|
+
const stored = key ? lookupProjectCredential(environmentVariableAccount(key)) : void 0;
|
|
15482
|
+
if (!stored) return variable;
|
|
15483
|
+
changed = true;
|
|
15484
|
+
return {
|
|
15485
|
+
...variable,
|
|
15486
|
+
value: stored
|
|
15487
|
+
};
|
|
15488
|
+
});
|
|
15489
|
+
return changed ? overlaid : variables;
|
|
15490
|
+
}
|
|
15491
|
+
/** Preferences with every empty stored credential filled in. */
|
|
15492
|
+
function overlayStoredPreferenceCredentials(preferences) {
|
|
15493
|
+
const geocoding = overlayStoredGeocodingApiKeys(preferences.geocoding);
|
|
15494
|
+
const environmentVariables = overlayStoredEnvironmentVariables(preferences.environmentVariables);
|
|
15495
|
+
if (geocoding === preferences.geocoding && environmentVariables === preferences.environmentVariables) return preferences;
|
|
15496
|
+
return {
|
|
15497
|
+
...preferences,
|
|
15498
|
+
geocoding,
|
|
15499
|
+
environmentVariables
|
|
15500
|
+
};
|
|
15501
|
+
}
|
|
15502
|
+
/**
|
|
15503
|
+
* The credential changes an edited Settings draft makes, keyed by account
|
|
15504
|
+
* (`""` deletes).
|
|
15505
|
+
*
|
|
15506
|
+
* - `seeded`: the overlaid preferences the dialog opened with.
|
|
15507
|
+
* - `next`: the normalized draft being saved.
|
|
15508
|
+
*
|
|
15509
|
+
* Only values the user changed are returned, so an untouched session override
|
|
15510
|
+
* (plaintext from the opened file) stays out of the keychain and opening a
|
|
15511
|
+
* file never writes it. Removing, renaming, or un-secreting a variable never
|
|
15512
|
+
* deletes its stored value: names are shared by every project on the device.
|
|
15513
|
+
* Clearing a field deletes the stored value only when the field showed that
|
|
15514
|
+
* stored value; clearing a file override that differed from it just drops the
|
|
15515
|
+
* override, so the shared value applies again and other projects keep it.
|
|
15516
|
+
*/
|
|
15517
|
+
function changedPreferenceCredentials(seeded, next) {
|
|
15518
|
+
const changes = {};
|
|
15519
|
+
const record = (account, seededValue, nextValue) => {
|
|
15520
|
+
if (nextValue === seededValue) return;
|
|
15521
|
+
if (nextValue === "" && seededValue !== lookupProjectCredential(account)) return;
|
|
15522
|
+
changes[account] = nextValue;
|
|
15523
|
+
};
|
|
15524
|
+
const providerIds = /* @__PURE__ */ new Set([...Object.keys(seeded.geocoding.apiKeys), ...Object.keys(next.geocoding.apiKeys)]);
|
|
15525
|
+
for (const providerId of providerIds) record(geocodingApiKeyAccount(providerId), seeded.geocoding.apiKeys[providerId]?.trim() ?? "", next.geocoding.apiKeys[providerId]?.trim() ?? "");
|
|
15526
|
+
const seededSecrets = /* @__PURE__ */ new Map();
|
|
15527
|
+
for (const variable of seeded.environmentVariables) {
|
|
15528
|
+
if (!isSecretEnvironmentVariable(variable) || seededSecrets.has(variable.key)) continue;
|
|
15529
|
+
seededSecrets.set(variable.key, variable.value);
|
|
15530
|
+
}
|
|
15531
|
+
for (const variable of next.environmentVariables) {
|
|
15532
|
+
if (!isSecretEnvironmentVariable(variable)) continue;
|
|
15533
|
+
const seededValue = seededSecrets.get(variable.key);
|
|
15534
|
+
if (seededValue === void 0 && variable.value === "") continue;
|
|
15535
|
+
record(environmentVariableAccount(variable.key), seededValue ?? "", variable.value);
|
|
15536
|
+
}
|
|
15537
|
+
return changes;
|
|
15538
|
+
}
|
|
15539
|
+
/**
|
|
15540
|
+
* Move unambiguous geocoding API keys and secret environment variable values
|
|
15541
|
+
* out of `project` (a copy; the input is untouched) into `secrets`, keyed by
|
|
15542
|
+
* account. Secret environment rows that have no unique name remain in the
|
|
15543
|
+
* project for an explicit keep/strip choice. Layers pass through: their
|
|
15544
|
+
* request headers reference variables as `${NAME}` rather than holding
|
|
15545
|
+
* secrets that need moving.
|
|
15546
|
+
*/
|
|
15547
|
+
function splitProjectCredentials(project) {
|
|
15548
|
+
const secrets = {};
|
|
15549
|
+
for (const [providerId, apiKey] of Object.entries(project.preferences.geocoding.apiKeys)) {
|
|
15550
|
+
const trimmed = apiKey.trim();
|
|
15551
|
+
if (trimmed) secrets[geocodingApiKeyAccount(providerId)] = trimmed;
|
|
15552
|
+
}
|
|
15553
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
15554
|
+
for (const variable of project.preferences.environmentVariables) {
|
|
15555
|
+
const key = variable.key.trim();
|
|
15556
|
+
if (key) nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);
|
|
15557
|
+
}
|
|
15558
|
+
const environmentVariables = project.preferences.environmentVariables.map((variable) => {
|
|
15559
|
+
if (!isSecretEnvironmentVariable(variable) || variable.value === "") return variable;
|
|
15560
|
+
const key = variable.key.trim();
|
|
15561
|
+
if (!key || (nameCounts.get(key) ?? 0) > 1) return variable;
|
|
15562
|
+
secrets[environmentVariableAccount(key)] = variable.value;
|
|
15563
|
+
return {
|
|
15564
|
+
...variable,
|
|
15565
|
+
value: ""
|
|
15566
|
+
};
|
|
15567
|
+
});
|
|
15568
|
+
return {
|
|
15569
|
+
project: {
|
|
15570
|
+
...project,
|
|
15571
|
+
preferences: {
|
|
15572
|
+
...project.preferences,
|
|
15573
|
+
geocoding: {
|
|
15574
|
+
...project.preferences.geocoding,
|
|
15575
|
+
apiKeys: {}
|
|
15576
|
+
},
|
|
15577
|
+
environmentVariables
|
|
15578
|
+
}
|
|
15579
|
+
},
|
|
15580
|
+
secrets
|
|
15581
|
+
};
|
|
15582
|
+
}
|
|
15583
|
+
/**
|
|
15584
|
+
* `headers` with every `${NAME}` resolved from the project's enabled
|
|
15585
|
+
* Environment Variables (stored secret values filled in). Only those rows
|
|
15586
|
+
* count, not the OS environment or build-time values that share the runtime
|
|
15587
|
+
* env. A header referencing an unset variable is dropped.
|
|
15588
|
+
*/
|
|
15589
|
+
function resolveProjectHeaderReferences(headers) {
|
|
15590
|
+
if (!headers) return headers;
|
|
15591
|
+
const values = {};
|
|
15592
|
+
const variables = overlayStoredEnvironmentVariables(useAppStore.getState().preferences.environmentVariables);
|
|
15593
|
+
for (const variable of variables) {
|
|
15594
|
+
const key = variable.key.trim();
|
|
15595
|
+
if (variable.enabled && key) values[key] = variable.value;
|
|
15596
|
+
}
|
|
15597
|
+
return resolveHeaderReferences(headers, values);
|
|
15598
|
+
}
|
|
15599
|
+
//#endregion
|
|
14468
15600
|
//#region src/visibility.ts
|
|
14469
15601
|
/**
|
|
14470
15602
|
* Returns a new FeatureCollection with properties marked as "excluded" removed.
|
|
@@ -14744,6 +15876,6 @@ function currentEditorIdentity() {
|
|
|
14744
15876
|
return pickEditorIdentity(isActive ? selfName : void 0, readStoredAuthorName());
|
|
14745
15877
|
}
|
|
14746
15878
|
//#endregion
|
|
14747
|
-
export { ALL_APP_PRIVILEGES, ALL_DEPLOYMENT_CAPABILITIES, BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CESIUM_BASEMAPS, CESIUM_BING_AERIAL_ASSET_ID, CESIUM_GOOGLE_PHOTOREALISTIC_ASSET_ID, CESIUM_ION_QUICK_PICKS, CESIUM_ION_SOURCE_KIND, CESIUM_KML_SOURCE_KIND, CESIUM_OSM_BUILDINGS_ASSET_ID, CHINA_BASEMAPS, CZML_QUICK_PICKS, CZML_SOURCE_KIND, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRIMARY_RENDERER, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DEPLOYMENT_CAPABILITIES, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, IDENTIFY_ALL_LAYERS_ID, LABEL_NUMBER_LOCALES, LABEL_NUMBER_SAMPLE, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, NO_DEPLOYMENT_CAPABILITIES, NO_LAYERS, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, POPUP_IMAGE_HEIGHT_RANGE, POPUP_MAX_WIDTH_RANGE, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, ROLE_PRIVILEGES, RoutingRequestError, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, activeLayerFilterExpression, activeQuickFilters, appPrivilegeReason, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, applyStoryLayerOpacity, attributeFormFieldLabel, attributeLinkUrl, availableCesiumBasemap, basemapToCesiumImagery, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, cesiumIonAssetId, cesiumIonAssetKind, cesiumKmlSource, circleRadiusValue, clampLabelDecimals, clearExternalNativePaintBridge, clearHistory, clearQuickFilterValues, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, compileLayerFilters, compileQuickFilter, compileQuickFilters, controlRendersLayer, copyableLayerStyleKind, createCesiumIonLayer, createCesiumKmlLayer, createCzmlLayer, createDefaultAppCapabilities, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectLayerSerializationCache, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, czmlSource, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, documentLocale, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, ensureEditorTrackingFields, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, extractCopiedLayerStyle, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, formatCameraAltitude, formatExpressionPreviewValue, formatLabelNumber, formatLabelNumberSample, formatPixelValue, formatPopupValue, formatRoundNum, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getArcgisApiKey, getAttributeFormField, getBuildEnvironment, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasActiveLayerFilter, hasActiveQuickFilter, hasAppPrivilege, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, heatmapRampColors, horizontalBbox, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, intersectPrivileges, invertSelection, isAbsoluteFilesystemPath, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCesiumIonLayer, isCesiumKmlLayer, isCesiumOnlyLayer, isCredentialFieldName, isCzmlLayer, isDefaultPrintLayout, isDeploymentCapability, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHexColor, isInitialLayerStyle, isInlineImageValue, isInternalPopupField, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isPopupClickEnabled, isPopupHoverEnabled, isRegionalBasemapSentinel, isSafePopupUrl, isStoryHiddenLayer, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, labelFieldTextField, layerGroupDepth, layerGroupMoveability, layerGroupSortability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, localFileName, mapZoomStepOutputs, matchFeaturesByExpression, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeAppPrivileges, normalizeBlankBackgroundColor, normalizeCesiumBasemap, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrimaryRenderer, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, parseCesiumIonAssetId, parseContours, parseCzml, parseDeploymentCapabilities, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseProject, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, popupFieldLabel, portableWmsTileUrl, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, removeTrailingJsonCommas, rendererAppliesOpacity, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveConfiguredPopupTitle, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveLabelNumberLocale, resolveLayerCapabilities, resolveMapGrid, resolvePopupBody, resolvePopupImageHeight, resolvePopupMaxWidth, resolvePopupRows, resolvePopupTitle, resolveRolePrivileges, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, sameCesiumImagery, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, selectLayerById, selectLayerIds, selectLayerSummaries, selectLayersWhen, serializeLayerLibrary, serializeProject, serializeProjectWithLayerCache, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, sortLayerGroupInPanel, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyLayerOpacityFactor, storyMapHasContent, storyVisibleLayers, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, uniqueImportedLayerName, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, useAppCapability, useAppStore, useLayer, useLayerIds, useLayerSummaries, useLayersWhen, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue, visiblePopupFields, visiblePopupProperties };
|
|
15879
|
+
export { ALL_APP_PRIVILEGES, ALL_DEPLOYMENT_CAPABILITIES, BLANK_BASEMAP, BLEND_MODES, BUILT_IN_STYLE_PRESETS, CARTOCIUDAD_MIN_INTERVAL_MS, CARTOCIUDAD_PUBLIC_HOST, CESIUM_BASEMAPS, CESIUM_BING_AERIAL_ASSET_ID, CESIUM_GOOGLE_PHOTOREALISTIC_ASSET_ID, CESIUM_ION_QUICK_PICKS, CESIUM_ION_SOURCE_KIND, CESIUM_KML_SOURCE_KIND, CESIUM_OSM_BUILDINGS_ASSET_ID, CHINA_BASEMAPS, CZML_QUICK_PICKS, CZML_SOURCE_KIND, DEFAULT_BASEMAP, DEFAULT_BLEND_MODE, DEFAULT_COLLABORATION_STATE, DEFAULT_DASHBOARD_COLUMNS, DEFAULT_EDITOR_IDENTITY, DEFAULT_EDITOR_TRACKING_CONFIG, DEFAULT_ELLIPSOID_ID, DEFAULT_FORWARD_GEOCODE_ENDPOINT, DEFAULT_GEOCODING_PROVIDER_ID, DEFAULT_LAYER_GROUP_OPACITY, DEFAULT_LAYER_STYLE, DEFAULT_LEGEND_CONFIG, DEFAULT_MAP_GRID_LAYOUT, DEFAULT_PRIMARY_RENDERER, DEFAULT_PRINT_LAYOUT, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_PREFERENCES, DEFAULT_REVERSE_GEOCODE_ENDPOINT, DEFAULT_ROUTING_ENDPOINT, DEFAULT_STORY_MAP, DEPLOYMENT_CAPABILITIES, DUCKDB_VECTOR_FEATURE_WARN_COUNT, DUCKDB_VECTOR_ROUTE_BYTES, EARTH_MEAN_RADIUS_METERS, EDITOR_IDENTITY_STORAGE_KEY, ELEVATION_REQUEST_TIMEOUT_MS, ELLIPSOIDS, EXPRESSION_FUNCTION_CATEGORIES, ElevationFetchError, FEET_PER_METER, FEET_PER_MILE, GEOCODE_DISPLAY_NAME_KEY, GEOCODE_LAT_KEY, GEOCODE_LON_KEY, GEOCODE_PROVIDER_KEY, GEOCODE_SCORE_KEY, GEOCODE_STATUS_KEY, GEOCODING_PROVIDERS, GOOGLE_MAPS_API_KEY_HEADER, IDENTIFY_ALL_LAYERS_ID, LABEL_NUMBER_LOCALES, LABEL_NUMBER_SAMPLE, LARGE_VECTOR_FEATURE_THRESHOLD, LAYER_LIBRARY_BUNDLE_TYPE, LAYER_LIBRARY_BUNDLE_VERSION, LAYER_PALETTE, LAYER_STYLES_FILE_TYPE, LAYER_STYLES_FILE_VERSION, LAYER_TYPES, MAX_DASHBOARD_COLUMNS, MAX_DIAGRAM_FEATURES, MAX_DIAGRAM_SCAN_FEATURES, MAX_LAYER_LIBRARY_ENTRIES, MAX_LAYER_LIBRARY_ENTRY_BYTES, MAX_MAP_GRID_DIM, MAX_POINTS_PER_REQUEST, MAX_PROCESSING_HISTORY, MAX_REDACT_DEPTH, MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0, METERS_PER_FOOT, METERS_PER_MILE, METERS_PER_NAUTICAL_MILE, MIN_DASHBOARD_COLUMNS, MIN_DIAGRAM_SIZE, NETCDF_IMAGE_SOURCE_KIND, NOMINATIM_MIN_INTERVAL_MS, NOMINATIM_PUBLIC_HOST, NO_DEPLOYMENT_CAPABILITIES, NO_LAYERS, OPENFREEMAP_BASEMAPS, PHOTO_FULL_PROPERTY, PHOTO_PROPERTY, PLANETARY_BASEMAPS, PLANETARY_BASEMAP_GROUPS, PLANETARY_BASEMAP_SENTINEL_PREFIX, PLANET_SWITCHER_OPTIONS, POINTER_ELEVATION_CACHE_LIMIT, POINTER_ELEVATION_DEBOUNCE_MS, POPUP_IMAGE_HEIGHT_RANGE, POPUP_MAX_WIDTH_RANGE, PROJECT_CREDENTIAL_FIELDS, PROJECT_VERSION, PROTOMAPS_BASEMAPS, PUBLIC_GEOCODE_ROW_CAP, PUBLISHABLE_PLUGIN_SETTINGS, RAMP_STYLE_KEYS, RASTER_APPEARANCE_STATE_KEYS, REGIONAL_BASEMAPS, REGIONAL_BASEMAP_GROUPS, REGIONAL_BASEMAP_SENTINEL_PREFIX, ROLE_PRIVILEGES, RoutingRequestError, S3CorsError, S3_MAX_PRESIGN_SECONDS, SELECTION_MODES, SQL_QUERY_SOURCE_KIND, STYLE_LIBRARY_BUNDLE_TYPE, STYLE_LIBRARY_BUNDLE_VERSION, SYMBOL_STYLE_KEYS, VECTOR_COLOR_RAMPS, activeLayerFilterExpression, activeQuickFilters, allowsCredentialHeaders, appPrivilegeReason, applyCopiedLayerStyle, applyGroupEffects, applyJoinsToLayer, applyLayerJoins, applyLayerVirtualFields, applyMatchedSelection, applyProjectToStore, applySelectionMode, applyStoryLayerOpacity, attributeFormFieldLabel, attributeLinkUrl, availableCesiumBasemap, basemapToCesiumImagery, batchDecodePolylines, bodyLengthToEarth, buildForwardGeocodeUrl, buildIsochroneRequest, buildLayerPanelUnits, buildLayerTree, buildMatrixRequest, buildReverseGeocodeUrl, buildRouteRequest, canRedoProjectRestore, canSaveLayerToLibrary, canUndoProjectRestore, captureLayerLibraryEntry, cascadeLayerJoinRefresh, cesiumIonAssetId, cesiumIonAssetKind, cesiumKmlSource, changedPreferenceCredentials, circleRadiusValue, clampLabelDecimals, clampLatitude, clearExternalNativePaintBridge, clearHistory, clearQuickFilterValues, coerceAttributeFormValue, collectDiagramData, collectTransitiveJoinSourceIds, compareSequenceValues, compileFeatureExpression, compileLayerFilters, compileQuickFilter, compileQuickFilters, controlRendersLayer, copyableLayerStyleKind, createCesiumIonLayer, createCesiumKmlLayer, createCzmlLayer, createDefaultAppCapabilities, createDefaultMapView, createDefaultPrintLayout, createEmptyProject, createEqualIntervalBreaks, createGraduatedClassBreaks, createLayerLibraryEntryId, createPointerElevationResolver, createProjectLayerSerializationCache, createProjectTemplate, createQuantileBreaks, createSampleStoryMap, createStyleLayerEvaluator, createStyleLibraryEntryId, csvRowsToGeocodeRequests, currentEditorIdentity, czmlSource, darkenHex, decodePolyline, decodePolylineDetailed, detachProjectCopy, detectNonGeographicCoordinates, diagramAnchor, diagramPixelSize, diagramsSuppressedByPointRenderer, documentLocale, dominantGeometry, drawMarkerPath, earthAreaToBody, earthLengthToBody, editorTrackingFieldNames, effectiveLayerRenderState, effectiveVectorRules, encodePolyline, encodeS3Key, ensureEditorTrackingFields, environmentVariableAccount, evaluateMapExpression, excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject, explainS3ReadError, extractCopiedLayerStyle, extractLayerStyleEntries, extractStyleLibraryStyle, extrusionColorValue, extrusionHeightValue, featureSelectionId, fetchElevations, findLayerStyleEntryIndex, formatCameraAltitude, formatExpressionPreviewValue, formatLabelNumber, formatLabelNumberSample, formatPixelValue, formatPopupValue, formatRoundNum, formatS3Uri, generatorCircleRadiusValue, generatorSizeRange, geoJSONToPolylineStr, geocodeForward, geocodeMatchToFeature, geocodeReverse, geocoderMinIntervalMs, geocoderNeedsApiKey, geocodingApiKeyAccount, geojsonHasZCoordinates, getActiveBodyRadiusRatio, getActiveEllipsoid, getActiveMeanRadiusMeters, getActiveSemiMajorAxisMeters, getArcgisApiKey, getAttributeFormField, getBuildEnvironment, getCesiumIonToken, getEllipsoid, getExternalNativePaintBridge, getGeocoderConfig, getGeocodingProvider, getGoogleMapsApiKey, getHistoryCoalesceMs, getMapboxAccessToken, getMaxHistoryFeatureCount, getPlanetaryBasemapById, getPlanetaryBasemapByStyleUrl, getProtomapsApiKey, getProtomapsStyleUrl, getRegionalBasemapById, getRegionalBasemapByStyleUrl, getRoundNum, getRoutingConfig, getRuntimeEnvironment, getS3UrlSigner, getSpatialExtensionPath, getVectorColorRamp, googleMapsApiKeyHeaderValue, groupMemberIndices, hasActiveLayerFilter, hasActiveQuickFilter, hasAppPrivilege, hasHeaderReferences, hasPathTraversal, hasRestorableLayerSource, hasSimpleStyleProperties, heatmapRampColors, horizontalBbox, identifyAllIncludes, identifyStateWithoutLayers, inferFieldTypes, inferLayerCapabilities, initialLayerStyle, interpolateColors, interpolateHexColor, interpolateRampColors, intersectPrivileges, invertSelection, isAbsoluteFilesystemPath, isAdoptedVectorAwaitingFeatures, isAllowedPluginManifestUrl, isAttributeFormFieldVisible, isCesiumIonLayer, isCesiumKmlLayer, isCesiumOnlyLayer, isCredentialFieldName, isCredentialedS3Url, isCzmlLayer, isDefaultPrintLayout, isDeploymentCapability, isDiagramStyleEnabled, isDuckDBQueryLayer, isExternalNativeLayerRecord, isGooglePhotorealisticTilesetUrl, isHeaderReferenceOnly, isHexColor, isInitialLayerStyle, isInlineImageValue, isInternalPopupField, isIpadDesktopUserAgent, isMaintainedEditorTrackingField, isPopupClickEnabled, isPopupHoverEnabled, isRegionalBasemapSentinel, isS3Uri, isSafePopupUrl, isSecretEnvironmentVariable, isStoryHiddenLayer, isStyleLibraryTargetLayer, isStyleSpecColor, isVectorColorExpression, isochroneResponseToFeatures, labelFieldTextField, layerGroupDepth, layerGroupMoveability, layerGroupSortability, layerJoinKey, layerLibraryConfigPatch, layerLibraryEntryNeedsLocalFile, layerPanelGroupHeaders, lineWidthValue, localFileName, lookupProjectCredential, mapZoomStepOutputs, matchFeaturesByExpression, matchLayerStyleEntry, matrixResponseToFeatures, meanRadiusMeters, mercatorMetersPerPixelAtZoom0, metersWidthExpression, nextDelayMs, nextLayerPaletteColor, nominatimResultToFeature, nominatimReverseResultToDisplay, nonEmptyRecord, normalizeAppPrivileges, normalizeBlankBackgroundColor, normalizeCesiumBasemap, normalizeDashboardColumns, normalizeGeocodingProviderId, normalizeGroupContiguity, normalizeHexColor, normalizeLayerCapabilities, normalizeLayerLibraryEntries, normalizeLayerStyleEntries, normalizeLongitude, normalizeMapLayout, normalizeMapViewState, normalizeModelGraph, normalizeModels, normalizePrimaryRenderer, normalizePrintLayoutConfig, normalizeProcessingHistory, normalizeProjectComments, normalizeProjectInteraction, normalizeSecondaryMapViews, normalizeStoryMap, normalizeStyleLibraryEntries, normalizeWidgets, overlayStoredEnvironmentVariables, overlayStoredGeocodingApiKeys, overlayStoredPreferenceCredentials, parseCesiumIonAssetId, parseContours, parseCzml, parseDeploymentCapabilities, parseHexColor, parseHexColorList, parseJsonExpression, parseLayerLibrary, parseLayerStylesFile, parseProject, parseS3BucketList, parseS3Error, parseS3ListObjects, parseS3Url, parseStoryMapCsv, parseStoryMapJson, parseStyleLibrary, parseValueMapText, persistedThreeDTilesRequestHeaders, pickEditorIdentity, planLayerLibraryAdd, pluginOwnsPaint, polylineStrToGeoJSON, popupFieldLabel, portableWmsTileUrl, presignS3Url, printLayoutConfigsEqual, projectFromStore, projectPathLabel, proportionalRadiusExpression, proportionalSizeRange, readStoredAuthorName, reapplyLayerJoins, redactCredentials, redactProjectCredentials, redactUrlCredentials, redo, registerProjectRestoreHistory, registerS3UrlSigner, removeTrailingJsonCommas, rendererAppliesOpacity, reorderLayerGroupInPanel, requestIsochrone, requestMatrix, requestRoute, resolveConfiguredPopupTitle, resolveEditorTrackingConfig, resolveGeocoderConfig, resolveHeaderReferences, resolveIdentifyTarget, resolveLabelNumberLocale, resolveLayerCapabilities, resolveMapGrid, resolvePopupBody, resolvePopupImageHeight, resolvePopupMaxWidth, resolvePopupRows, resolvePopupTitle, resolveProjectHeaderReferences, resolveReadableUrl, resolveRolePrivileges, resolveSvgSource, resolveThreeDTilesRequestHeaders, rgbToHex, routeResponseToFeatures, rowCap, ruleBasedColorExpression, ruleBasedVisibilityFilter, s3ObjectHttpsUrl, sameCesiumImagery, sampleMapTerrain, sampleMapTerrainPoint, sampleRemoteElevations, sanitizeLayerStylePatch, scaleAltitudeToActiveBody, scaleDenomination, scaleSpan, scrubCommentsForRemovedLayers, scrubLegendForRemovedLayers, scrubPrintLayoutForLayers, scrubPrintLayoutForRemovedLayers, scrubWidgetsForRemovedLayers, selectLayerById, selectLayerIds, selectLayerSummaries, selectLayersWhen, serializeLayerLibrary, serializeLayerStylesFile, serializeProject, serializeProjectWithLayerCache, serializeStoryMapCsv, serializeStoryMapJson, serializeStyleLibrary, setActiveEllipsoidId, setExternalNativePaintBridge, setGeocodingFetch, setHistoryCoalesceMs, setMaxHistoryFeatureCount, setProjectCredentialLookup, setStoredAuthorName, shouldThrottle, shouldUseTiledRendering, simpleStyleNumberValue, sortLayerGroupInPanel, splitProjectCredentials, stampFeatureCollectionEditorTracking, stampFeatureEditorTracking, stampFeaturePropertiesEditorTracking, storyLayerOpacityFactor, storyMapHasContent, storyVisibleLayers, stringifyPopupValue, stripGoogleMapsApiKeyHeader, stripJoinFields, stripVirtualFieldColumns, styleValue, subscribeProjectRestoreHistory, substituteExpressionVariables, supportsBridgedOpacity, transformGeojsonElevation, trimHistoryBySize, undo, unescapePolyline, uniqueImportedLayerName, unmatchedGeocodeFeature, unresolvedLayerLibraryJoins, unsignedSourceUrl, useAppCapability, useAppStore, useLayer, useLayerIds, useLayerSummaries, useLayersWhen, validateAttributeFormField, validateAttributeFormValues, validateMapExpression, valueMapLabelFor, valueMapToText, vectorCircleColorValue, vectorColorExpression, vectorFillColorValue, vectorFillOpacityValue, vectorLineColorValue, vectorOutlineColorValue, vectorStrokeWidthValue, visiblePopupFields, visiblePopupProperties };
|
|
14748
15880
|
|
|
14749
15881
|
//# sourceMappingURL=index.mjs.map
|