@geolibre/core 3.0.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 CHANGED
@@ -1,3 +1,5 @@
1
+ import "zustand";
2
+ import "zundo";
1
3
  //#region src/cesium-imagery.d.ts
2
4
  /**
3
5
  * Maps the project's basemap onto imagery the Cesium globe can render.
@@ -113,42 +115,19 @@ export declare const CESIUM_BASEMAPS: readonly [{
113
115
  readonly name: "OpenStreetMap";
114
116
  readonly icon: "openStreetMap.png";
115
117
  readonly category: "Other";
116
- }, {
117
- readonly id: "stadia-watercolor";
118
- readonly name: "Stadia x Stamen Watercolor";
119
- readonly icon: "stamenWatercolor.png";
120
- readonly category: "Other";
121
- readonly stadiaStyle: "stamen_watercolor";
122
- readonly maximumLevel: 16;
123
- readonly extension: "jpg";
124
- }, {
125
- readonly id: "stadia-toner";
126
- readonly name: "Stadia x Stamen Toner";
127
- readonly icon: "stamenToner.png";
128
- readonly category: "Other";
129
- readonly stadiaStyle: "stamen_toner";
130
- readonly maximumLevel: 20;
131
- readonly extension: "png";
132
- }, {
133
- readonly id: "stadia-smooth";
134
- readonly name: "Stadia Alidade Smooth";
135
- readonly icon: "stadiaAlidadeSmooth.png";
136
- readonly category: "Other";
137
- readonly stadiaStyle: "alidade_smooth";
138
- readonly maximumLevel: 20;
139
- readonly extension: "png";
140
- }, {
141
- readonly id: "stadia-dark";
142
- readonly name: "Stadia Alidade Smooth Dark";
143
- readonly icon: "stadiaAlidadeSmoothDark.png";
144
- readonly category: "Other";
145
- readonly stadiaStyle: "alidade_smooth_dark";
146
- readonly maximumLevel: 20;
147
- readonly extension: "png";
148
118
  }];
149
119
  export type CesiumBasemapId = (typeof CESIUM_BASEMAPS)[number]["id"];
150
120
  export declare function normalizeCesiumBasemap(value: unknown): CesiumBasemapId;
151
- /** Missing credentials fall back to the project background without changing the saved choice. */
121
+ /**
122
+ * Missing credentials fall back to keyless imagery without changing the saved
123
+ * choice.
124
+ *
125
+ * The fallback is ArcGIS World Imagery rather than the project background: a
126
+ * globe wants to look like the Earth, and the project's own basemap is usually
127
+ * a vector street style whose raster analogue is mostly empty ocean from
128
+ * orbit. It needs no key, so the globe looks the same with or without an Ion
129
+ * token — only terrain and the Ion-only basemaps are lost.
130
+ */
152
131
  export declare function availableCesiumBasemap(value: unknown, hasIonToken: boolean): CesiumBasemapId;
153
132
  /**
154
133
  * What the globe should draw underneath the project's data layers.
@@ -177,8 +156,6 @@ export type CesiumBasemapImagery = {
177
156
  kind: "xyz";
178
157
  /** Tile template with `{z}`/`{x}`/`{y}` placeholders. */
179
158
  template: string;
180
- /** Credentials are resolved at render time, never embedded in the descriptor. */
181
- apiKeyProvider?: "stadia";
182
159
  /** Credit to show on the globe, as the HTML the 2D map already uses. */
183
160
  attribution: string;
184
161
  /** Max native zoom of the source, so the globe overzooms rather than 404s. */
@@ -1347,6 +1324,21 @@ export interface LayerPopupConfig {
1347
1324
  bodyExpression?: string;
1348
1325
  /** `false` drops the synthetic `id` row. Defaults to `true`. */
1349
1326
  showFeatureId?: boolean;
1327
+ /**
1328
+ * Widest the click popup may grow, in CSS pixels. Unset keeps the default
1329
+ * (520px, or 420px for a popup carrying an image). Clamped to the range
1330
+ * `resolvePopupMaxWidth` enforces and always capped by the viewport, so a
1331
+ * value wider than the window still leaves the map usable.
1332
+ */
1333
+ maxWidth?: number;
1334
+ /**
1335
+ * Tallest an `"image"` field's thumbnail may draw inside the popup, in CSS
1336
+ * pixels. Unset keeps the default (`min(50vh, 420px)`). Clamped by
1337
+ * `resolvePopupImageHeight`. Pair it with {@link maxWidth} for a
1338
+ * bigger picture: the thumbnail keeps its aspect ratio, so widening the
1339
+ * popup is what lets a landscape photo use the extra height.
1340
+ */
1341
+ imageHeight?: number;
1350
1342
  /**
1351
1343
  * The fields to show and their order. An empty or absent list keeps today's
1352
1344
  * behavior: every visible property, in the feature's own key order.
@@ -1646,7 +1638,9 @@ export interface AddTileLayerOptions {
1646
1638
  tiles: string[];
1647
1639
  /**
1648
1640
  * Layer discriminator, controlling how the layer is labelled and (for WMS)
1649
- * dev-server proxied. Defaults to `"xyz"`.
1641
+ * dev-server proxied. Defaults to `"xyz"`. The layer's `source.type` is
1642
+ * always `"raster"`, so any other value (such as `"vector-tiles"` from an
1643
+ * untyped JS caller) throws rather than persisting a mislabelled source.
1650
1644
  */
1651
1645
  type?: "xyz" | "wms" | "wmts" | "raster";
1652
1646
  /** Service or base URL recorded on the source for display and restore. */
@@ -1751,13 +1745,15 @@ export interface MapGridLayout {
1751
1745
  * `"maplibre"` is the 2D MapLibre GL map that owns the app's plugin, styling,
1752
1746
  * and deck.gl integrations. `"cesium"` is the 3D globe (see `CesiumCanvas`),
1753
1747
  * which renders the same shared store state — camera, basemap, layers, group
1754
- * effects — through CesiumJS.
1748
+ * effects — through CesiumJS. `"mapbox"` is Mapbox GL JS and `"arcgis"` the
1749
+ * ArcGIS Maps SDK for JavaScript, loaded from Esri's CDN at runtime (see
1750
+ * `ArcgisCanvas`); both draw the same store state through their own engines.
1755
1751
  *
1756
1752
  * Used both for secondary panes ({@link SecondaryMapView.viewKind}) and for the
1757
1753
  * primary workspace ({@link GeoLibreProject.primaryRenderer}), so the two never
1758
1754
  * drift apart.
1759
1755
  */
1760
- export type MapRendererKind = "maplibre" | "cesium";
1756
+ export type MapRendererKind = "maplibre" | "cesium" | "mapbox" | "arcgis";
1761
1757
  /**
1762
1758
  * The engine that draws the primary map area when a project says nothing. The
1763
1759
  * 2D map: it is the renderer every tool, plugin, and panel is wired to, so an
@@ -1925,6 +1921,16 @@ export interface MapPreferences {
1925
1921
  showPointerElevation: boolean;
1926
1922
  /** Whether the built-in 3D terrain control and terrain surface are enabled. */
1927
1923
  terrainEnabled: boolean;
1924
+ /** Mapbox-only style. New projects use Streets; absent follows the shared basemap. */
1925
+ mapboxStyleUrl?: string;
1926
+ /**
1927
+ * ArcGIS-only basemap: an Esri basemap style id (`arcgis/streets`,
1928
+ * `arcgis/imagery`, `osm/standard`, ...). New projects use Streets. Absent
1929
+ * follows the shared basemap, translated to tiles the SDK can draw; the id
1930
+ * is also set aside when no ArcGIS API key is configured, since Esri's
1931
+ * basemap styles service requires one.
1932
+ */
1933
+ arcgisBasemap?: string;
1928
1934
  /** Cesium imagery override; absent follows the shared project basemap. */
1929
1935
  cesiumBasemap?: CesiumBasemapId;
1930
1936
  /**
@@ -1941,6 +1947,12 @@ export interface RuntimeEnvironmentVariable {
1941
1947
  key: string;
1942
1948
  value: string;
1943
1949
  enabled: boolean;
1950
+ /**
1951
+ * Absent or `true`: the value is a secret (desktop keeps it in the OS
1952
+ * keychain; every egress removes it). `false`: an ordinary value saved in the
1953
+ * project and kept on share/export.
1954
+ */
1955
+ secret?: boolean;
1944
1956
  }
1945
1957
  declare global {
1946
1958
  interface Window {
@@ -2526,8 +2538,32 @@ export interface GeoLibreProject {
2526
2538
  styleLibrary?: StyleLibraryEntry[];
2527
2539
  /** Anchored review comments on map points or features (issue #1518). */
2528
2540
  comments?: ProjectComment[];
2541
+ /**
2542
+ * Interaction state applied when the project opens: the Identify target and
2543
+ * which map controls and toolbar panels start shown (issue #2688). Written by
2544
+ * the Python `geolibre` package from `set_identify` / `show_control`, so a
2545
+ * saved or HTML-exported notebook map opens the way it was set up. Omitted
2546
+ * by default; the app keeps what it loaded and writes it back unchanged.
2547
+ */
2548
+ interaction?: ProjectInteraction;
2529
2549
  metadata: Record<string, unknown>;
2530
2550
  }
2551
+ /**
2552
+ * Startup interaction state carried by a project (see
2553
+ * {@link GeoLibreProject.interaction}).
2554
+ */
2555
+ export interface ProjectInteraction {
2556
+ /**
2557
+ * Identify target on open: a layer id, `"all"` for every visible queryable
2558
+ * layer, a list of layer ids to identify only those, or `null` for off.
2559
+ */
2560
+ identify?: string | string[] | null;
2561
+ /**
2562
+ * Map control or toolbar panel name (`"search"`, `"bookmark"`, `"globe"`,
2563
+ * ...) to whether it starts shown. Unknown names are ignored on open.
2564
+ */
2565
+ controls?: Record<string, boolean>;
2566
+ }
2531
2567
  export type CommentAnchor = {
2532
2568
  type: "point";
2533
2569
  lngLat: [number, number];
@@ -3176,8 +3212,9 @@ export declare function horizontalBbox(box: readonly number[] | null | undefined
3176
3212
  * cached per GeoJSON object, so repeated calls on the same data are free.
3177
3213
  *
3178
3214
  * @param geojson - Any GeoJSON object (geometry, feature, or collection).
3215
+ * @param includeZero - Accept zero elevations when explicit absolute-height placement matters.
3179
3216
  */
3180
- export declare function geojsonHasZCoordinates(geojson: GeoJSON$1 | null | undefined): boolean;
3217
+ export declare function geojsonHasZCoordinates(geojson: GeoJSON$1 | null | undefined, includeZero?: boolean): boolean;
3181
3218
  /**
3182
3219
  * Returns a copy of the GeoJSON with every coordinate's Z value mapped to
3183
3220
  * `z * verticalScale + offset` (missing or non-finite Z treated as 0). Used
@@ -4252,6 +4289,47 @@ export declare function inferFieldTypes(features: ReadonlyArray<{
4252
4289
  properties?: Record<string, unknown> | null;
4253
4290
  }>, names: string[]): Record<string, ExpressionFieldType>;
4254
4291
  //#endregion
4292
+ //#region src/style-layer-evaluator.d.ts
4293
+ /** The geometry kinds a MapLibre style expression tells apart (`["geometry-type"]`). */
4294
+ export type StyleGeometryKind = "Point" | "LineString" | "Polygon";
4295
+ /** A feature as a style layer's expressions read it. */
4296
+ export interface StyleFeatureInput {
4297
+ id?: string | number;
4298
+ properties: Record<string, unknown> | null | undefined;
4299
+ kind: StyleGeometryKind;
4300
+ }
4301
+ /** A style layer's filter and property values, evaluated as MapLibre would. */
4302
+ export interface StyleLayerEvaluator {
4303
+ /** Whether the layer's `filter` keeps the feature at `zoom`. */
4304
+ passes: (zoom: number, feature: StyleFeatureInput) => boolean;
4305
+ /**
4306
+ * A paint or layout property's value for a feature at `zoom`: the layer's
4307
+ * value (a literal, a legacy function or an expression) or the spec's
4308
+ * default. A color comes back as `[r, g, b, a]` with channels 0-255 and
4309
+ * alpha 0-1; formatted text as its plain string. `undefined` for a property
4310
+ * the spec does not know or an expression that does not compile.
4311
+ */
4312
+ value: (group: "paint" | "layout", name: string, zoom: number, feature: StyleFeatureInput) => unknown;
4313
+ }
4314
+ interface StyleLayerInput {
4315
+ id?: string;
4316
+ type: string;
4317
+ filter?: unknown;
4318
+ paint?: Record<string, unknown>;
4319
+ layout?: Record<string, unknown>;
4320
+ }
4321
+ /**
4322
+ * Compile a MapLibre style layer's filter and properties for evaluation
4323
+ * outside MapLibre, for a renderer that draws the layer itself (the ArcGIS
4324
+ * renderer drawing a plugin's own GeoJSON overlay, say). Uses the style spec's
4325
+ * own evaluator, so data-driven and zoom expressions, legacy functions and
4326
+ * defaults behave as they do on MapLibre. Each property compiles once.
4327
+ *
4328
+ * @param layer - The style layer (its `type`, `filter`, `paint`, `layout`).
4329
+ * @returns The layer's evaluator.
4330
+ */
4331
+ export declare function createStyleLayerEvaluator(layer: StyleLayerInput): StyleLayerEvaluator;
4332
+ //#endregion
4255
4333
  //#region src/document-locale.d.ts
4256
4334
  /**
4257
4335
  * The document language, so formatted numbers and dates follow the UI locale.
@@ -4423,6 +4501,38 @@ export declare function getExternalNativePaintBridge(layerId: string): ExternalN
4423
4501
  * `!pluginOwnsPaint(layer)` to decide whether to show the slider at all.
4424
4502
  */
4425
4503
  export declare function supportsBridgedOpacity(layerId: string): boolean;
4504
+ /**
4505
+ * True when the primary renderer draws this plugin-painted layer itself, from
4506
+ * the store record, and so applies `layer.opacity` without any plugin bridge.
4507
+ *
4508
+ * A Zarr layer is a MapLibre custom layer owned by the Zarr control on the 2D
4509
+ * map, but the ArcGIS view and the Cesium globe render it natively (an ArcGIS
4510
+ * layer's `opacity`, a Cesium `ImageryLayer.alpha`) and mount no control to
4511
+ * register a bridge. Without this the Opacity slider would be hidden there
4512
+ * although it works (opengeos/GeoLibre#2261).
4513
+ *
4514
+ * @param layer - A store layer.
4515
+ * @param renderer - The primary renderer (`useAppStore().primaryRenderer`).
4516
+ * @returns Whether an Opacity slider reaches the layer through the renderer.
4517
+ */
4518
+ export declare function rendererAppliesOpacity(layer: {
4519
+ type: string;
4520
+ }, renderer: string | undefined): boolean;
4521
+ /**
4522
+ * Whether `layer` is an Add Vector Layer layer GeoLibre adopted
4523
+ * (`maplibre-gl-vector-adopted`, ADOPTED_VECTOR_SOURCE_KIND in
4524
+ * @geolibre/plugins) that has no features yet. A project saves a URL- or
4525
+ * path-backed adopted layer without them, and the vector control reads them
4526
+ * again on reopen. Until then its `source.url` may name GeoParquet or
4527
+ * GeoPackage, so a renderer must wait rather than load that URL as GeoJSON.
4528
+ *
4529
+ * @param layer - A store layer.
4530
+ * @returns True while the adopted layer awaits its features.
4531
+ */
4532
+ export declare function isAdoptedVectorAwaitingFeatures(layer: {
4533
+ metadata: Record<string, unknown>;
4534
+ geojson?: unknown;
4535
+ }): boolean;
4426
4536
  //#endregion
4427
4537
  //#region src/attribute-form.d.ts
4428
4538
  /** Options threaded to expression evaluation (constraints, visibility). */
@@ -4516,6 +4626,36 @@ export interface PopupRow {
4516
4626
  export declare function isPopupClickEnabled(popup: LayerPopupConfig | undefined): boolean;
4517
4627
  /** Whether a hover tooltip should follow the pointer. Default: no. */
4518
4628
  export declare function isPopupHoverEnabled(popup: LayerPopupConfig | undefined): boolean;
4629
+ /**
4630
+ * Bounds for {@link LayerPopupConfig.maxWidth}, in CSS pixels. The floor is
4631
+ * the popup's own minimum width (`min-w-[18rem]`), below which the setting
4632
+ * would be overruled by the layout and read as broken; the ceiling keeps a
4633
+ * hand-edited project from authoring a popup that blankets the map.
4634
+ */
4635
+ export declare const POPUP_MAX_WIDTH_RANGE: {
4636
+ readonly min: 288;
4637
+ readonly max: 1200;
4638
+ };
4639
+ /**
4640
+ * Bounds for {@link LayerPopupConfig.imageHeight}, in CSS pixels. Below the
4641
+ * floor a thumbnail carries no information; the ceiling matches
4642
+ * {@link POPUP_MAX_WIDTH_RANGE}, since a taller picture than that is what the
4643
+ * lightbox is for.
4644
+ */
4645
+ export declare const POPUP_IMAGE_HEIGHT_RANGE: {
4646
+ readonly min: 40;
4647
+ readonly max: 1200;
4648
+ };
4649
+ /**
4650
+ * The widest the click popup may draw for a layer, in CSS pixels, or
4651
+ * `undefined` to keep the renderer's default.
4652
+ */
4653
+ export declare function resolvePopupMaxWidth(popup: LayerPopupConfig | undefined): number | undefined;
4654
+ /**
4655
+ * The tallest an `"image"` field may draw inside the popup, in CSS pixels, or
4656
+ * `undefined` to keep the stylesheet's default.
4657
+ */
4658
+ export declare function resolvePopupImageHeight(popup: LayerPopupConfig | undefined): number | undefined;
4519
4659
  /** The label a popup shows for a configured field. */
4520
4660
  export declare function popupFieldLabel(config: PopupFieldConfig): string;
4521
4661
  /**
@@ -5087,14 +5227,23 @@ export declare class ElevationFetchError extends Error {
5087
5227
  }
5088
5228
  /** A `fetch`-compatible function, so tests can inject a stub. */
5089
5229
  export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
5230
+ /**
5231
+ * Normalize longitude into [-180, 180] degrees (handles unwrapped coordinates
5232
+ * from antimeridian cross-overs and world-copy pannings).
5233
+ */
5234
+ export declare function normalizeLongitude(lng: number): number;
5235
+ /**
5236
+ * Clamp latitude into [-90, 90] degrees.
5237
+ */
5238
+ export declare function clampLatitude(lat: number): number;
5090
5239
  /**
5091
5240
  * Fetch elevations (in meters) for an ordered list of coordinates.
5092
5241
  *
5093
5242
  * @param points - Coordinates as `[lng, lat]`, at most {@link MAX_POINTS_PER_REQUEST}
5094
5243
  * @param fetchImpl - Optional `fetch` implementation; defaults to the global `fetch`
5095
5244
  * @returns The elevation in meters for each input coordinate, in the same order
5096
- * @throws {ElevationFetchError} On too many points, a network error, a non-2xx
5097
- * response, a malformed body, or a length mismatch
5245
+ * @throws {ElevationFetchError} On too many points, a non-finite coordinate, a
5246
+ * network error, a non-2xx response, a malformed body, or a length mismatch
5098
5247
  */
5099
5248
  export declare function fetchElevations(points: LngLat[], fetchImpl?: FetchLike): Promise<number[]>;
5100
5249
  /** The slice of the MapLibre map the terrain sampler needs (stubbed in tests). */
@@ -5225,6 +5374,42 @@ export declare function createEmptyProject(name?: string, options?: CreateProjec
5225
5374
  * @returns The file contents to write.
5226
5375
  */
5227
5376
  export declare function serializeProject(project: GeoLibreProject): string;
5377
+ /**
5378
+ * Per-layer serialized text kept across {@link serializeProjectWithLayerCache}
5379
+ * calls, keyed by the layer record each entry was built from. A `WeakMap`, so a
5380
+ * layer the store has replaced or removed drops its text with it.
5381
+ */
5382
+ export type ProjectLayerSerializationCache = WeakMap<object, {
5383
+ index: number;
5384
+ text: string;
5385
+ }>;
5386
+ /** Create an empty {@link ProjectLayerSerializationCache}. */
5387
+ export declare function createProjectLayerSerializationCache(): ProjectLayerSerializationCache;
5388
+ /**
5389
+ * Serialize a project exactly as {@link serializeProject} does, reusing the
5390
+ * text of every layer whose source record is unchanged since an earlier call.
5391
+ *
5392
+ * Feature data lives inside layer records, so re-stringifying every layer on
5393
+ * each autosave costs megabytes of main-thread work for a project with large
5394
+ * GeoJSON layers — even when only the camera moved (GeoLibre#2633). The store
5395
+ * replaces a layer object whenever it changes, so object identity is a sound
5396
+ * cache key: a camera move reuses every layer, and an edit to one layer
5397
+ * re-serializes only that one.
5398
+ *
5399
+ * `layerSources[i]` must be the record `project.layers[i]` was derived from
5400
+ * (e.g. the store's `layers`, before `projectFromStore` prepared them), and that
5401
+ * derivation must depend on the record alone — as `buildProjectSnapshot`'s does
5402
+ * — or a cached entry could outlive a change it should have reflected. When the
5403
+ * two arrays differ in length the cache is bypassed rather than trusted.
5404
+ *
5405
+ * @param project Project to serialize, as built from `layerSources`.
5406
+ * @param layerSources Immutable source record for each entry of
5407
+ * `project.layers`, in the same order, used as the cache key.
5408
+ * @param cache Cache shared between calls; see
5409
+ * {@link createProjectLayerSerializationCache}.
5410
+ * @returns The same text {@link serializeProject} returns for `project`.
5411
+ */
5412
+ export declare function serializeProjectWithLayerCache(project: GeoLibreProject, layerSources: readonly object[], cache: ProjectLayerSerializationCache): string;
5228
5413
  export declare function parseProject(json: string): GeoLibreProject;
5229
5414
  /**
5230
5415
  * Validate and coerce a story map loaded from an untrusted project file.
@@ -5293,10 +5478,11 @@ export declare function normalizeMapLayout(value: unknown): MapGridLayout | null
5293
5478
  * Returns null for the default 2D map — absent, unknown, or an explicit
5294
5479
  * `"maplibre"` — because the field is only written when it is not the default,
5295
5480
  * so a MapLibre project serializes byte-identically to before this existed.
5296
- * The return type is narrowed to `"cesium" | null` rather than the full
5297
- * {@link MapRendererKind} for that reason: `"maplibre"` is never a result.
5481
+ * The return type is narrowed to `"cesium" | "mapbox" | "arcgis" | null` rather
5482
+ * than the full {@link MapRendererKind} for that reason: `"maplibre"` is never a
5483
+ * result.
5298
5484
  */
5299
- export declare function normalizePrimaryRenderer(value: unknown): "cesium" | null;
5485
+ export declare function normalizePrimaryRenderer(value: unknown): "cesium" | "mapbox" | "arcgis" | null;
5300
5486
  /**
5301
5487
  * Coerce an untrusted `secondaryMapViews` array into valid
5302
5488
  * {@link SecondaryMapView} records, dropping entries without a usable id and
@@ -5335,6 +5521,18 @@ export declare function normalizeWidgets(value: unknown): DashboardWidget[] | nu
5335
5521
  * @returns An integer column count within [MIN, MAX].
5336
5522
  */
5337
5523
  export declare function normalizeDashboardColumns(value: unknown): number;
5524
+ /**
5525
+ * Coerce an untrusted `interaction` block into a {@link ProjectInteraction}.
5526
+ *
5527
+ * Layer ids are kept even when no such layer exists: the block is replayed
5528
+ * against whatever layers the project has when it opens, and the loader drops
5529
+ * the ones it cannot find. Control names are kept as written for the same
5530
+ * reason; the app ignores names it does not know.
5531
+ *
5532
+ * @param value - Raw `interaction` value from the project JSON or the store.
5533
+ * @returns The normalized block, or null when it carries nothing to apply.
5534
+ */
5535
+ export declare function normalizeProjectInteraction(value: unknown): ProjectInteraction | null;
5338
5536
  export declare function isAllowedPluginManifestUrl(url: string): boolean;
5339
5537
  export declare function normalizeProjectComments(rawComments: unknown): ProjectComment[];
5340
5538
  export declare function projectFromStore(state: {
@@ -5363,8 +5561,20 @@ export declare function projectFromStore(state: {
5363
5561
  /** Project-scoped Style Manager entries (the store's `projectStyleLibrary`). */
5364
5562
  styleLibrary?: StyleLibraryEntry[] | null;
5365
5563
  comments?: ProjectComment[] | null;
5564
+ /** Startup interaction state loaded with the project, written back as-is. */
5565
+ interaction?: ProjectInteraction | null;
5366
5566
  metadata: Record<string, unknown>;
5367
5567
  }): GeoLibreProject;
5568
+ /**
5569
+ * The plain HTTP(S) WMS template inside the desktop app's `geolibre-wms://`
5570
+ * wrapper (its CORS-exempt native fetcher), or the tile unchanged when it is
5571
+ * not wrapped or the wrapped URL is not HTTP(S). Shared by project export and
5572
+ * the ArcGIS engine's story-export templates.
5573
+ *
5574
+ * @param tile - A tile template, wrapped or not.
5575
+ * @returns The unwrapped template, or `tile` itself.
5576
+ */
5577
+ export declare function portableWmsTileUrl(tile: unknown): unknown;
5368
5578
  export declare function applyProjectToStore(project: GeoLibreProject): {
5369
5579
  projectName: string;
5370
5580
  mapView: MapViewState;
@@ -5389,6 +5599,7 @@ export declare function applyProjectToStore(project: GeoLibreProject): {
5389
5599
  primaryRenderer: MapRendererKind;
5390
5600
  projectStyleLibrary: StyleLibraryEntry[];
5391
5601
  comments: ProjectComment[];
5602
+ projectInteraction: ProjectInteraction | null;
5392
5603
  metadata: Record<string, unknown>;
5393
5604
  };
5394
5605
  /**
@@ -5931,6 +6142,101 @@ export declare function extractCopiedLayerStyle(layer: GeoLibreLayer): CopiedLay
5931
6142
  */
5932
6143
  export declare function applyCopiedLayerStyle(target: GeoLibreLayer, copied: CopiedLayerStyle): Partial<GeoLibreLayer> | null;
5933
6144
  //#endregion
6145
+ //#region src/layer-style-file.d.ts
6146
+ /** `type` tag identifying a layer styles file. */
6147
+ export declare const LAYER_STYLES_FILE_TYPE = "geolibre-layer-styles";
6148
+ /** Format version written by {@link serializeLayerStylesFile}. */
6149
+ export declare const LAYER_STYLES_FILE_VERSION = 1;
6150
+ /** One layer's style in a layer styles file, keyed by the layer's name. */
6151
+ export type LayerStyleFileEntry = {
6152
+ /** Name of the layer the style was exported from; the match key. */
6153
+ layerName: string;
6154
+ kind: "vector";
6155
+ /** The full vector {@link LayerStyle} bag. */
6156
+ style: LayerStyle;
6157
+ } | {
6158
+ layerName: string;
6159
+ kind: "raster";
6160
+ /** Raster visualization state (`metadata.rasterState`). */
6161
+ rasterState?: Record<string, unknown>;
6162
+ /** Raster classification symbology (`metadata.rasterSymbology`). */
6163
+ rasterSymbology?: Record<string, unknown>;
6164
+ };
6165
+ /** The on-disk layer styles document. */
6166
+ export interface LayerStylesFile {
6167
+ type: typeof LAYER_STYLES_FILE_TYPE;
6168
+ version: typeof LAYER_STYLES_FILE_VERSION;
6169
+ styles: LayerStyleFileEntry[];
6170
+ }
6171
+ /**
6172
+ * Capture the style of every layer that has one. Layers without copyable
6173
+ * symbology (basemap tiles, 3D tiles, ...) are skipped. Layers that share a
6174
+ * name are all written, in stack order; the first one wins when the file is
6175
+ * applied (see {@link findLayerStyleEntryIndex}).
6176
+ *
6177
+ * @param layers - The layers to export, in store (bottom-to-top) order.
6178
+ * @returns The style entries.
6179
+ */
6180
+ export declare function extractLayerStyleEntries(layers: readonly GeoLibreLayer[]): LayerStyleFileEntry[];
6181
+ /**
6182
+ * Serialize style entries into the JSON written by Export Layer Styles.
6183
+ *
6184
+ * @param entries - The entries to write.
6185
+ * @returns Pretty-printed file content.
6186
+ */
6187
+ export declare function serializeLayerStylesFile(entries: readonly LayerStyleFileEntry[]): string;
6188
+ /**
6189
+ * Coerce untrusted entries (a hand-edited file, or a copy kept in settings)
6190
+ * into clean ones. An entry without a name or a known kind is dropped; a
6191
+ * vector style is sanitized and completed against the default style, and a
6192
+ * raster state keeps only well-typed appearance keys, so applying an entry
6193
+ * never hands a renderer an unknown, missing or wrong-typed field.
6194
+ *
6195
+ * @param value - The raw `styles` array.
6196
+ * @returns The usable entries.
6197
+ */
6198
+ export declare function normalizeLayerStyleEntries(value: unknown): LayerStyleFileEntry[];
6199
+ /**
6200
+ * Parse a file written by {@link serializeLayerStylesFile}.
6201
+ *
6202
+ * @param json - The file content.
6203
+ * @returns The normalized entries.
6204
+ * @throws Error when the content is not a layer styles file, comes from a
6205
+ * newer format version, or holds no usable entries.
6206
+ */
6207
+ export declare function parseLayerStylesFile(json: string): LayerStyleFileEntry[];
6208
+ /**
6209
+ * Find the entry for a layer name: an exact match first, then one that differs
6210
+ * only in case or surrounding whitespace, so `Roads` still picks up a style
6211
+ * saved for `roads`. The first match in file order wins.
6212
+ *
6213
+ * @param entries - The style entries.
6214
+ * @param layerName - The layer name to look up.
6215
+ * @param kind - When given, only entries of this style family are considered,
6216
+ * so a raster style saved under a shared name does not shadow the vector one.
6217
+ * @returns The matching entry's index in `entries`, or -1.
6218
+ */
6219
+ export declare function findLayerStyleEntryIndex(entries: readonly LayerStyleFileEntry[], layerName: string, kind?: LayerStyleClipboardKind): number;
6220
+ /** A layer restyled from a style entry, and the entry it took. */
6221
+ export interface LayerStyleMatch {
6222
+ /** Index of the applied entry in the entries array. */
6223
+ entryIndex: number;
6224
+ /** The patch that applies it. */
6225
+ patch: Partial<GeoLibreLayer>;
6226
+ }
6227
+ /**
6228
+ * Build the patch that restyles a layer from the entry matching its name, with
6229
+ * the same semantics as pasting a copied style: a vector style replaces the
6230
+ * whole style bag, a raster style merges its appearance keys and keeps the
6231
+ * layer's own band selection, and layer opacity is left alone.
6232
+ *
6233
+ * @param layer - The layer to restyle.
6234
+ * @param entries - The style entries.
6235
+ * @returns The matched entry and patch, or `null` when no entry of the layer's
6236
+ * style family matches its name (or the layer has no stylable symbology).
6237
+ */
6238
+ export declare function matchLayerStyleEntry(layer: GeoLibreLayer, entries: readonly LayerStyleFileEntry[]): LayerStyleMatch | null;
6239
+ //#endregion
5934
6240
  //#region src/layer-groups.d.ts
5935
6241
  /** Opacity a freshly created {@link LayerGroup} starts at (fully opaque). */
5936
6242
  export declare const DEFAULT_LAYER_GROUP_OPACITY = 1;
@@ -6157,6 +6463,35 @@ export declare function reorderLayerGroupInPanel(layers: GeoLibreLayer[], groups
6157
6463
  layers: GeoLibreLayer[];
6158
6464
  groups: LayerGroup[];
6159
6465
  } | null;
6466
+ /** Direction {@link sortLayerGroupInPanel} orders names in, top of panel first. */
6467
+ export type LayerGroupSortOrder = "asc" | "desc";
6468
+ /**
6469
+ * Sort what sits directly inside a group by name, A to Z (`"asc"`) or Z to A
6470
+ * (`"desc"`) from the top of the panel down (GeoLibre#2599).
6471
+ *
6472
+ * The group's own layers are sorted among themselves, and its child groups are
6473
+ * sorted among themselves by group name, each carrying everything nested inside
6474
+ * it. The two are not interleaved: a group's own layers are one contiguous
6475
+ * block (see {@link normalizeGroupContiguity}), so that block keeps its slot
6476
+ * among the child groups rather than being split between them. Only direct
6477
+ * children move; the contents of a child group keep their order.
6478
+ *
6479
+ * Like {@link reorderLayerGroupInPanel}, both arrays come back, so empty child
6480
+ * folders follow the sort too.
6481
+ *
6482
+ * @param layers Flat layer list in store (render) order.
6483
+ * @param groups Group definitions.
6484
+ * @param id Group whose children to sort.
6485
+ * @param order `"asc"` for A to Z, `"desc"` for Z to A.
6486
+ * @param locale BCP 47 tag whose collation rules to compare names by, normally
6487
+ * the app's display language. Defaults to the runtime locale.
6488
+ * @returns The new arrays, or `null` when the children are already in that
6489
+ * order and nothing would change.
6490
+ */
6491
+ export declare function sortLayerGroupInPanel(layers: GeoLibreLayer[], groups: LayerGroup[], id: string, order: LayerGroupSortOrder, locale?: string): {
6492
+ layers: GeoLibreLayer[];
6493
+ groups: LayerGroup[];
6494
+ } | null;
6160
6495
  /**
6161
6496
  * Which directions {@link reorderLayerGroupInPanel} can actually move each
6162
6497
  * group, so the layer panel can disable a menu item that would do nothing.
@@ -6169,6 +6504,16 @@ export declare function layerGroupMoveability(layers: GeoLibreLayer[], groups: L
6169
6504
  up: boolean;
6170
6505
  down: boolean;
6171
6506
  }>;
6507
+ /**
6508
+ * Which orders {@link sortLayerGroupInPanel} would actually change for each
6509
+ * group, so the layer panel can disable a sort that would do nothing.
6510
+ *
6511
+ * @param layers Flat layer list in store (render) order.
6512
+ * @param groups Group definitions.
6513
+ * @param locale Collation locale, as for {@link sortLayerGroupInPanel}.
6514
+ * @returns Per-group flags, keyed by group id.
6515
+ */
6516
+ export declare function layerGroupSortability(layers: GeoLibreLayer[], groups: LayerGroup[], locale?: string): Map<string, Record<LayerGroupSortOrder, boolean>>;
6172
6517
  //#endregion
6173
6518
  //#region src/pixel-format.d.ts
6174
6519
  /**
@@ -6183,6 +6528,236 @@ export declare function layerGroupMoveability(layers: GeoLibreLayer[], groups: L
6183
6528
  */
6184
6529
  export declare function formatPixelValue(value: number): string;
6185
6530
  //#endregion
6531
+ //#region src/s3.d.ts
6532
+ /**
6533
+ * Amazon S3 (and S3-compatible) object URLs: parsing, public HTTPS mapping,
6534
+ * SigV4 query-string presigning, and the registry the app uses to plug
6535
+ * credentialed signing into every reader.
6536
+ *
6537
+ * Readers in GeoLibre take a plain HTTPS URL (geotiff.js, cog-tiler-wasm,
6538
+ * pmtiles, DuckDB-WASM's HTTP reader, the vector control). A presigned URL is
6539
+ * the one form of authenticated S3 access all of them accept without a custom
6540
+ * fetch hook, so private objects are read by signing the URL first and handing
6541
+ * the reader the result. Layers keep the unsigned source URL (`s3://…` or the
6542
+ * plain object URL); the signed one is minted again on every load, so a saved
6543
+ * project never carries a signature or a session token.
6544
+ */
6545
+ /** A bucket and object key, with the endpoint the URL named, if any. */
6546
+ export interface S3ObjectLocation {
6547
+ bucket: string;
6548
+ /** The raw (decoded) object key, without a leading slash. */
6549
+ key: string;
6550
+ /** The region an AWS HTTPS URL named in its host, when it named one. */
6551
+ region?: string;
6552
+ }
6553
+ /** Where and how a bucket is addressed. */
6554
+ export interface S3EndpointConfig {
6555
+ /** Signing region, e.g. `us-east-1`. S3-compatible stores often accept `auto`. */
6556
+ region: string;
6557
+ /**
6558
+ * Custom endpoint origin for S3-compatible stores (MinIO, Cloudflare R2,
6559
+ * Wasabi, Ceph), e.g. `https://minio.example.com:9000`. Empty for AWS.
6560
+ */
6561
+ endpoint?: string;
6562
+ /** Address the bucket in the path instead of the host name. */
6563
+ pathStyle?: boolean;
6564
+ }
6565
+ /** AWS credentials used to sign a request. */
6566
+ export interface S3Credentials {
6567
+ accessKeyId: string;
6568
+ secretAccessKey: string;
6569
+ sessionToken?: string;
6570
+ /** Epoch milliseconds at which temporary credentials stop working. */
6571
+ expiresAt?: number;
6572
+ }
6573
+ /** A presigned URL and the moment it stops working. */
6574
+ export interface S3SignedUrl {
6575
+ href: string;
6576
+ /** Epoch milliseconds. */
6577
+ expiresAt: number;
6578
+ }
6579
+ /** SigV4 allows a presigned URL to live at most seven days. */
6580
+ export declare const S3_MAX_PRESIGN_SECONDS: number;
6581
+ /**
6582
+ * Parses an `s3://bucket/key` URI or an AWS S3 HTTPS object URL
6583
+ * (virtual-hosted or path-style, any region) into its bucket and key.
6584
+ * Query strings and fragments are ignored, so a presigned URL parses to the
6585
+ * object it signs.
6586
+ *
6587
+ * @param url The URL to parse.
6588
+ * @returns The bucket and key, or null when the URL is not an S3 object URL.
6589
+ */
6590
+ export declare function parseS3Url(url: string): S3ObjectLocation | null;
6591
+ /** Whether `url` is an `s3://` URI (as opposed to an HTTPS object URL). */
6592
+ export declare function isS3Uri(url: string): boolean;
6593
+ /** Formats a location as an `s3://bucket/key` URI. */
6594
+ export declare function formatS3Uri(location: S3ObjectLocation): string;
6595
+ /**
6596
+ * URI-encodes a key the way SigV4 canonicalizes an S3 path: every byte except
6597
+ * the unreserved set and `/` is percent-encoded, once.
6598
+ */
6599
+ export declare function encodeS3Key(key: string): string;
6600
+ /**
6601
+ * Builds the HTTPS URL of an object.
6602
+ *
6603
+ * AWS buckets whose name contains a dot are addressed path-style, since the
6604
+ * `*.s3.amazonaws.com` wildcard certificate covers one label only.
6605
+ *
6606
+ * @param location The bucket and key.
6607
+ * @param config The endpoint; without one the global AWS endpoint is used.
6608
+ * @returns The unsigned object URL.
6609
+ */
6610
+ export declare function s3ObjectHttpsUrl(location: S3ObjectLocation, config?: Partial<S3EndpointConfig>): string;
6611
+ export interface PresignS3Options {
6612
+ /** The unsigned HTTPS object URL (see {@link s3ObjectHttpsUrl}). */
6613
+ url: string;
6614
+ region: string;
6615
+ credentials: S3Credentials;
6616
+ /** Lifetime in seconds, clamped to 1…{@link S3_MAX_PRESIGN_SECONDS}. */
6617
+ expiresIn: number;
6618
+ /** HTTP method the URL authorizes. Defaults to GET. */
6619
+ method?: string;
6620
+ /** Signing time; defaults to now. Exposed for tests. */
6621
+ now?: Date;
6622
+ }
6623
+ /**
6624
+ * Presigns an S3 request with AWS Signature Version 4 in the query string.
6625
+ * Runs on WebCrypto, so it works in the browser, the Tauri webview, and Node.
6626
+ *
6627
+ * The URL authorizes exactly one method: a GET URL cannot answer a HEAD, so
6628
+ * readers that probe with HEAD need a GET range request instead.
6629
+ *
6630
+ * @param options The object URL, region, credentials, and lifetime.
6631
+ * @returns The presigned URL.
6632
+ */
6633
+ export declare function presignS3Url(options: PresignS3Options): Promise<string>;
6634
+ /** One signed S3 GET: an object read, or a bucket-level call such as ListObjectsV2. */
6635
+ export interface S3PresignRequest {
6636
+ bucket: string;
6637
+ /** Object key; empty for a bucket-level request. */
6638
+ key: string;
6639
+ /** Extra query parameters to sign (e.g. `list-type=2`). */
6640
+ query?: Record<string, string>;
6641
+ /** Region an AWS URL named, used ahead of the connection's default. */
6642
+ region?: string;
6643
+ /**
6644
+ * Sign with this connection instead of the one matched by bucket name (the
6645
+ * S3 browser listing a chosen connection's buckets, where `bucket` is empty).
6646
+ */
6647
+ connectionId?: string;
6648
+ }
6649
+ /** A configured connection, as the S3 browser lists it. No secrets. */
6650
+ export interface S3ConnectionSummary {
6651
+ id: string;
6652
+ name: string;
6653
+ /** Bucket patterns the connection covers; empty means every bucket. */
6654
+ buckets: string[];
6655
+ }
6656
+ /**
6657
+ * Presigns S3 requests with the app's configured credentials. `presign`
6658
+ * resolves to null when no configured connection covers the bucket, in which
6659
+ * case the object is read anonymously.
6660
+ */
6661
+ export interface S3UrlSigner {
6662
+ /** Whether a configured connection covers `bucket`. Must be synchronous. */
6663
+ covers(bucket: string): boolean;
6664
+ /** Presigns a GET; rejects when the connection's credentials cannot be resolved. */
6665
+ presign(request: S3PresignRequest, signal?: AbortSignal): Promise<S3SignedUrl | null>;
6666
+ /** The configured connections, for pickers. */
6667
+ connections(): S3ConnectionSummary[];
6668
+ /**
6669
+ * Fetches a presigned bucket-level URL (listing). The desktop app routes it
6670
+ * through its native HTTP client, which needs no bucket CORS rule.
6671
+ */
6672
+ fetchText?(url: string, signal?: AbortSignal): Promise<{
6673
+ status: number;
6674
+ body: string;
6675
+ }>;
6676
+ /** The persisted location (`s3://bucket/prefix/`) the S3 browser opens at, or "". */
6677
+ defaultLocation?(): string;
6678
+ /** Persists the S3 browser's default location; "" clears it. */
6679
+ setDefaultLocation?(location: string): void;
6680
+ }
6681
+ /** Installs (or with null, removes) the app's S3 signer. */
6682
+ export declare function registerS3UrlSigner(signer: S3UrlSigner | null): void;
6683
+ /**
6684
+ * Whether reading `url` needs the S3 signer: it names an S3 object in a bucket
6685
+ * a configured connection covers.
6686
+ */
6687
+ export declare function isCredentialedS3Url(url: string): boolean;
6688
+ /**
6689
+ * The unsigned URL a presigned one was minted for by
6690
+ * {@link resolveReadableUrl}, or `url` itself. Layer sync runs every URL a
6691
+ * control reports through this, so the store (and a saved project) keeps the
6692
+ * `s3://` source rather than an expiring signature.
6693
+ */
6694
+ export declare function unsignedSourceUrl(url: string): string;
6695
+ export declare function unsignedSourceUrl(url: string | undefined): string | undefined;
6696
+ /**
6697
+ * Resolves a data URL into one a plain HTTP reader can fetch:
6698
+ *
6699
+ * - an S3 object in a bucket a configured connection covers → a presigned URL;
6700
+ * - any other `s3://` URI → the public HTTPS object URL (anonymous read);
6701
+ * - anything else → unchanged.
6702
+ *
6703
+ * @param url The URL a layer or query names.
6704
+ * @param signal Aborts a pending credential lookup.
6705
+ * @returns The URL to hand the reader.
6706
+ * @throws When the bucket is covered but its credentials cannot be resolved.
6707
+ */
6708
+ export declare function resolveReadableUrl(url: string, signal?: AbortSignal): Promise<string>;
6709
+ /** The installed signer, for the S3 browser. */
6710
+ export declare function getS3UrlSigner(): S3UrlSigner | null;
6711
+ /** One page of a ListObjectsV2 response. */
6712
+ export interface S3ListPage {
6713
+ /** Common prefixes ("folders") under the requested prefix. */
6714
+ prefixes: string[];
6715
+ objects: Array<{
6716
+ key: string;
6717
+ size: number;
6718
+ lastModified?: string;
6719
+ }>;
6720
+ /** Present when more results follow. */
6721
+ nextContinuationToken?: string;
6722
+ }
6723
+ /**
6724
+ * Parses a ListObjectsV2 XML body. S3's listing XML is flat and predictable,
6725
+ * so this reads it with patterns rather than an XML DOM, which keeps it usable
6726
+ * in workers and Node tests.
6727
+ */
6728
+ export declare function parseS3ListObjects(xml: string): S3ListPage;
6729
+ /** The error code and message of an S3 XML error body, when it is one. */
6730
+ export declare function parseS3Error(xml: string): {
6731
+ code: string;
6732
+ message: string;
6733
+ region?: string;
6734
+ } | null;
6735
+ /** Bucket names from a ListBuckets (`GET /` on the service) XML body. */
6736
+ export declare function parseS3BucketList(xml: string): string[];
6737
+ /** A read the bucket's CORS configuration blocked. */
6738
+ export declare class S3CorsError extends Error {
6739
+ readonly bucket: string;
6740
+ readonly origin: string;
6741
+ constructor(message: string, bucket: string, origin: string, cause: unknown);
6742
+ }
6743
+ /** Translates `key`, falling back to `defaultValue` (an app API's `translate`). */
6744
+ export type S3ErrorTranslate = (key: string, defaultValue: string, params?: Record<string, string | number>) => string;
6745
+ /**
6746
+ * Explains why reading an S3 object failed when the browser only said
6747
+ * "Failed to fetch". A browser reports a request the bucket's CORS rules
6748
+ * blocked exactly like a network outage, so this repeats the request twice:
6749
+ * normally, where success means the failure was transient; then in `no-cors`
6750
+ * mode, which CORS cannot block. Only when the first fails and the second gets
6751
+ * an answer is the bucket's CORS configuration the cause.
6752
+ *
6753
+ * @param url The `s3://` URI or object URL that was being read.
6754
+ * @param error What the reader threw.
6755
+ * @param translate Turns the message into the UI language.
6756
+ * @returns An {@link S3CorsError} naming the bucket and the origin to allow,
6757
+ * or `error` unchanged when it is not a CORS refusal of an S3 read.
6758
+ */
6759
+ export declare function explainS3ReadError(url: string, error: unknown, translate?: S3ErrorTranslate): Promise<unknown>;
6760
+ //#endregion
6186
6761
  //#region src/storymap-sample.d.ts
6187
6762
  /**
6188
6763
  * Build a ready-to-play sample story map.
@@ -6308,351 +6883,249 @@ export declare function serializeStoryMapCsv(storymap: StoryMap): string;
6308
6883
  */
6309
6884
  export declare function parseStoryMapCsv(text: string, base?: StoryMap | null): StoryMap;
6310
6885
  //#endregion
6311
- //#region src/store.d.ts
6312
- type ConversionToolKind = "vector-to-vector" | "vector-to-geoparquet" | "vector-to-flatgeobuf" | "vector-to-shapefile" | "vector-to-geopackage" | "csv-to-geoparquet" | "vector-to-pmtiles" | "raster-to-pmtiles" | "raster-to-cog";
6313
- /**
6314
- * Identifiers of the vector processing tools. Kept in sync by hand with the
6315
- * `id` fields of `VECTOR_TOOLS` in `@geolibre/processing` (`vector-tools.ts`);
6316
- * deriving the type there would create a core -> processing circular import.
6317
- */
6318
- type VectorToolKind = "buffer" | "centroids" | "convex-hull" | "dissolve" | "bounding-box" | "simplify" | "clip" | "intersection" | "difference" | "union" | "spatial-join" | "attribute-join" | "select-by-value" | "select-by-location" | "random-extract" | "reproject" | "explode" | "aggregate" | "smooth" | "extract-vertices" | "points-along-geometry" | "grid" | "voronoi" | "cell-sectors" | "dggs-grid" | "dggs-bin" | "dggs-compact" | "trajectory-speed" | "detect-stops" | "space-time-proximity" | "decode-polyline" | "encode-polyline" | "merge-layers" | "check-validity" | "fix-geometries" | "check-topology-rules" | "fix-topology";
6319
- /** Identifiers of the network-analysis tools (`NETWORK_TOOLS` ids). */
6320
- type NetworkToolKind = "isochrone" | "od-matrix" | "sequential-route";
6321
- /** Identifiers of the spatial-statistics tools (`STATISTICS_TOOLS` ids). */
6322
- type StatisticsToolKind = "global-morans-i" | "local-morans-i" | "getis-ord-gi" | "average-nearest-neighbor" | "kernel-density" | "emerging-hot-spot" | "composite-score";
6323
- /**
6324
- * Identifiers of the raster processing tools. Kept in sync by hand with the
6325
- * `id` fields of `RASTER_TOOLS` in `@geolibre/processing` (`raster-tools.ts`);
6326
- * deriving the type there would create a core -> processing circular import.
6327
- */
6328
- type RasterToolKind = "hillshade" | "slope" | "aspect" | "reproject" | "resample" | "clip-extent" | "clip-mask" | "polygonize" | "contour" | "interpolate" | "zonal" | "raster-calc" | "spectral-index" | "reclassify" | "mosaic" | "focal";
6329
- /**
6330
- * Latest live device-GPS fix published by the GPS Tracking tool (issue #1316),
6331
- * read by the status bar readout. Device state, not project state: excluded
6332
- * from undo history (partialize never lists it) and from project files, and
6333
- * deliberately left untouched on project switches.
6334
- */
6335
- interface GpsStatusFix {
6336
- lng: number;
6337
- lat: number;
6338
- /** Horizontal accuracy radius in meters. */
6339
- accuracy: number;
6340
- /** Satellites used for the fix, or null when the provider does not report it. */
6341
- satellites: number | null;
6342
- /** Ground speed in m/s, or null when the device doesn't report one. */
6343
- speed: number | null;
6344
- /** Fix time in epoch milliseconds. */
6345
- timestamp: number;
6346
- }
6347
- interface AppState {
6348
- projectName: string;
6349
- projectPath: string | null;
6350
- projectGeneration: number;
6351
- isDirty: boolean;
6352
- mapView: MapViewState;
6353
- basemapStyleUrl: string;
6354
- basemapVisible: boolean;
6355
- basemapOpacity: number;
6356
- blankBackgroundColor: string | null;
6357
- layers: GeoLibreLayer[];
6358
- layerGroups: LayerGroup[];
6359
- preferences: ProjectPreferences;
6360
- projectPlugins: ProjectPluginState | null;
6361
- legend: LegendConfig;
6362
- /** Print Layout composer settings for the open project (discussion #1992). */
6363
- printLayout: PrintLayoutConfig;
6364
- storymap: StoryMap | null;
6365
- /** Saved processing pipelines (batch/model chaining; issue #344). */
6366
- models: ProcessingModel[];
6367
- /**
6368
- * App-level Style Manager library (issue #1294). Lives outside the project
6369
- * lifecycle: never serialized into the project file, untouched by
6370
- * newProject/loadProject, and persisted by the desktop app (IndexedDB).
6371
- */
6372
- styleLibrary: StyleLibraryEntry[];
6373
- /**
6374
- * App-level Layer Library (issue #1520) — the Browser panel's My Data
6375
- * section. Like {@link styleLibrary} it lives outside the project lifecycle:
6376
- * never serialized into the project file, untouched by newProject/loadProject,
6377
- * and persisted by the desktop app (IndexedDB). Most recently saved first.
6378
- */
6379
- layerLibrary: LayerLibraryEntry[];
6380
- /**
6381
- * App-level Template Library. Persisted by the desktop app (IndexedDB).
6382
- */
6383
- templateLibrary: ProjectTemplateEntry[];
6886
+ //#region src/store/capabilities-slice.d.ts
6887
+ interface CapabilitiesSlice {
6384
6888
  /**
6385
- * Project-scoped Style Manager entries (issue #1294), serialized into the
6386
- * `.geolibre.json` `styleLibrary` array and replaced on project load.
6889
+ * What this *deployment* is allowed to do (issue #1673). Set once at startup
6890
+ * from the deployment configuration; never from a project file, a URL
6891
+ * parameter, or anything else the visitor controls, and never edited from the
6892
+ * UI. Defaults to the full set so an unconfigured build behaves as before.
6893
+ *
6894
+ * Excluded from the project file and from undo history: it describes the
6895
+ * server that served the app, not the document being edited.
6387
6896
  */
6388
- projectStyleLibrary: StyleLibraryEntry[];
6389
- /** Recorded processing tool runs, oldest first (Processing History; #1292). */
6390
- processingHistory: ProcessingRun[];
6391
- /** Saved Dashboard panel chart widgets (issue #401). */
6392
- widgets: DashboardWidget[];
6393
- /** Number of columns in the Dashboard widget grid. */
6394
- dashboardColumns: number;
6897
+ deploymentCapabilities: ReadonlySet<DeploymentCapability>;
6395
6898
  /**
6396
- * Multi-map grid layout (issue: split/grid view). A 1x1 grid is the normal
6397
- * single-map workspace. `rows * cols` panes are shown; pane 0 is the primary
6398
- * map driven by `mapView` / `basemap*`, panes 1.. are `secondaryMapViews`.
6899
+ * Ephemeral application capability model (issue #1672). Gating role and
6900
+ * privileges for the current session/deployment. Excluded from the project file
6901
+ * and undo history.
6399
6902
  */
6400
- mapLayout: MapGridLayout;
6903
+ capabilities: AppCapabilities;
6401
6904
  /**
6402
- * Secondary map panes (everything past the primary pane). The store keeps
6403
- * exactly `rows * cols - 1` entries in sync with `mapLayout`.
6905
+ * Narrow what this deployment may do. Intended for the startup path only —
6906
+ * calling it later would leave already-rendered surfaces stale.
6404
6907
  */
6405
- secondaryMapViews: SecondaryMapView[];
6406
- /** User-entered label for the primary pane (shown only in multi-map mode). */
6407
- primaryMapLabel: string;
6908
+ setDeploymentCapabilities: (capabilities: Iterable<DeploymentCapability>) => void;
6408
6909
  /**
6409
- * Which engine draws the primary map area (issue #2217): the 2D MapLibre map
6410
- * or the 3D Cesium globe. Both render the same store state, so switching
6411
- * keeps the camera, basemap, layers, groups, visibility, and opacity — it
6412
- * only changes what draws them. Independent of `mapLayout`: switching never
6413
- * adds or removes panes.
6910
+ * Assign an application role (e.g. "viewer", "editor", "publisher", "administrator", "custom"),
6911
+ * deriving the effective privileges and optional reason.
6414
6912
  */
6415
- primaryRenderer: MapRendererKind;
6416
- selectedLayerId: string | null;
6417
- selectedFeatureId: string | null;
6418
- /**
6419
- * Full set of selected feature ids. The attribute table extends the single
6420
- * selection to many rows via Ctrl/Cmd (toggle) and Shift (range). The anchor
6421
- * — `selectedFeatureId` — is the primary/last-clicked feature used for map
6422
- * fit, DuckDB highlight, and scripting, and is always one of these ids (or
6423
- * `null` when the set is empty). A single click leaves exactly one id here.
6913
+ setAppRole: (role: AppRole, options?: {
6914
+ customPrivileges?: AppPrivilege[];
6915
+ reason?: string;
6916
+ }) => void;
6917
+ /** Set explicit custom privileges and an optional reason. */
6918
+ setAppPrivileges: (privileges: AppPrivilege[], reason?: string) => void;
6919
+ /** Grant an individual application privilege. */
6920
+ grantAppPrivilege: (privilege: AppPrivilege) => void;
6921
+ /** Revoke an individual application privilege with an optional reason. */
6922
+ revokeAppPrivilege: (privilege: AppPrivilege, reason?: string) => void;
6923
+ /** Reset application capabilities back to the default unconstrained Administrator role. */
6924
+ resetAppCapabilities: () => void;
6925
+ /** Check if the current capabilities grant the requested privilege. */
6926
+ hasAppPrivilege: (privilege: AppPrivilege) => boolean;
6927
+ }
6928
+ //#endregion
6929
+ //#region src/store/collaboration-slice.d.ts
6930
+ /**
6931
+ * A fresh, inactive collaboration slice (no live session). Frozen (like
6932
+ * DEFAULT_LEGEND_CONFIG) to guard against accidental in-place mutation; store
6933
+ * actions always produce new objects via spread, so the frozen default is only
6934
+ * ever read.
6935
+ */
6936
+ export declare const DEFAULT_COLLABORATION_STATE: CollaborationState;
6937
+ interface CollaborationSlice {
6938
+ collaboration: CollaborationState;
6939
+ setCollaboration: (patch: Partial<CollaborationState>) => void;
6940
+ updateCollaborationPresence: (clientId: string, presence: CollaborationPresence | null) => void;
6941
+ /** Append a chat message to the session log (bounded; #754). */
6942
+ addCollaborationChat: (message: CollaborationChatMessage) => void;
6943
+ resetCollaboration: () => void;
6944
+ }
6945
+ //#endregion
6946
+ //#region src/store/layer-groups-slice.d.ts
6947
+ interface LayerGroupsSlice {
6948
+ layerGroups: LayerGroup[];
6949
+ addLayerGroup: (name?: string, layerIds?: string[]) => string;
6950
+ removeLayerGroup: (id: string, options?: {
6951
+ removeChildren?: boolean;
6952
+ }) => void;
6953
+ renameLayerGroup: (id: string, name: string) => void;
6954
+ setLayerGroupVisibility: (id: string, visible: boolean) => void;
6955
+ setLayerGroupOpacity: (id: string, opacity: number) => void;
6956
+ toggleLayerGroupCollapsed: (id: string) => void;
6957
+ moveLayerToGroup: (layerId: string, groupId: string | null, beforeLayerId?: string | null) => void;
6958
+ moveLayersToGroup: (layerIds: string[], groupId: string | null, beforeLayerId?: string | null) => void;
6959
+ moveLayerGroupToGroup: (id: string, parentId: string | null) => void;
6960
+ reorderLayerGroup: (id: string, direction: "up" | "down") => void;
6961
+ /**
6962
+ * Sort a group's direct children by name, A to Z or Z to A (top of panel
6963
+ * first), collating by `locale` (the app's display language) when given.
6424
6964
  */
6425
- selectedFeatureIds: string[];
6965
+ sortLayerGroup: (id: string, order: LayerGroupSortOrder, locale?: string) => void;
6966
+ }
6967
+ //#endregion
6968
+ //#region src/store/layers-slice.d.ts
6969
+ interface LayersSlice {
6970
+ layers: GeoLibreLayer[];
6971
+ addLayer: (layer: GeoLibreLayer, beforeLayerId?: string | null) => void;
6972
+ removeLayer: (id: string) => void;
6973
+ updateLayer: (id: string, patch: Partial<GeoLibreLayer>) => void;
6974
+ setLayerVisibility: (id: string, visible: boolean) => void;
6975
+ setLayerOpacity: (id: string, opacity: number) => void;
6976
+ setLayerStyle: (id: string, style: Partial<LayerStyle>) => void;
6426
6977
  /**
6427
- * Store-layer id targeted by Identify, or {@link IDENTIFY_ALL_LAYERS_ID} for
6428
- * the map-level mode that queries every visible queryable layer — vector,
6429
- * DuckDB query, WMS, COG, NetCDF image and time-slider raster alike.
6978
+ * Transient clipboard holding a layer's symbology, captured by
6979
+ * {@link copyLayerStyle} and applied by {@link pasteLayerStyle} (copy/paste
6980
+ * styles, issue #1339). Runtime-only: excluded from undo history
6981
+ * (`partialize` never lists it) and from the saved project. `null` until a
6982
+ * style is copied this session. Cleared by `newProject`/`loadProject` so a
6983
+ * paste can't apply an entry from a different project; it deliberately
6984
+ * survives undo/redo within a project (it holds a deep snapshot, not a live
6985
+ * layer reference, so a paste stays valid even if the source layer is undone
6986
+ * away — only the displayed source name may then be stale).
6430
6987
  */
6431
- identifyLayerId: string | null;
6432
- pointerCoords: [number, number] | null;
6988
+ copiedLayerStyle: CopiedLayerStyle | null;
6433
6989
  /**
6434
- * Ground elevation in true metres under the pointer, for the status bar
6435
- * (issue #1813). Null when it cannot be resolved — the pointer is off the
6436
- * map, terrain is off and the remote lookup has not answered (or failed), or
6437
- * the active body is not Earth. Set alongside `pointerCoords` by MapCanvas,
6438
- * which owns the map instance the terrain sample comes from.
6990
+ * Snapshot the given layer's style into {@link copiedLayerStyle} so it can be
6991
+ * pasted onto a compatible layer. No-op when the layer is missing or has no
6992
+ * copyable symbology (leaving any prior clipboard entry untouched). Returns
6993
+ * `true` when a style was captured, so callers can skip the confirmation on a
6994
+ * no-op.
6439
6995
  */
6440
- pointerElevation: number | null;
6996
+ copyLayerStyle: (id: string) => boolean;
6441
6997
  /**
6442
- * Camera height above sea level in metres — Google Earth Pro's "Eye alt"
6443
- * (issue #1816). Derived from the camera, so deliberately *not* part of
6444
- * `mapView`: that shape is persisted into the project file, and a stored
6445
- * altitude could only drift from the center/zoom/pitch it is computed from.
6446
- * Null before the map loads, or when MapLibre cannot report it.
6998
+ * Apply the {@link copiedLayerStyle} clipboard entry onto the given layer.
6999
+ * No-op when the clipboard is empty, the layer is missing, or the entry's
7000
+ * style family does not match the target layer's. Returns `true` when the
7001
+ * style was applied, so callers can skip the confirmation on a no-op.
6447
7002
  */
6448
- cameraAltitude: number | null;
6449
- /** Live GPS fix for the status bar, or null while GPS tracking is off. */
6450
- gpsStatus: GpsStatusFix | null;
6451
- /** Anchored review comments on map points or features (issue #1518). */
6452
- comments: ProjectComment[];
6453
- metadata: Record<string, unknown>;
6454
- recentProjects: RecentProjectEntry[];
6455
- attributeFilter: string;
7003
+ pasteLayerStyle: (id: string) => boolean;
6456
7004
  /**
6457
- * What this *deployment* is allowed to do (issue #1673). Set once at startup
6458
- * from the deployment configuration; never from a project file, a URL
6459
- * parameter, or anything else the visitor controls, and never edited from the
6460
- * UI. Defaults to the full set so an unconfigured build behaves as before.
7005
+ * Restyle layers from layer styles file entries matched by layer name
7006
+ * (Import Layer Styles, and the Startup setting's auto-apply). Each matched
7007
+ * layer is patched the way {@link pasteLayerStyle} would patch it, all in a
7008
+ * single store update so the import is one undo step.
6461
7009
  *
6462
- * Excluded from the project file and from undo history: it describes the
6463
- * server that served the app, not the document being edited.
7010
+ * @param entries - The parsed style entries.
7011
+ * @param layerIds - Restrict the restyle to these layers; every layer when
7012
+ * omitted.
7013
+ * @returns Each restyled layer's id with the index of the entry it took, in
7014
+ * stack order.
7015
+ */
7016
+ applyLayerStyleEntries: (entries: readonly LayerStyleFileEntry[], layerIds?: readonly string[]) => {
7017
+ layerId: string;
7018
+ entryIndex: number;
7019
+ }[];
7020
+ /**
7021
+ * Replace a layer's persistent attribute joins and immediately re-derive its
7022
+ * joined columns (strip what the previous joins added, apply the new list).
7023
+ * Pass an empty array to detach every join and restore the base attributes.
6464
7024
  */
6465
- deploymentCapabilities: ReadonlySet<DeploymentCapability>;
6466
- collaboration: CollaborationState;
7025
+ setLayerJoins: (id: string, joins: LayerJoin[]) => void;
6467
7026
  /**
6468
- * Ephemeral application capability model (issue #1672). Gating role and
6469
- * privileges for the current session/deployment. Excluded from the project file
6470
- * and undo history.
7027
+ * Replace the layer's Attribute Form designer configuration (per-field edit
7028
+ * widgets, constraints, conditional visibility). Pass `undefined` to remove
7029
+ * the form config entirely.
6471
7030
  */
6472
- capabilities: AppCapabilities;
6473
- ui: {
6474
- processingOpen: boolean;
6475
- /**
6476
- * Tool id to preselect when the Whitebox toolbox dialog opens, set when the
6477
- * user picks a specific tool from the Processing menu's category submenus.
6478
- * Consumed and cleared by ProcessingDialog. Null means "no preselection".
6479
- */
6480
- processingInitialTool: string | null;
6481
- conversionOpen: ConversionToolKind | null;
6482
- vectorToolOpen: VectorToolKind | null;
6483
- networkToolOpen: NetworkToolKind | null;
6484
- statisticsToolOpen: StatisticsToolKind | null;
6485
- rasterToolOpen: RasterToolKind | null;
6486
- segmentationOpen: boolean;
6487
- objectDetectionOpen: boolean;
6488
- segmentEverythingOpen: boolean;
6489
- geocodeOpen: boolean;
6490
- sqlWorkspaceOpen: boolean;
6491
- loadEditorFeaturesOpen: boolean;
6492
- loadEditorFeaturesLayerId: string | null;
6493
- pythonConsoleOpen: boolean;
6494
- notebookOpen: boolean;
6495
- assistantOpen: boolean;
6496
- attributeTableOpen: boolean;
6497
- /** Whether the Raster Attribute Table bottom panel is open (issue #1307). */
6498
- rasterAttributeTableOpen: boolean;
6499
- dashboardOpen: boolean;
6500
- storymapPanelOpen: boolean;
6501
- storymapPresenting: boolean;
6502
- storymapReturnToEditor: boolean;
6503
- /**
6504
- * Layer opacities the active story presentation has applied so far, keyed
6505
- * by store layer id. Playback fades layers by writing MapLibre paint
6506
- * properties directly (never the persisted `layers[].opacity`), so this is
6507
- * how renderers outside MapLibre's paint model (deck.gl diagrams, 3D
6508
- * Z-value geometry) and the on-map Legend follow a chapter's fades. Empty
6509
- * while not presenting; never saved with the project.
6510
- */
6511
- storymapLayerOpacity: Record<string, number>;
6512
- storymapComposingId: string | null;
6513
- /** The Batch tools dialog (run one tool across many layers). */
6514
- batchToolsOpen: boolean;
6515
- /** The Model Builder canvas panel (author a processing graph). */
6516
- modelBuilderOpen: boolean;
6517
- /** One-shot request for Model Builder to load a saved model. */
6518
- modelBuilderRequestedModelId: string | null;
6519
- /** Style Manager dialog visibility (issue #1294). */
6520
- styleManagerOpen: boolean;
6521
- /** Processing History panel visibility (#1292). */
6522
- processingHistoryOpen: boolean;
6523
- /** Select by Expression dialog visibility (#1314). */
6524
- selectByExpressionOpen: boolean;
6525
- selectByExpressionLayerId: string | null;
6526
- /** Select by Location dialog visibility (#1314). */
6527
- selectByLocationOpen: boolean;
6528
- /** Same contract as `selectByExpressionLayerId`, for Select by Location. */
6529
- selectByLocationLayerId: string | null;
6530
- /**
6531
- * Pending "re-run from History" request. Written by the History panel just
6532
- * before it opens the target processing dialog; consumed and cleared by
6533
- * that dialog once it has pre-filled its parameter form. Null when idle.
6534
- */
6535
- processingRerun: ProcessingRerunRequest | null;
6536
- zoomToSelectedFeature: boolean;
6537
- collaborateDialogOpen: boolean;
6538
- };
6539
- setPointerCoords: (coords: [number, number] | null) => void;
6540
- setPointerElevation: (elevation: number | null) => void;
6541
- setCameraAltitude: (altitude: number | null) => void;
6542
- setGpsStatus: (fix: GpsStatusFix | null) => void;
6543
- setCollaboration: (patch: Partial<CollaborationState>) => void;
6544
- updateCollaborationPresence: (clientId: string, presence: CollaborationPresence | null) => void;
6545
- /** Append a chat message to the session log (bounded; #754). */
6546
- addCollaborationChat: (message: CollaborationChatMessage) => void;
6547
- resetCollaboration: () => void;
6548
- setMapView: (view: Partial<MapViewState>, markDirty?: boolean) => void;
7031
+ setLayerAttributeForm: (id: string, attributeForm: AttributeFormConfig | undefined) => void;
6549
7032
  /**
6550
- * Resize the map grid. Clamps `rows`/`cols` into range and grows/shrinks
6551
- * `secondaryMapViews` so it always holds `rows * cols - 1` panes; new panes
6552
- * clone the primary map's current camera and basemap.
7033
+ * Replace the layer's popup/tooltip design (which fields the Identify popup
7034
+ * shows, in what order and under what labels, plus the hover tooltip). Pass
7035
+ * `undefined` to restore the default full-property dump.
6553
7036
  */
6554
- setMapGrid: (rows: number, cols: number) => void;
6555
- /** Toggle synchronized camera across all panes. */
6556
- setSyncView: (syncView: boolean) => void;
6557
- /** Patch one secondary pane's camera by id (no-op if the id is unknown). */
6558
- setSecondaryMapView: (id: string, view: Partial<MapViewState>, markDirty?: boolean) => void;
7037
+ setLayerPopup: (id: string, popup: LayerPopupConfig | undefined) => void;
6559
7038
  /**
6560
- * Override a layer's visibility in one secondary pane (no-op if the pane id is
6561
- * unknown). The override forces the layer visible/hidden in that pane only,
6562
- * independent of the primary map's visibility.
7039
+ * Replace the layer's editor tracking configuration (whether creation/edit
7040
+ * author and timestamp columns are maintained, and under which names). Pass
7041
+ * `undefined` to drop the configuration entirely.
6563
7042
  */
6564
- setSecondaryLayerVisibility: (id: string, layerId: string, visible: boolean) => void;
6565
- /** Set the primary pane's custom label. */
6566
- setPrimaryMapLabel: (label: string) => void;
7043
+ setLayerEditorTracking: (id: string, editorTracking: EditorTrackingConfig | undefined) => void;
6567
7044
  /**
6568
- * Switch the primary map area between the 2D map and the 3D globe (no-op if
6569
- * unchanged). Touches nothing else in the store, so the shared camera, layer,
6570
- * and basemap state carries straight across the swap.
7045
+ * Replace a layer's virtual fields and immediately re-derive its computed
7046
+ * columns (strip what the previous fields added, evaluate the new list).
7047
+ * Pass an empty array to detach every virtual field.
6571
7048
  */
6572
- setPrimaryRenderer: (renderer: MapRendererKind) => void;
6573
- /** Set one secondary pane's custom label (no-op if the id is unknown). */
6574
- setSecondaryMapLabel: (id: string, label: string) => void;
7049
+ setLayerVirtualFields: (id: string, fields: LayerVirtualField[]) => void;
6575
7050
  /**
6576
- * Switch one secondary pane between the 2D map and the 3D globe (no-op if the
6577
- * id is unknown or the kind is unchanged).
7051
+ * Replace a layer's quick filters (issue #2114). The controls persist with
7052
+ * the project and are compiled to a MapLibre filter at sync time, so this
7053
+ * only stores state — nothing re-derives the layer's data. Pass an empty
7054
+ * array to remove every control.
6578
7055
  */
6579
- setSecondaryViewKind: (id: string, viewKind: NonNullable<SecondaryMapView["viewKind"]>) => void;
6580
- /** Remove one secondary pane and collapse the grid back toward 1x1. */
6581
- removeSecondaryMapView: (id: string) => void;
6582
- setBasemapStyleUrl: (url: string) => void;
7056
+ setLayerQuickFilters: (id: string, filters: LayerQuickFilter[]) => void;
7057
+ /** Set or clear the project-persisted expression filter for a layer. */
7058
+ setLayerFilterExpression: (id: string, expression: unknown[] | null) => void;
7059
+ reorderLayer: (id: string, direction: "up" | "down") => void;
7060
+ moveLayer: (id: string, targetIndex: number) => void;
7061
+ moveLayersRelative: (layerIds: string[], targetLayerId: string, position: "above" | "below") => void;
7062
+ addGeoJsonLayer: (name: string, geojson: FeatureCollection, sourcePath?: string, beforeLayerId?: string | null) => string;
6583
7063
  /**
6584
- * Apply a planetary basemap and sync the project's ellipsoid to the body it
6585
- * depicts, so measurements and the globe control use that body's radius. Used
6586
- * by both the basemap picker and the Layers-panel planet switcher.
7064
+ * Add a georeferenced image overlay (a MapLibre `image` source rendered as a
7065
+ * raster layer) from an image URL and its four corner coordinates, and return
7066
+ * its id. Used for KML/KMZ `<GroundOverlay>` imports; the layer persists and
7067
+ * renders exactly like a Raster Georeferencer overlay. Corners are `[lng,
7068
+ * lat]` in top-left, top-right, bottom-right, bottom-left order.
6587
7069
  */
6588
- applyPlanetaryBasemap: (basemap: PlanetaryBasemap) => void;
7070
+ addImageOverlayLayer: (name: string, source: {
7071
+ url: string;
7072
+ coordinates: [number, number][];
7073
+ }, options?: {
7074
+ opacity?: number;
7075
+ bounds?: [number, number, number, number];
7076
+ sourcePath?: string;
7077
+ /** Initial visibility (default true); a time-slider frame past the first
7078
+ * starts hidden. */
7079
+ visible?: boolean;
7080
+ /** Epoch-ms time bounds of a KML `<TimeSpan>`/`<TimeStamp>` frame; the
7081
+ * Time Slider toggles this frame's visibility by the current date. */
7082
+ timeSpan?: {
7083
+ begin: number | null;
7084
+ end: number | null;
7085
+ };
7086
+ /**
7087
+ * What produced the overlay, e.g. a NetCDF grid baked to pixels. Defaults
7088
+ * to the KML ground overlay this was first written for; panels gate their
7089
+ * per-source controls on it.
7090
+ */
7091
+ sourceKind?: string;
7092
+ /** Extra metadata merged onto the layer (e.g. a symbology record). */
7093
+ metadata?: Record<string, unknown>;
7094
+ }, beforeLayerId?: string | null) => string;
6589
7095
  /**
6590
- * Return to Earth: apply `styleUrl` (typically the Earth basemap that was
6591
- * active before a planet was selected, e.g. Liberty) and reset the ellipsoid
6592
- * to Earth. Used when a planet is deselected in the switcher.
7096
+ * Add a native raster tile layer (XYZ, WMS, or WMTS) from one or more tile
7097
+ * URL templates and return its id. The layer appears in the Layers panel and
7098
+ * persists with the project exactly like a layer added through the Add Data
7099
+ * dialog, so callers (e.g. an external plugin) do not have to touch MapLibre
7100
+ * directly. See {@link AddTileLayerOptions}.
6593
7101
  */
6594
- restoreEarthBasemap: (styleUrl: string) => void;
6595
- setBasemapVisible: (visible: boolean) => void;
6596
- setBasemapOpacity: (opacity: number) => void;
6597
- setBlankBackgroundColor: (color: string | null) => void;
6598
- setPreferences: (preferences: ProjectPreferences) => void;
6599
- setLegend: (legend: LegendConfig) => void;
7102
+ addTileLayer: (name: string, options: AddTileLayerOptions, beforeLayerId?: string | null) => string;
7103
+ }
7104
+ //#endregion
7105
+ //#region src/store/libraries-slice.d.ts
7106
+ interface LibrariesSlice {
6600
7107
  /**
6601
- * Replace the Print Layout composer settings. A config equal to the current
6602
- * one is ignored, so re-opening the composer (or a project load seeding the
6603
- * dialog) never marks the project dirty.
7108
+ * App-level Style Manager library (issue #1294). Lives outside the project
7109
+ * lifecycle: never serialized into the project file, untouched by
7110
+ * newProject/loadProject, and persisted by the desktop app (IndexedDB).
6604
7111
  */
6605
- setPrintLayout: (printLayout: PrintLayoutConfig) => void;
6606
- setProjectPlugins: (projectPlugins: ProjectPluginState | null, shouldMarkDirty?: boolean) => void;
6607
- selectLayer: (id: string | null) => void;
6608
- selectFeature: (id: string | null) => void;
7112
+ styleLibrary: StyleLibraryEntry[];
6609
7113
  /**
6610
- * Replace the multi-selection with `ids`. The anchor (`selectedFeatureId`)
6611
- * becomes `anchorId` when provided, otherwise the last id in the list (or
6612
- * `null` when the list is empty).
7114
+ * App-level Layer Library (issue #1520) — the Browser panel's My Data
7115
+ * section. Like {@link styleLibrary} it lives outside the project lifecycle:
7116
+ * never serialized into the project file, untouched by newProject/loadProject,
7117
+ * and persisted by the desktop app (IndexedDB). Most recently saved first.
6613
7118
  */
6614
- selectFeatures: (ids: string[], anchorId?: string | null) => void;
6615
- setIdentifyLayer: (id: string | null) => void;
6616
- setAttributeFilter: (filter: string) => void;
6617
- setProcessingOpen: (open: boolean) => void;
6618
- setProcessingInitialTool: (toolId: string | null) => void;
6619
- setConversionOpen: (kind: ConversionToolKind | null) => void;
6620
- setVectorToolOpen: (kind: VectorToolKind | null) => void;
6621
- setNetworkToolOpen: (kind: NetworkToolKind | null) => void;
6622
- setStatisticsToolOpen: (kind: StatisticsToolKind | null) => void;
6623
- setRasterToolOpen: (kind: RasterToolKind | null) => void;
6624
- setSegmentationOpen: (open: boolean) => void;
6625
- setObjectDetectionOpen: (open: boolean) => void;
6626
- setSegmentEverythingOpen: (open: boolean) => void;
6627
- setGeocodeOpen: (open: boolean) => void;
6628
- setSqlWorkspaceOpen: (open: boolean) => void;
6629
- setLoadEditorFeaturesOpen: (open: boolean, layerId?: string | null) => void;
6630
- setPythonConsoleOpen: (open: boolean) => void;
6631
- setNotebookOpen: (open: boolean) => void;
6632
- setAssistantOpen: (open: boolean) => void;
6633
- setAttributeTableOpen: (open: boolean) => void;
6634
- setRasterAttributeTableOpen: (open: boolean) => void;
6635
- setDashboardOpen: (open: boolean) => void;
6636
- setStorymapPanelOpen: (open: boolean) => void;
6637
- setStorymapPresenting: (presenting: boolean, returnToEditor?: boolean) => void;
7119
+ layerLibrary: LayerLibraryEntry[];
6638
7120
  /**
6639
- * Record the layer opacities a story chapter applied, merging into the
6640
- * presentation's running map (see `ui.storymapLayerOpacity`).
7121
+ * App-level Template Library. Persisted by the desktop app (IndexedDB).
6641
7122
  */
6642
- setStorymapLayerOpacity: (changes: Record<string, number>) => void;
6643
- setStorymapComposing: (chapterId: string | null) => void;
6644
- setBatchToolsOpen: (open: boolean) => void;
6645
- setModelBuilderOpen: (open: boolean) => void;
6646
- setModelBuilderRequestedModelId: (id: string | null) => void;
6647
- setProcessingHistoryOpen: (open: boolean) => void;
6648
- /** Open/close Select by Expression, optionally preselecting a target layer. */
6649
- setSelectByExpressionOpen: (open: boolean, layerId?: string | null) => void;
6650
- /** Open/close Select by Location, optionally preselecting a target layer. */
6651
- setSelectByLocationOpen: (open: boolean, layerId?: string | null) => void;
6652
- setProcessingRerun: (request: ProcessingRerunRequest | null) => void;
6653
- setCollaborateDialogOpen: (open: boolean) => void;
6654
- setZoomToSelectedFeature: (enabled: boolean) => void;
6655
- setStyleManagerOpen: (open: boolean) => void;
7123
+ templateLibrary: ProjectTemplateEntry[];
7124
+ /**
7125
+ * Project-scoped Style Manager entries (issue #1294), serialized into the
7126
+ * `.geolibre.json` `styleLibrary` array and replaced on project load.
7127
+ */
7128
+ projectStyleLibrary: StyleLibraryEntry[];
6656
7129
  /**
6657
7130
  * Replace the app-level style library wholesale. Used by the persistence
6658
7131
  * layer on startup and by bundle imports.
@@ -6665,35 +7138,125 @@ interface AppState {
6665
7138
  * unrelated entry after loading a project authored elsewhere. Project-scope
6666
7139
  * saves mark the project dirty.
6667
7140
  */
6668
- saveStyleLibraryEntry: (entry: StyleLibraryEntry, scope?: "app" | "project") => void;
7141
+ saveStyleLibraryEntry: (entry: StyleLibraryEntry, scope?: "app" | "project") => void;
7142
+ /**
7143
+ * Remove a Style Manager entry by id. When `scope` is given only that list
7144
+ * is touched — the two scopes can legitimately hold the same id after
7145
+ * loading a project authored elsewhere, and deleting a project entry must
7146
+ * not erase a local app-library style (or vice versa). Omitting `scope`
7147
+ * removes the id from both lists.
7148
+ */
7149
+ deleteStyleLibraryEntry: (id: string, scope?: "app" | "project") => void;
7150
+ /**
7151
+ * Replace the app-level Layer Library wholesale. Used by the persistence
7152
+ * layer on startup and by bundle imports.
7153
+ */
7154
+ setLayerLibrary: (entries: LayerLibraryEntry[]) => void;
7155
+ /**
7156
+ * Insert or replace (matching by `id`) a Layer Library entry. New entries go
7157
+ * to the front so the most recently saved layer leads the My Data section.
7158
+ */
7159
+ saveLayerLibraryEntry: (entry: LayerLibraryEntry) => void;
7160
+ /** Rename a Layer Library entry; a blank name is ignored. */
7161
+ renameLayerLibraryEntry: (id: string, name: string) => void;
7162
+ /** Remove a Layer Library entry by id. */
7163
+ deleteLayerLibraryEntry: (id: string) => void;
7164
+ /** Replace the app-level template library wholesale. */
7165
+ setTemplateLibrary: (templates: ProjectTemplateEntry[]) => void;
7166
+ /** Insert or replace a template in the Template Library. */
7167
+ saveTemplateEntry: (entry: ProjectTemplateEntry) => void;
7168
+ /** Remove a template entry by id from the Template Library. */
7169
+ deleteTemplateEntry: (id: string) => void;
7170
+ }
7171
+ //#endregion
7172
+ //#region src/store/map-view-slice.d.ts
7173
+ interface MapViewSlice {
7174
+ mapView: MapViewState;
7175
+ basemapStyleUrl: string;
7176
+ basemapVisible: boolean;
7177
+ basemapOpacity: number;
7178
+ blankBackgroundColor: string | null;
7179
+ preferences: ProjectPreferences;
7180
+ /**
7181
+ * Multi-map grid layout (issue: split/grid view). A 1x1 grid is the normal
7182
+ * single-map workspace. `rows * cols` panes are shown; pane 0 is the primary
7183
+ * map driven by `mapView` / `basemap*`, panes 1.. are `secondaryMapViews`.
7184
+ */
7185
+ mapLayout: MapGridLayout;
7186
+ /**
7187
+ * Secondary map panes (everything past the primary pane). The store keeps
7188
+ * exactly `rows * cols - 1` entries in sync with `mapLayout`.
7189
+ */
7190
+ secondaryMapViews: SecondaryMapView[];
7191
+ /** User-entered label for the primary pane (shown only in multi-map mode). */
7192
+ primaryMapLabel: string;
7193
+ /**
7194
+ * Which engine draws the primary map area (issue #2217): the 2D MapLibre map
7195
+ * or the 3D Cesium globe. Both render the same store state, so switching
7196
+ * keeps the camera, basemap, layers, groups, visibility, and opacity — it
7197
+ * only changes what draws them. Independent of `mapLayout`: switching never
7198
+ * adds or removes panes.
7199
+ */
7200
+ primaryRenderer: MapRendererKind;
7201
+ setMapView: (view: Partial<MapViewState>, markDirty?: boolean) => void;
7202
+ /**
7203
+ * Resize the map grid. Clamps `rows`/`cols` into range and grows/shrinks
7204
+ * `secondaryMapViews` so it always holds `rows * cols - 1` panes; new panes
7205
+ * clone the primary map's current camera and basemap.
7206
+ */
7207
+ setMapGrid: (rows: number, cols: number) => void;
7208
+ /** Toggle synchronized camera across all panes. */
7209
+ setSyncView: (syncView: boolean) => void;
7210
+ /** Patch one secondary pane's camera by id (no-op if the id is unknown). */
7211
+ setSecondaryMapView: (id: string, view: Partial<MapViewState>, markDirty?: boolean) => void;
7212
+ /**
7213
+ * Override a layer's visibility in one secondary pane (no-op if the pane id is
7214
+ * unknown). The override forces the layer visible/hidden in that pane only,
7215
+ * independent of the primary map's visibility.
7216
+ */
7217
+ setSecondaryLayerVisibility: (id: string, layerId: string, visible: boolean) => void;
7218
+ /** Set the primary pane's custom label. */
7219
+ setPrimaryMapLabel: (label: string) => void;
7220
+ /**
7221
+ * Switch the primary map area between the 2D map and the 3D globe (no-op if
7222
+ * unchanged). Touches nothing else in the store, so the shared camera, layer,
7223
+ * and basemap state carries straight across the swap.
7224
+ */
7225
+ setPrimaryRenderer: (renderer: MapRendererKind) => void;
7226
+ /** Set one secondary pane's custom label (no-op if the id is unknown). */
7227
+ setSecondaryMapLabel: (id: string, label: string) => void;
6669
7228
  /**
6670
- * Remove a Style Manager entry by id. When `scope` is given only that list
6671
- * is touched — the two scopes can legitimately hold the same id after
6672
- * loading a project authored elsewhere, and deleting a project entry must
6673
- * not erase a local app-library style (or vice versa). Omitting `scope`
6674
- * removes the id from both lists.
7229
+ * Switch one secondary pane between the 2D map and the 3D globe (no-op if the
7230
+ * id is unknown or the kind is unchanged).
6675
7231
  */
6676
- deleteStyleLibraryEntry: (id: string, scope?: "app" | "project") => void;
7232
+ setSecondaryViewKind: (id: string, viewKind: NonNullable<SecondaryMapView["viewKind"]>) => void;
7233
+ /** Remove one secondary pane and collapse the grid back toward 1x1. */
7234
+ removeSecondaryMapView: (id: string) => void;
7235
+ setBasemapStyleUrl: (url: string) => void;
6677
7236
  /**
6678
- * Replace the app-level Layer Library wholesale. Used by the persistence
6679
- * layer on startup and by bundle imports.
7237
+ * Apply a planetary basemap and sync the project's ellipsoid to the body it
7238
+ * depicts, so measurements and the globe control use that body's radius. Used
7239
+ * by both the basemap picker and the Layers-panel planet switcher.
6680
7240
  */
6681
- setLayerLibrary: (entries: LayerLibraryEntry[]) => void;
7241
+ applyPlanetaryBasemap: (basemap: PlanetaryBasemap) => void;
6682
7242
  /**
6683
- * Insert or replace (matching by `id`) a Layer Library entry. New entries go
6684
- * to the front so the most recently saved layer leads the My Data section.
7243
+ * Return to Earth: apply `styleUrl` (typically the Earth basemap that was
7244
+ * active before a planet was selected, e.g. Liberty) and reset the ellipsoid
7245
+ * to Earth. Used when a planet is deselected in the switcher.
6685
7246
  */
6686
- saveLayerLibraryEntry: (entry: LayerLibraryEntry) => void;
6687
- /** Rename a Layer Library entry; a blank name is ignored. */
6688
- renameLayerLibraryEntry: (id: string, name: string) => void;
6689
- /** Remove a Layer Library entry by id. */
6690
- deleteLayerLibraryEntry: (id: string) => void;
6691
- /** Replace the app-level template library wholesale. */
6692
- setTemplateLibrary: (templates: ProjectTemplateEntry[]) => void;
6693
- /** Insert or replace a template in the Template Library. */
6694
- saveTemplateEntry: (entry: ProjectTemplateEntry) => void;
6695
- /** Remove a template entry by id from the Template Library. */
6696
- deleteTemplateEntry: (id: string) => void;
7247
+ restoreEarthBasemap: (styleUrl: string) => void;
7248
+ setBasemapVisible: (visible: boolean) => void;
7249
+ setBasemapOpacity: (opacity: number) => void;
7250
+ setBlankBackgroundColor: (color: string | null) => void;
7251
+ setPreferences: (preferences: ProjectPreferences) => void;
7252
+ }
7253
+ //#endregion
7254
+ //#region src/store/processing-slice.d.ts
7255
+ interface ProcessingSlice {
7256
+ /** Saved processing pipelines (batch/model chaining; issue #344). */
7257
+ models: ProcessingModel[];
7258
+ /** Recorded processing tool runs, oldest first (Processing History; #1292). */
7259
+ processingHistory: ProcessingRun[];
6697
7260
  /** Insert a new model or replace an existing one matching by `id`. */
6698
7261
  saveModel: (model: ProcessingModel) => void;
6699
7262
  /** Remove a saved model by id. */
@@ -6704,6 +7267,23 @@ interface AppState {
6704
7267
  updateProcessingRun: (id: string, patch: Partial<Omit<ProcessingRun, "id">>) => void;
6705
7268
  /** Drop all recorded processing runs. */
6706
7269
  clearProcessingHistory: () => void;
7270
+ }
7271
+ //#endregion
7272
+ //#region src/store/project-content-slice.d.ts
7273
+ interface ProjectContentSlice {
7274
+ storymap: StoryMap | null;
7275
+ /** Saved Dashboard panel chart widgets (issue #401). */
7276
+ widgets: DashboardWidget[];
7277
+ /** Number of columns in the Dashboard widget grid. */
7278
+ dashboardColumns: number;
7279
+ /** Anchored review comments on map points or features (issue #1518). */
7280
+ comments: ProjectComment[];
7281
+ /**
7282
+ * The loaded project's startup `interaction` block (issue #2688). Applied
7283
+ * once on load and written back unchanged on save; live Identify and control
7284
+ * changes do not edit it.
7285
+ */
7286
+ projectInteraction: ProjectInteraction | null;
6707
7287
  /** Append a new dashboard widget. */
6708
7288
  addWidget: (widget: DashboardWidget) => void;
6709
7289
  /** Patch an existing dashboard widget by id (no-op if absent). Merges, so an
@@ -6726,6 +7306,35 @@ interface AppState {
6726
7306
  updateStoryChapter: (id: string, patch: Partial<StoryChapter>) => void;
6727
7307
  removeStoryChapter: (id: string) => void;
6728
7308
  moveStoryChapter: (id: string, targetIndex: number) => void;
7309
+ addComment: (comment: ProjectComment) => void;
7310
+ replyToComment: (commentId: string, reply: CommentReply) => void;
7311
+ toggleResolveComment: (commentId: string, resolved?: boolean) => void;
7312
+ deleteComment: (commentId: string) => void;
7313
+ setComments: (comments: ProjectComment[]) => void;
7314
+ }
7315
+ //#endregion
7316
+ //#region src/store/project-slice.d.ts
7317
+ /** Derive a human-friendly display name from a file path or URL. */
7318
+ export declare function projectPathLabel(path: string): string;
7319
+ interface ProjectSlice {
7320
+ projectName: string;
7321
+ projectPath: string | null;
7322
+ projectGeneration: number;
7323
+ isDirty: boolean;
7324
+ projectPlugins: ProjectPluginState | null;
7325
+ legend: LegendConfig;
7326
+ /** Print Layout composer settings for the open project (discussion #1992). */
7327
+ printLayout: PrintLayoutConfig;
7328
+ metadata: Record<string, unknown>;
7329
+ recentProjects: RecentProjectEntry[];
7330
+ setLegend: (legend: LegendConfig) => void;
7331
+ /**
7332
+ * Replace the Print Layout composer settings. A config equal to the current
7333
+ * one is ignored, so re-opening the composer (or a project load seeding the
7334
+ * dialog) never marks the project dirty.
7335
+ */
7336
+ setPrintLayout: (printLayout: PrintLayoutConfig) => void;
7337
+ setProjectPlugins: (projectPlugins: ProjectPluginState | null, shouldMarkDirty?: boolean) => void;
6729
7338
  newProject: (options?: CreateProjectOptions & {
6730
7339
  name?: string;
6731
7340
  }) => void;
@@ -6735,179 +7344,271 @@ interface AppState {
6735
7344
  }) => void;
6736
7345
  setProjectPath: (path: string | null) => void;
6737
7346
  setProjectName: (name: string) => void;
6738
- /**
6739
- * Narrow what this deployment may do. Intended for the startup path only —
6740
- * calling it later would leave already-rendered surfaces stale.
6741
- */
6742
- setDeploymentCapabilities: (capabilities: Iterable<DeploymentCapability>) => void;
6743
7347
  setRecentProjects: (projects: RecentProjectEntry[]) => void;
6744
7348
  rememberRecentProject: (entry: RecentProjectEntry) => void;
6745
7349
  forgetRecentProject: (path: string) => void;
6746
7350
  clearRecentProjects: () => void;
6747
7351
  markSaved: () => void;
7352
+ }
7353
+ //#endregion
7354
+ //#region src/store/session-slice.d.ts
7355
+ /**
7356
+ * Latest live device-GPS fix published by the GPS Tracking tool (issue #1316),
7357
+ * read by the status bar readout. Device state, not project state: excluded
7358
+ * from undo history (partialize never lists it) and from project files, and
7359
+ * deliberately left untouched on project switches.
7360
+ */
7361
+ interface GpsStatusFix {
7362
+ lng: number;
7363
+ lat: number;
7364
+ /** Horizontal accuracy radius in meters. */
7365
+ accuracy: number;
7366
+ /** Satellites used for the fix, or null when the provider does not report it. */
7367
+ satellites: number | null;
7368
+ /** Ground speed in m/s, or null when the device doesn't report one. */
7369
+ speed: number | null;
7370
+ /** Fix time in epoch milliseconds. */
7371
+ timestamp: number;
7372
+ }
7373
+ /** Reserved Identify target for querying every visible queryable layer at once. */
7374
+ export declare const IDENTIFY_ALL_LAYERS_ID = "__geolibre_identify_all_layers__";
7375
+ /** The Identify fields of the store, as set together by an Identify change. */
7376
+ interface IdentifyState {
7377
+ identifyLayerId: string | null;
7378
+ identifyLayerIds: string[] | null;
7379
+ }
7380
+ /**
7381
+ * Resolve an Identify target against the layers that exist.
7382
+ *
7383
+ * Shared by the scripting `setIdentify` command and by project load, which
7384
+ * applies a project's saved `interaction.identify` (issue #2688).
7385
+ *
7386
+ * @param target - A layer id, `"all"`, a list of layer ids, or null/undefined.
7387
+ * @param layerIds - Ids of the layers currently in the project.
7388
+ * @returns The store fields to set. Ids that name no layer are dropped; a list
7389
+ * that keeps one id becomes a single-layer target, and one that keeps several
7390
+ * becomes the all-layers mode restricted to those ids.
7391
+ */
7392
+ export declare function resolveIdentifyTarget(target: string | readonly string[] | null | undefined, layerIds: Iterable<string>): IdentifyState;
7393
+ /**
7394
+ * Whether the all-layers Identify mode may query a layer.
7395
+ *
7396
+ * @param layerId - The candidate layer's id.
7397
+ * @param identifyLayerIds - The store's `identifyLayerIds` restriction.
7398
+ * @returns True when there is no restriction or the layer is in it.
7399
+ */
7400
+ export declare function identifyAllIncludes(layerId: string, identifyLayerIds: readonly string[] | null | undefined): boolean;
7401
+ /**
7402
+ * Drop removed layers from the Identify state.
7403
+ *
7404
+ * @param state - The current Identify fields.
7405
+ * @param removed - Ids of the layers being removed.
7406
+ * @returns The Identify fields with those layers gone. A restricted all-layers
7407
+ * mode left with one layer narrows to it, and with none turns Identify off.
7408
+ */
7409
+ export declare function identifyStateWithoutLayers(state: IdentifyState, removed: ReadonlySet<string>): IdentifyState;
7410
+ interface SessionSlice {
7411
+ /** Local viewing switch; saved layer popup settings are left untouched. */
7412
+ hoverTooltipsEnabled: boolean;
7413
+ setHoverTooltipsEnabled: (enabled: boolean) => void;
7414
+ selectedLayerId: string | null;
7415
+ selectedFeatureId: string | null;
6748
7416
  /**
6749
- * Assign an application role (e.g. "viewer", "editor", "publisher", "administrator", "custom"),
6750
- * deriving the effective privileges and optional reason.
6751
- */
6752
- setAppRole: (role: AppRole, options?: {
6753
- customPrivileges?: AppPrivilege[];
6754
- reason?: string;
6755
- }) => void;
6756
- /** Set explicit custom privileges and an optional reason. */
6757
- setAppPrivileges: (privileges: AppPrivilege[], reason?: string) => void;
6758
- /** Grant an individual application privilege. */
6759
- grantAppPrivilege: (privilege: AppPrivilege) => void;
6760
- /** Revoke an individual application privilege with an optional reason. */
6761
- revokeAppPrivilege: (privilege: AppPrivilege, reason?: string) => void;
6762
- /** Reset application capabilities back to the default unconstrained Administrator role. */
6763
- resetAppCapabilities: () => void;
6764
- /** Check if the current capabilities grant the requested privilege. */
6765
- hasAppPrivilege: (privilege: AppPrivilege) => boolean;
6766
- addLayer: (layer: GeoLibreLayer, beforeLayerId?: string | null) => void;
6767
- removeLayer: (id: string) => void;
6768
- updateLayer: (id: string, patch: Partial<GeoLibreLayer>) => void;
6769
- setLayerVisibility: (id: string, visible: boolean) => void;
6770
- setLayerOpacity: (id: string, opacity: number) => void;
6771
- setLayerStyle: (id: string, style: Partial<LayerStyle>) => void;
6772
- /**
6773
- * Transient clipboard holding a layer's symbology, captured by
6774
- * {@link copyLayerStyle} and applied by {@link pasteLayerStyle} (copy/paste
6775
- * styles, issue #1339). Runtime-only: excluded from undo history
6776
- * (`partialize` never lists it) and from the saved project. `null` until a
6777
- * style is copied this session. Cleared by `newProject`/`loadProject` so a
6778
- * paste can't apply an entry from a different project; it deliberately
6779
- * survives undo/redo within a project (it holds a deep snapshot, not a live
6780
- * layer reference, so a paste stays valid even if the source layer is undone
6781
- * away — only the displayed source name may then be stale).
6782
- */
6783
- copiedLayerStyle: CopiedLayerStyle | null;
6784
- /**
6785
- * Snapshot the given layer's style into {@link copiedLayerStyle} so it can be
6786
- * pasted onto a compatible layer. No-op when the layer is missing or has no
6787
- * copyable symbology (leaving any prior clipboard entry untouched). Returns
6788
- * `true` when a style was captured, so callers can skip the confirmation on a
6789
- * no-op.
6790
- */
6791
- copyLayerStyle: (id: string) => boolean;
6792
- /**
6793
- * Apply the {@link copiedLayerStyle} clipboard entry onto the given layer.
6794
- * No-op when the clipboard is empty, the layer is missing, or the entry's
6795
- * style family does not match the target layer's. Returns `true` when the
6796
- * style was applied, so callers can skip the confirmation on a no-op.
6797
- */
6798
- pasteLayerStyle: (id: string) => boolean;
6799
- /**
6800
- * Replace a layer's persistent attribute joins and immediately re-derive its
6801
- * joined columns (strip what the previous joins added, apply the new list).
6802
- * Pass an empty array to detach every join and restore the base attributes.
6803
- */
6804
- setLayerJoins: (id: string, joins: LayerJoin[]) => void;
6805
- /**
6806
- * Replace the layer's Attribute Form designer configuration (per-field edit
6807
- * widgets, constraints, conditional visibility). Pass `undefined` to remove
6808
- * the form config entirely.
7417
+ * Full set of selected feature ids. The attribute table extends the single
7418
+ * selection to many rows via Ctrl/Cmd (toggle) and Shift (range). The anchor
7419
+ * — `selectedFeatureId` — is the primary/last-clicked feature used for map
7420
+ * fit, DuckDB highlight, and scripting, and is always one of these ids (or
7421
+ * `null` when the set is empty). A single click leaves exactly one id here.
6809
7422
  */
6810
- setLayerAttributeForm: (id: string, attributeForm: AttributeFormConfig | undefined) => void;
7423
+ selectedFeatureIds: string[];
6811
7424
  /**
6812
- * Replace the layer's popup/tooltip design (which fields the Identify popup
6813
- * shows, in what order and under what labels, plus the hover tooltip). Pass
6814
- * `undefined` to restore the default full-property dump.
7425
+ * Store-layer id targeted by Identify, or {@link IDENTIFY_ALL_LAYERS_ID} for
7426
+ * the map-level mode that queries every visible queryable layer — vector,
7427
+ * DuckDB query, WMS, COG, NetCDF image and time-slider raster alike.
6815
7428
  */
6816
- setLayerPopup: (id: string, popup: LayerPopupConfig | undefined) => void;
7429
+ identifyLayerId: string | null;
6817
7430
  /**
6818
- * Replace the layer's editor tracking configuration (whether creation/edit
6819
- * author and timestamp columns are maintained, and under which names). Pass
6820
- * `undefined` to drop the configuration entirely.
7431
+ * Layers the all-layers Identify mode is limited to, or null for every
7432
+ * visible queryable layer. Set only alongside {@link IDENTIFY_ALL_LAYERS_ID},
7433
+ * by a script or project that names several layers (issue #2688), and
7434
+ * cleared by every `setIdentifyLayer` call so the in-app buttons keep their
7435
+ * plain meaning.
6821
7436
  */
6822
- setLayerEditorTracking: (id: string, editorTracking: EditorTrackingConfig | undefined) => void;
7437
+ identifyLayerIds: string[] | null;
7438
+ pointerCoords: [number, number] | null;
6823
7439
  /**
6824
- * Replace a layer's virtual fields and immediately re-derive its computed
6825
- * columns (strip what the previous fields added, evaluate the new list).
6826
- * Pass an empty array to detach every virtual field.
7440
+ * Ground elevation in true metres under the pointer, for the status bar
7441
+ * (issue #1813). Null when it cannot be resolved — the pointer is off the
7442
+ * map, terrain is off and the remote lookup has not answered (or failed), or
7443
+ * the active body is not Earth. Set alongside `pointerCoords` by MapCanvas,
7444
+ * which owns the map instance the terrain sample comes from.
6827
7445
  */
6828
- setLayerVirtualFields: (id: string, fields: LayerVirtualField[]) => void;
7446
+ pointerElevation: number | null;
6829
7447
  /**
6830
- * Replace a layer's quick filters (issue #2114). The controls persist with
6831
- * the project and are compiled to a MapLibre filter at sync time, so this
6832
- * only stores state — nothing re-derives the layer's data. Pass an empty
6833
- * array to remove every control.
7448
+ * Camera height above sea level in metres — Google Earth Pro's "Eye alt"
7449
+ * (issue #1816). Derived from the camera, so deliberately *not* part of
7450
+ * `mapView`: that shape is persisted into the project file, and a stored
7451
+ * altitude could only drift from the center/zoom/pitch it is computed from.
7452
+ * Null before the map loads, or when MapLibre cannot report it.
6834
7453
  */
6835
- setLayerQuickFilters: (id: string, filters: LayerQuickFilter[]) => void;
6836
- /** Set or clear the project-persisted expression filter for a layer. */
6837
- setLayerFilterExpression: (id: string, expression: unknown[] | null) => void;
6838
- reorderLayer: (id: string, direction: "up" | "down") => void;
6839
- moveLayer: (id: string, targetIndex: number) => void;
6840
- moveLayersRelative: (layerIds: string[], targetLayerId: string, position: "above" | "below") => void;
6841
- addGeoJsonLayer: (name: string, geojson: FeatureCollection, sourcePath?: string, beforeLayerId?: string | null) => string;
6842
- /**
6843
- * Add a georeferenced image overlay (a MapLibre `image` source rendered as a
6844
- * raster layer) from an image URL and its four corner coordinates, and return
6845
- * its id. Used for KML/KMZ `<GroundOverlay>` imports; the layer persists and
6846
- * renders exactly like a Raster Georeferencer overlay. Corners are `[lng,
6847
- * lat]` in top-left, top-right, bottom-right, bottom-left order.
7454
+ cameraAltitude: number | null;
7455
+ /** Live GPS fix for the status bar, or null while GPS tracking is off. */
7456
+ gpsStatus: GpsStatusFix | null;
7457
+ attributeFilter: string;
7458
+ setPointerCoords: (coords: [number, number] | null) => void;
7459
+ setPointerElevation: (elevation: number | null) => void;
7460
+ setCameraAltitude: (altitude: number | null) => void;
7461
+ setGpsStatus: (fix: GpsStatusFix | null) => void;
7462
+ selectLayer: (id: string | null) => void;
7463
+ selectFeature: (id: string | null) => void;
7464
+ /**
7465
+ * Replace the multi-selection with `ids`. The anchor (`selectedFeatureId`)
7466
+ * becomes `anchorId` when provided, otherwise the last id in the list (or
7467
+ * `null` when the list is empty).
6848
7468
  */
6849
- addImageOverlayLayer: (name: string, source: {
6850
- url: string;
6851
- coordinates: [number, number][];
6852
- }, options?: {
6853
- opacity?: number;
6854
- bounds?: [number, number, number, number];
6855
- sourcePath?: string;
6856
- /** Initial visibility (default true); a time-slider frame past the first
6857
- * starts hidden. */
6858
- visible?: boolean;
6859
- /** Epoch-ms time bounds of a KML `<TimeSpan>`/`<TimeStamp>` frame; the
6860
- * Time Slider toggles this frame's visibility by the current date. */
6861
- timeSpan?: {
6862
- begin: number | null;
6863
- end: number | null;
6864
- };
7469
+ selectFeatures: (ids: string[], anchorId?: string | null) => void;
7470
+ setIdentifyLayer: (id: string | null) => void;
7471
+ /** Set both Identify fields at once (see {@link resolveIdentifyTarget}). */
7472
+ setIdentifyState: (state: IdentifyState) => void;
7473
+ setAttributeFilter: (filter: string) => void;
7474
+ }
7475
+ //#endregion
7476
+ //#region src/store/ui-slice.d.ts
7477
+ type ConversionToolKind = "vector-to-vector" | "vector-to-geoparquet" | "vector-to-flatgeobuf" | "vector-to-shapefile" | "vector-to-geopackage" | "csv-to-geoparquet" | "vector-to-pmtiles" | "raster-to-pmtiles" | "raster-to-cog";
7478
+ /**
7479
+ * Identifiers of the vector processing tools. Kept in sync by hand with the
7480
+ * `id` fields of `VECTOR_TOOLS` in `@geolibre/processing` (`vector-tools.ts`);
7481
+ * deriving the type there would create a core -> processing circular import.
7482
+ */
7483
+ type VectorToolKind = "buffer" | "centroids" | "convex-hull" | "dissolve" | "bounding-box" | "simplify" | "clip" | "intersection" | "difference" | "union" | "spatial-join" | "attribute-join" | "select-by-value" | "select-by-location" | "random-extract" | "reproject" | "explode" | "aggregate" | "smooth" | "extract-vertices" | "points-along-geometry" | "grid" | "voronoi" | "cell-sectors" | "dggs-grid" | "dggs-bin" | "dggs-compact" | "trajectory-speed" | "detect-stops" | "space-time-proximity" | "decode-polyline" | "encode-polyline" | "merge-layers" | "check-validity" | "fix-geometries" | "check-topology-rules" | "fix-topology";
7484
+ /** Identifiers of the network-analysis tools (`NETWORK_TOOLS` ids). */
7485
+ type NetworkToolKind = "isochrone" | "od-matrix" | "sequential-route";
7486
+ /** Identifiers of the spatial-statistics tools (`STATISTICS_TOOLS` ids). */
7487
+ type StatisticsToolKind = "global-morans-i" | "local-morans-i" | "getis-ord-gi" | "average-nearest-neighbor" | "kernel-density" | "emerging-hot-spot" | "composite-score";
7488
+ /**
7489
+ * Identifiers of the raster processing tools. Kept in sync by hand with the
7490
+ * `id` fields of `RASTER_TOOLS` in `@geolibre/processing` (`raster-tools.ts`);
7491
+ * deriving the type there would create a core -> processing circular import.
7492
+ */
7493
+ type RasterToolKind = "hillshade" | "slope" | "aspect" | "reproject" | "resample" | "clip-extent" | "clip-mask" | "polygonize" | "contour" | "interpolate" | "zonal" | "raster-calc" | "spectral-index" | "reclassify" | "mosaic" | "focal";
7494
+ interface UiSlice {
7495
+ ui: {
7496
+ processingOpen: boolean;
6865
7497
  /**
6866
- * What produced the overlay, e.g. a NetCDF grid baked to pixels. Defaults
6867
- * to the KML ground overlay this was first written for; panels gate their
6868
- * per-source controls on it.
7498
+ * Tool id to preselect when the Whitebox toolbox dialog opens, set when the
7499
+ * user picks a specific tool from the Processing menu's category submenus.
7500
+ * Consumed and cleared by ProcessingDialog. Null means "no preselection".
6869
7501
  */
6870
- sourceKind?: string;
6871
- /** Extra metadata merged onto the layer (e.g. a symbology record). */
6872
- metadata?: Record<string, unknown>;
6873
- }, beforeLayerId?: string | null) => string;
7502
+ processingInitialTool: string | null;
7503
+ conversionOpen: ConversionToolKind | null;
7504
+ vectorToolOpen: VectorToolKind | null;
7505
+ networkToolOpen: NetworkToolKind | null;
7506
+ statisticsToolOpen: StatisticsToolKind | null;
7507
+ rasterToolOpen: RasterToolKind | null;
7508
+ segmentationOpen: boolean;
7509
+ objectDetectionOpen: boolean;
7510
+ segmentEverythingOpen: boolean;
7511
+ geocodeOpen: boolean;
7512
+ sqlWorkspaceOpen: boolean;
7513
+ loadEditorFeaturesOpen: boolean;
7514
+ loadEditorFeaturesLayerId: string | null;
7515
+ pythonConsoleOpen: boolean;
7516
+ notebookOpen: boolean;
7517
+ assistantOpen: boolean;
7518
+ attributeTableOpen: boolean;
7519
+ /** Whether the Raster Attribute Table bottom panel is open (issue #1307). */
7520
+ rasterAttributeTableOpen: boolean;
7521
+ dashboardOpen: boolean;
7522
+ storymapPanelOpen: boolean;
7523
+ storymapPresenting: boolean;
7524
+ storymapReturnToEditor: boolean;
7525
+ /**
7526
+ * Layer opacities the active story presentation has applied so far, keyed
7527
+ * by store layer id. Playback fades layers by writing MapLibre paint
7528
+ * properties directly (never the persisted `layers[].opacity`), so this is
7529
+ * how renderers outside MapLibre's paint model (deck.gl diagrams, 3D
7530
+ * Z-value geometry) and the on-map Legend follow a chapter's fades. Empty
7531
+ * while not presenting; never saved with the project.
7532
+ */
7533
+ storymapLayerOpacity: Record<string, number>;
7534
+ storymapComposingId: string | null;
7535
+ /** The Batch tools dialog (run one tool across many layers). */
7536
+ batchToolsOpen: boolean;
7537
+ /** The Model Builder canvas panel (author a processing graph). */
7538
+ modelBuilderOpen: boolean;
7539
+ /** One-shot request for Model Builder to load a saved model. */
7540
+ modelBuilderRequestedModelId: string | null;
7541
+ /** Style Manager dialog visibility (issue #1294). */
7542
+ styleManagerOpen: boolean;
7543
+ /** Processing History panel visibility (#1292). */
7544
+ processingHistoryOpen: boolean;
7545
+ /** Select by Expression dialog visibility (#1314). */
7546
+ selectByExpressionOpen: boolean;
7547
+ selectByExpressionLayerId: string | null;
7548
+ /** Select by Location dialog visibility (#1314). */
7549
+ selectByLocationOpen: boolean;
7550
+ /** Same contract as `selectByExpressionLayerId`, for Select by Location. */
7551
+ selectByLocationLayerId: string | null;
7552
+ /**
7553
+ * Pending "re-run from History" request. Written by the History panel just
7554
+ * before it opens the target processing dialog; consumed and cleared by
7555
+ * that dialog once it has pre-filled its parameter form. Null when idle.
7556
+ */
7557
+ processingRerun: ProcessingRerunRequest | null;
7558
+ zoomToSelectedFeature: boolean;
7559
+ collaborateDialogOpen: boolean;
7560
+ };
7561
+ setProcessingOpen: (open: boolean) => void;
7562
+ setProcessingInitialTool: (toolId: string | null) => void;
7563
+ setConversionOpen: (kind: ConversionToolKind | null) => void;
7564
+ setVectorToolOpen: (kind: VectorToolKind | null) => void;
7565
+ setNetworkToolOpen: (kind: NetworkToolKind | null) => void;
7566
+ setStatisticsToolOpen: (kind: StatisticsToolKind | null) => void;
7567
+ setRasterToolOpen: (kind: RasterToolKind | null) => void;
7568
+ setSegmentationOpen: (open: boolean) => void;
7569
+ setObjectDetectionOpen: (open: boolean) => void;
7570
+ setSegmentEverythingOpen: (open: boolean) => void;
7571
+ setGeocodeOpen: (open: boolean) => void;
7572
+ setSqlWorkspaceOpen: (open: boolean) => void;
7573
+ setLoadEditorFeaturesOpen: (open: boolean, layerId?: string | null) => void;
7574
+ setPythonConsoleOpen: (open: boolean) => void;
7575
+ setNotebookOpen: (open: boolean) => void;
7576
+ setAssistantOpen: (open: boolean) => void;
7577
+ setAttributeTableOpen: (open: boolean) => void;
7578
+ setRasterAttributeTableOpen: (open: boolean) => void;
7579
+ setDashboardOpen: (open: boolean) => void;
7580
+ setStorymapPanelOpen: (open: boolean) => void;
7581
+ setStorymapPresenting: (presenting: boolean, returnToEditor?: boolean) => void;
6874
7582
  /**
6875
- * Add a native raster tile layer (XYZ, WMS, or WMTS) from one or more tile
6876
- * URL templates and return its id. The layer appears in the Layers panel and
6877
- * persists with the project exactly like a layer added through the Add Data
6878
- * dialog, so callers (e.g. an external plugin) do not have to touch MapLibre
6879
- * directly. See {@link AddTileLayerOptions}.
7583
+ * Record the layer opacities a story chapter applied, merging into the
7584
+ * presentation's running map (see `ui.storymapLayerOpacity`).
6880
7585
  */
6881
- addTileLayer: (name: string, options: AddTileLayerOptions, beforeLayerId?: string | null) => string;
6882
- addLayerGroup: (name?: string, layerIds?: string[]) => string;
6883
- removeLayerGroup: (id: string, options?: {
6884
- removeChildren?: boolean;
6885
- }) => void;
6886
- renameLayerGroup: (id: string, name: string) => void;
6887
- setLayerGroupVisibility: (id: string, visible: boolean) => void;
6888
- setLayerGroupOpacity: (id: string, opacity: number) => void;
6889
- toggleLayerGroupCollapsed: (id: string) => void;
6890
- moveLayerToGroup: (layerId: string, groupId: string | null, beforeLayerId?: string | null) => void;
6891
- moveLayersToGroup: (layerIds: string[], groupId: string | null, beforeLayerId?: string | null) => void;
6892
- moveLayerGroupToGroup: (id: string, parentId: string | null) => void;
6893
- reorderLayerGroup: (id: string, direction: "up" | "down") => void;
6894
- addComment: (comment: ProjectComment) => void;
6895
- replyToComment: (commentId: string, reply: CommentReply) => void;
6896
- toggleResolveComment: (commentId: string, resolved?: boolean) => void;
6897
- deleteComment: (commentId: string) => void;
6898
- setComments: (comments: ProjectComment[]) => void;
7586
+ setStorymapLayerOpacity: (changes: Record<string, number>) => void;
7587
+ setStorymapComposing: (chapterId: string | null) => void;
7588
+ setBatchToolsOpen: (open: boolean) => void;
7589
+ setModelBuilderOpen: (open: boolean) => void;
7590
+ setModelBuilderRequestedModelId: (id: string | null) => void;
7591
+ setProcessingHistoryOpen: (open: boolean) => void;
7592
+ /** Open/close Select by Expression, optionally preselecting a target layer. */
7593
+ setSelectByExpressionOpen: (open: boolean, layerId?: string | null) => void;
7594
+ /** Open/close Select by Location, optionally preselecting a target layer. */
7595
+ setSelectByLocationOpen: (open: boolean, layerId?: string | null) => void;
7596
+ setProcessingRerun: (request: ProcessingRerunRequest | null) => void;
7597
+ setCollaborateDialogOpen: (open: boolean) => void;
7598
+ setZoomToSelectedFeature: (enabled: boolean) => void;
7599
+ setStyleManagerOpen: (open: boolean) => void;
6899
7600
  }
6900
- /** Reserved Identify target for querying every visible queryable layer at once. */
6901
- export declare const IDENTIFY_ALL_LAYERS_ID = "__geolibre_identify_all_layers__";
7601
+ //#endregion
7602
+ //#region src/store/types.d.ts
6902
7603
  /**
6903
- * A fresh, inactive collaboration slice (no live session). Frozen (like
6904
- * DEFAULT_LEGEND_CONFIG) to guard against accidental in-place mutation; store
6905
- * actions always produce new objects via spread, so the frozen default is only
6906
- * ever read.
7604
+ * The whole app store: every slice's state and actions in one flat object.
7605
+ * The slices are an internal split of `store.ts`; consumers see one store.
6907
7606
  */
6908
- export declare const DEFAULT_COLLABORATION_STATE: CollaborationState;
6909
- /** Derive a human-friendly display name from a file path or URL. */
6910
- export declare function projectPathLabel(path: string): string;
7607
+ interface AppState extends ProjectSlice, MapViewSlice, LayersSlice, LayerGroupsSlice, ProjectContentSlice, ProcessingSlice, LibrariesSlice, SessionSlice, CollaborationSlice, CapabilitiesSlice, UiSlice {}
7608
+ //#endregion
7609
+ //#region src/store/undo-history.d.ts
7610
+ /** The fields undo/redo snapshots, restores and compares. */
7611
+ type HistoryState = Pick<AppState, "layers" | "layerGroups" | "basemapStyleUrl" | "basemapVisible" | "basemapOpacity" | "blankBackgroundColor" | "storymap" | "comments">;
6911
7612
  export declare function subscribeProjectRestoreHistory(listener: () => void): () => void;
6912
7613
  export declare function canUndoProjectRestore(): boolean;
6913
7614
  export declare function canRedoProjectRestore(): boolean;
@@ -6918,18 +7619,6 @@ export declare function canRedoProjectRestore(): boolean;
6918
7619
  * serialized state, so it needs a full canonical project pair.
6919
7620
  */
6920
7621
  export declare function registerProjectRestoreHistory(before: GeoLibreProject, beforePath: string | null, after: GeoLibreProject, afterPath?: string | null): void;
6921
- export declare const useAppStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<AppState>, "temporal"> & {
6922
- temporal: import("zustand").StoreApi<import("zundo").TemporalState<{
6923
- layers: GeoLibreLayer[];
6924
- layerGroups: LayerGroup[];
6925
- basemapStyleUrl: string;
6926
- basemapVisible: boolean;
6927
- basemapOpacity: number;
6928
- blankBackgroundColor: string | null;
6929
- storymap: StoryMap | null;
6930
- comments: ProjectComment[];
6931
- }>>;
6932
- }>;
6933
7622
  /**
6934
7623
  * Step the layer/basemap history back one entry and mark the project dirty.
6935
7624
  * zundo restores the partialized slice via the store's set; the resulting new
@@ -6941,6 +7630,11 @@ export declare function undo(): void;
6941
7630
  export declare function redo(): void;
6942
7631
  /** Empty both the undo and redo stacks (e.g. on new/loaded project). */
6943
7632
  export declare function clearHistory(): void;
7633
+ //#endregion
7634
+ //#region src/store.d.ts
7635
+ export declare const useAppStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<AppState>, "temporal"> & {
7636
+ temporal: import("zustand").StoreApi<import("zundo").TemporalState<HistoryState>>;
7637
+ }>;
6944
7638
  /**
6945
7639
  * React hook for consuming application capability state for a specific privilege.
6946
7640
  *
@@ -6952,6 +7646,109 @@ export declare function useAppCapability(privilege: AppPrivilege): {
6952
7646
  reason?: string;
6953
7647
  };
6954
7648
  //#endregion
7649
+ //#region src/layer-selectors.d.ts
7650
+ /**
7651
+ * The fields a layer list needs: enough to draw a row, a `<select>` option or
7652
+ * a visibility checkbox. It deliberately leaves out opacity, style, source,
7653
+ * metadata and feature data, which change far more often.
7654
+ */
7655
+ interface LayerSummary {
7656
+ /** The layer id. */
7657
+ readonly id: string;
7658
+ /** The display name. */
7659
+ readonly name: string;
7660
+ /** The layer kind. */
7661
+ readonly type: LayerType;
7662
+ /** Whether the layer is shown on the primary map. */
7663
+ readonly visible: boolean;
7664
+ /** The layer panel group the layer belongs to, if any. */
7665
+ readonly groupId: string | undefined;
7666
+ }
7667
+ /**
7668
+ * The array {@link useLayersWhen} returns while inactive. Frozen, and shared so
7669
+ * the inactive selection never changes identity.
7670
+ */
7671
+ export declare const NO_LAYERS: GeoLibreLayer[];
7672
+ /**
7673
+ * Select one layer record by id.
7674
+ *
7675
+ * @param state - The app state.
7676
+ * @param id - The layer id; `null`/`undefined` selects nothing.
7677
+ * @returns The layer record, or `undefined` when there is no such layer.
7678
+ */
7679
+ export declare function selectLayerById(state: Pick<AppState, "layers">, id: string | null | undefined): GeoLibreLayer | undefined;
7680
+ /**
7681
+ * Select the layer ids in store order (bottom to top).
7682
+ *
7683
+ * Returns a new array on every call; pair it with a shallow comparison (as
7684
+ * {@link useLayerIds} does) so an unchanged id list keeps its identity.
7685
+ *
7686
+ * @param state - The app state.
7687
+ * @returns The layer ids.
7688
+ */
7689
+ export declare function selectLayerIds(state: Pick<AppState, "layers">): string[];
7690
+ /**
7691
+ * Select a {@link LayerSummary} per layer, in store order (bottom to top).
7692
+ *
7693
+ * Each summary keeps its identity while its own fields are unchanged, so the
7694
+ * returned array is shallow-equal to the previous one after an edit that
7695
+ * touches none of those fields (opacity, style, data, metadata).
7696
+ *
7697
+ * @param state - The app state.
7698
+ * @returns One summary per layer.
7699
+ */
7700
+ export declare function selectLayerSummaries(state: Pick<AppState, "layers">): LayerSummary[];
7701
+ /**
7702
+ * Build a selector for the full `layers` array that is live only while
7703
+ * `active`.
7704
+ *
7705
+ * @param active - Whether the caller currently needs the layers.
7706
+ * @returns A selector returning `state.layers` while active, and the constant
7707
+ * {@link NO_LAYERS} otherwise.
7708
+ */
7709
+ export declare function selectLayersWhen(active: boolean): (state: Pick<AppState, "layers">) => GeoLibreLayer[];
7710
+ /**
7711
+ * Subscribe to one layer record.
7712
+ *
7713
+ * Re-renders only when that layer changes (or appears/disappears), not when
7714
+ * another layer is edited.
7715
+ *
7716
+ * @param id - The layer id; `null`/`undefined` subscribes to nothing.
7717
+ * @returns The layer record, or `undefined` when there is no such layer.
7718
+ */
7719
+ export declare function useLayer(id: string | null | undefined): GeoLibreLayer | undefined;
7720
+ /**
7721
+ * Subscribe to the ordered list of layer ids.
7722
+ *
7723
+ * Re-renders only when a layer is added, removed or reordered.
7724
+ *
7725
+ * @returns The layer ids in store order (bottom to top).
7726
+ */
7727
+ export declare function useLayerIds(): string[];
7728
+ /**
7729
+ * Subscribe to a {@link LayerSummary} per layer.
7730
+ *
7731
+ * Re-renders only when a layer is added, removed, reordered, renamed, shown or
7732
+ * hidden, retyped or regrouped; opacity, style, data and metadata edits do not
7733
+ * re-render the caller.
7734
+ *
7735
+ * @returns One summary per layer, in store order (bottom to top).
7736
+ */
7737
+ export declare function useLayerSummaries(): LayerSummary[];
7738
+ /**
7739
+ * Subscribe to the full `layers` array only while `active`.
7740
+ *
7741
+ * For always-mounted surfaces (dialogs, floating panels) that need every layer
7742
+ * record while open but keep local state across closes and so cannot simply be
7743
+ * unmounted. While inactive the caller gets the constant {@link NO_LAYERS} and
7744
+ * layer edits do not re-render it.
7745
+ *
7746
+ * @param active - Whether the caller currently needs the layers (for example,
7747
+ * whether its dialog is open).
7748
+ * @returns `layers` while active, otherwise an empty, frozen array.
7749
+ */
7750
+ export declare function useLayersWhen(active: boolean): GeoLibreLayer[];
7751
+ //#endregion
6955
7752
  //#region src/history.d.ts
6956
7753
  export declare function setHistoryCoalesceMs(ms: number): void;
6957
7754
  export declare function getHistoryCoalesceMs(): number;
@@ -7420,6 +8217,23 @@ export declare function getGoogleMapsApiKey(env?: Record<string, string | undefi
7420
8217
  * @returns The trimmed token, or undefined when unset.
7421
8218
  */
7422
8219
  export declare function getMapboxAccessToken(env?: Record<string, string | undefined>): string | undefined;
8220
+ /**
8221
+ * Resolves the ArcGIS API key from the runtime environment.
8222
+ *
8223
+ * The ArcGIS renderer draws the translated project basemap and non-Esri layers
8224
+ * without a key; Esri's basemap styles and location services require one
8225
+ * (an ArcGIS Location Platform or ArcGIS Online API key credential). It is
8226
+ * supplied via `VITE_ARCGIS_API_KEY` (baked in at build time from the bare
8227
+ * `ARCGIS_API_KEY` env var, which `vite.config.ts` copies into the prefixed
8228
+ * name) or set at runtime through Settings → Environment variables
8229
+ * (`window.__GEOLIBRE_RUNTIME_ENV__`, so a bare `ARCGIS_API_KEY` entry works
8230
+ * there too). Same precedence as {@link getMapboxAccessToken}.
8231
+ *
8232
+ * @param env - Environment record (defaults to the runtime environment);
8233
+ * injectable for testing.
8234
+ * @returns The trimmed key, or undefined when unset.
8235
+ */
8236
+ export declare function getArcgisApiKey(env?: Record<string, string | undefined>): string | undefined;
7423
8237
  /**
7424
8238
  * Resolves the Cesium Ion access token from the runtime environment.
7425
8239
  *
@@ -7486,12 +8300,31 @@ type CesiumIonAssetKind = "3d-tiles" | "imagery";
7486
8300
  export declare const CESIUM_OSM_BUILDINGS_ASSET_ID = 96188;
7487
8301
  /** Bing Maps Aerial through Ion, the imagery asset every Ion account can use. */
7488
8302
  export declare const CESIUM_BING_AERIAL_ASSET_ID = 2;
7489
- /** Ion assets offered as one-click picks in the Add Data dialog. */
7490
- export declare const CESIUM_ION_QUICK_PICKS: ReadonlyArray<{
8303
+ /**
8304
+ * Google Photorealistic 3D Tiles served through Ion. Unlike the two above it
8305
+ * is not free on every plan: Ion accounts without it get a 403 the globe
8306
+ * surfaces as a layer error.
8307
+ */
8308
+ export declare const CESIUM_GOOGLE_PHOTOREALISTIC_ASSET_ID = 2275207;
8309
+ /**
8310
+ * Which tier of the Ion catalog a quick pick comes from. `global` assets ship
8311
+ * with every Ion account; `depot` assets are Asset Depot samples the account
8312
+ * has to add once at ion.cesium.com before they stream (until then the
8313
+ * endpoint 404s and the globe reports a layer error).
8314
+ */
8315
+ type CesiumIonQuickPickGroup = "global" | "depot";
8316
+ interface CesiumIonQuickPick {
7491
8317
  assetId: number;
7492
8318
  kind: CesiumIonAssetKind;
7493
8319
  name: string;
7494
- }>;
8320
+ group: CesiumIonQuickPickGroup;
8321
+ }
8322
+ /**
8323
+ * Ion assets offered as one-click picks in the Add Data dialog. Asset ids are
8324
+ * the dropdown's option values, so they have to stay unique; the ids below are
8325
+ * the ones Cesium's own samples use.
8326
+ */
8327
+ export declare const CESIUM_ION_QUICK_PICKS: ReadonlyArray<CesiumIonQuickPick>;
7495
8328
  /** Parse an Ion asset id the way the Add Data form and the project file carry it. */
7496
8329
  export declare function parseCesiumIonAssetId(value: unknown): number | null;
7497
8330
  /**
@@ -7577,6 +8410,8 @@ interface CzmlLayerOptions {
7577
8410
  url?: string;
7578
8411
  /** Local path when loaded from disk. */
7579
8412
  sourcePath?: string;
8413
+ /** Credit shown in the globe's attribution control while the layer is present. */
8414
+ attribution?: string;
7580
8415
  }
7581
8416
  /**
7582
8417
  * Build a typed GeoLibreLayer representing a CZML dynamic scene on the Cesium globe.
@@ -7614,6 +8449,89 @@ export declare function resolveThreeDTilesRequestHeaders(url: string, headers: R
7614
8449
  */
7615
8450
  export declare function persistedThreeDTilesRequestHeaders(url: string, headers: Record<string, string> | undefined): Record<string, string> | undefined;
7616
8451
  //#endregion
8452
+ //#region src/project-credential-refs.d.ts
8453
+ export declare function geocodingApiKeyAccount(providerId: string): string;
8454
+ export declare function environmentVariableAccount(key: string): string;
8455
+ /** Rows are secret unless explicitly marked `secret: false`. */
8456
+ export declare function isSecretEnvironmentVariable(variable: RuntimeEnvironmentVariable): boolean;
8457
+ type ProjectCredentialLookup = (account: string) => string | undefined;
8458
+ /** Install (or clear, with `null`) the source of stored project credentials. */
8459
+ export declare function setProjectCredentialLookup(lookup: ProjectCredentialLookup | null): void;
8460
+ /** The stored value for `account`, or `undefined` when none (or no lookup). */
8461
+ export declare function lookupProjectCredential(account: string): string | undefined;
8462
+ /** Fill empty geocoding API keys from the stored ones. */
8463
+ export declare function overlayStoredGeocodingApiKeys(geocoding: GeocodingPreferences): GeocodingPreferences;
8464
+ /** Fill empty secret environment variable values from the stored ones. */
8465
+ export declare function overlayStoredEnvironmentVariables(variables: RuntimeEnvironmentVariable[]): RuntimeEnvironmentVariable[];
8466
+ /** Preferences with every empty stored credential filled in. */
8467
+ export declare function overlayStoredPreferenceCredentials(preferences: ProjectPreferences): ProjectPreferences;
8468
+ /**
8469
+ * The credential changes an edited Settings draft makes, keyed by account
8470
+ * (`""` deletes).
8471
+ *
8472
+ * - `seeded`: the overlaid preferences the dialog opened with.
8473
+ * - `next`: the normalized draft being saved.
8474
+ *
8475
+ * Only values the user changed are returned, so an untouched session override
8476
+ * (plaintext from the opened file) stays out of the keychain and opening a
8477
+ * file never writes it. Removing, renaming, or un-secreting a variable never
8478
+ * deletes its stored value: names are shared by every project on the device.
8479
+ * Clearing a field deletes the stored value only when the field showed that
8480
+ * stored value; clearing a file override that differed from it just drops the
8481
+ * override, so the shared value applies again and other projects keep it.
8482
+ */
8483
+ export declare function changedPreferenceCredentials(seeded: ProjectPreferences, next: ProjectPreferences): Record<string, string>;
8484
+ /**
8485
+ * Move unambiguous geocoding API keys and secret environment variable values
8486
+ * out of `project` (a copy; the input is untouched) into `secrets`, keyed by
8487
+ * account. Secret environment rows that have no unique name remain in the
8488
+ * project for an explicit keep/strip choice. Layers pass through: their
8489
+ * request headers reference variables as `${NAME}` rather than holding
8490
+ * secrets that need moving.
8491
+ */
8492
+ export declare function splitProjectCredentials(project: GeoLibreProject): {
8493
+ project: GeoLibreProject;
8494
+ secrets: Record<string, string>;
8495
+ };
8496
+ /**
8497
+ * `headers` with every `${NAME}` resolved from the project's enabled
8498
+ * Environment Variables (stored secret values filled in). Only those rows
8499
+ * count, not the OS environment or build-time values that share the runtime
8500
+ * env. A header referencing an unset variable is dropped.
8501
+ */
8502
+ export declare function resolveProjectHeaderReferences(headers: Record<string, string> | undefined): Record<string, string> | undefined;
8503
+ //#endregion
8504
+ //#region src/header-references.d.ts
8505
+ /**
8506
+ * `${NAME}` references in request header values.
8507
+ *
8508
+ * A header such as `Authorization: Bearer ${TILES_TOKEN}` is saved as typed
8509
+ * and resolved from the project's Environment Variables when a request is
8510
+ * made, so the secret itself never sits on the layer. This module is pure (no
8511
+ * store import): `credentials.ts` depends on it, and the store depends on
8512
+ * `credentials.ts`.
8513
+ */
8514
+ export declare function hasHeaderReferences(headers: Record<string, string> | undefined): boolean;
8515
+ /**
8516
+ * Whether `value` carries no secret of its own: at most a scheme word such as
8517
+ * `Bearer` followed by a single `${NAME}`. Redaction keeps such values.
8518
+ */
8519
+ export declare function isHeaderReferenceOnly(value: string): boolean;
8520
+ /**
8521
+ * Whether credential-bearing request headers may be sent to `url`: HTTPS, or
8522
+ * plain HTTP to loopback so a local dev server still works. The scheme is read
8523
+ * off a parsed URL, so an unusually cased `HTTPS://` is not misread as
8524
+ * plaintext; a relative or unparseable URL is refused.
8525
+ */
8526
+ export declare function allowsCredentialHeaders(url: string): boolean;
8527
+ /**
8528
+ * `headers` with every `${NAME}` replaced by `values[NAME]`. A header that
8529
+ * references an unset (or empty) variable is omitted rather than sent with a
8530
+ * hole in it. Returns the input unchanged when nothing references a variable,
8531
+ * and `undefined` when no header remains.
8532
+ */
8533
+ export declare function resolveHeaderReferences(headers: Record<string, string> | undefined, values: Readonly<Record<string, string>>): Record<string, string> | undefined;
8534
+ //#endregion
7617
8535
  //#region src/credentials.d.ts
7618
8536
  /**
7619
8537
  * Project fields whose values are credentials. Keep this registry as the
@@ -7859,5 +8777,5 @@ export declare function localFileName(path: string): string;
7859
8777
  /** Keep imported layers distinguishable, including within a multi-file import. */
7860
8778
  export declare function uniqueImportedLayerName(name: string, names: Iterable<string>): string;
7861
8779
  //#endregion
7862
- export type { AppState, CesiumIonAssetKind, CesiumIonLayerOptions, CesiumKmlLayerOptions, ConversionToolKind, CredentialRedactionResult, CzmlDocument, CzmlLayerOptions, CzmlPacket, CzmlSource, GeocodeMatch, GeocodeRequest, GeocodeStatus, GeocoderConfig, GeocodingPreferenceInput, GeocodingProvider, GeocodingProviderId, GpsStatusFix, NetworkToolKind, NominatimForwardResult, NominatimReverseResult, RasterToolKind, ReverseGeocodeDisplay, StatisticsToolKind, VectorToolKind };
8780
+ export type { AppState, CesiumIonAssetKind, CesiumIonLayerOptions, CesiumIonQuickPick, CesiumIonQuickPickGroup, CesiumKmlLayerOptions, ConversionToolKind, CredentialRedactionResult, CzmlDocument, CzmlLayerOptions, CzmlPacket, CzmlSource, GeocodeMatch, GeocodeRequest, GeocodeStatus, GeocoderConfig, GeocodingPreferenceInput, GeocodingProvider, GeocodingProviderId, GpsStatusFix, IdentifyState, LayerSummary, NetworkToolKind, NominatimForwardResult, NominatimReverseResult, ProjectCredentialLookup, RasterToolKind, ReverseGeocodeDisplay, StatisticsToolKind, VectorToolKind };
7863
8781
  //# sourceMappingURL=index.d.mts.map