geonetwork-ui 2.11.0-dev.a83f0f51a → 2.11.0-dev.ca7fb6b05

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/fesm2022/geonetwork-ui.mjs +627 -499
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +83 -50
  4. package/index.d.ts.map +1 -1
  5. package/package.json +11 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +1 -0
  8. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  9. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  10. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  11. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  12. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  13. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  14. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  15. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  16. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  17. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  18. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  19. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  20. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  21. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  22. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -5
  23. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  24. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  25. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  26. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  27. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  28. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  29. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  30. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  31. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  32. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  33. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  34. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  35. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  36. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  37. package/translations/de.json +1 -0
  38. package/translations/en.json +1 -0
  39. package/translations/es.json +1 -0
  40. package/translations/fr.json +1 -0
  41. package/translations/it.json +1 -0
  42. package/translations/nl.json +1 -0
  43. package/translations/pt.json +1 -0
  44. package/translations/sk.json +1 -0
  45. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -6,7 +6,7 @@ import GeoJSON from 'ol/format/GeoJSON.js';
6
6
  import { parse as parse$1 } from 'ol/xml.js';
7
7
  import { format } from 'date-fns/format';
8
8
  import { Namespace, Literal, lit, parse as parse$2, sym, BlankNode, graph } from 'rdflib';
9
- import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, combineLatest, from, exhaustMap, throwError, forkJoin, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
9
+ import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, forkJoin, combineLatest, from, exhaustMap, throwError, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
10
10
  import * as i0 from '@angular/core';
11
11
  import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, HostListener, Input, Directive, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, afterNextRender, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
@@ -79,10 +79,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
79
  import Duration from 'duration-relativetimeformat';
80
80
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
81
  import { trigger, transition, animate, keyframes, style } from '@angular/animations';
82
- import * as Papa from 'papaparse';
83
- import { parse as parse$4 } from 'date-fns/parse';
84
- import { parseISO } from 'date-fns/parseISO';
85
- import { WFS, GeoJSON as GeoJSON$1 } from 'ol/format.js';
82
+ import * as duckdb from '@duckdb/duckdb-wasm';
83
+ import { Vector, DataType } from 'apache-arrow';
84
+ import WFS from 'ol/format/WFS';
85
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
86
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
87
  import * as i2$4 from '@angular/material/sort';
88
88
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +90,6 @@ import * as i1$b from '@angular/material/table';
90
90
  import { MatTableModule } from '@angular/material/table';
91
91
  import * as i3 from '@angular/material/paginator';
92
92
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
93
  import { Meta } from '@angular/platform-browser';
95
94
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
95
  import * as i3$1 from '@angular/material/radio';
@@ -18907,14 +18906,29 @@ class Gn4FieldMapper {
18907
18906
  featureTypes: selectField(source, 'featureTypes'),
18908
18907
  }, output),
18909
18908
  related: (output, source) => {
18910
- const fcatSource = selectField(getFirstValue(selectField(selectField(source, 'related'), 'fcats')) ?? {}, '_source');
18909
+ const related = selectField(source, 'related');
18910
+ const fcatSource = selectField(getFirstValue(selectField(related, 'fcats')) ?? {}, '_source');
18911
18911
  const featureCatalogIdentifier = selectField(fcatSource, 'uuid');
18912
- const sourceOfLinks = getAsArray(selectField(selectField(source, 'related'), 'hassources'));
18912
+ const sourceOfLinks = getAsArray(selectField(related, 'hassources'));
18913
18913
  const sourceOfIdentifiers = sourceOfLinks
18914
18914
  .filter((link) => link['origin'] === 'catalog')
18915
18915
  .map((link) => {
18916
18916
  return selectField(selectField(link, '_source'), 'uuid');
18917
18917
  });
18918
+ const siblingLinks = getAsArray(selectField(related, 'siblings'));
18919
+ const siblings = siblingLinks
18920
+ .filter((link) => link['origin'] === 'catalog')
18921
+ .map((link) => ({
18922
+ uniqueIdentifier: selectField(selectField(link, '_source'), 'uuid'),
18923
+ associationType: getAssociationTypeFromCode(selectField(selectField(link, 'properties'), 'associationType')),
18924
+ }));
18925
+ const associatedLinks = getAsArray(selectField(related, 'associated'));
18926
+ const associatedIdentifiers = associatedLinks
18927
+ .filter((link) => link['origin'] === 'catalog')
18928
+ .map((link) => {
18929
+ return selectField(selectField(link, '_source'), 'uuid');
18930
+ })
18931
+ .filter((uuid) => !siblings.some((sibling) => sibling.uniqueIdentifier === uuid));
18918
18932
  const extraValues = {};
18919
18933
  if (featureCatalogIdentifier) {
18920
18934
  extraValues.featureCatalogIdentifier = featureCatalogIdentifier;
@@ -18922,6 +18936,12 @@ class Gn4FieldMapper {
18922
18936
  if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
18923
18937
  extraValues.sourceOfIdentifiers = sourceOfIdentifiers;
18924
18938
  }
18939
+ if (associatedIdentifiers && associatedIdentifiers.length > 0) {
18940
+ extraValues.associatedIdentifiers = associatedIdentifiers;
18941
+ }
18942
+ if (siblings && siblings.length > 0) {
18943
+ extraValues.siblings = siblings;
18944
+ }
18925
18945
  return Object.keys(extraValues).length > 0
18926
18946
  ? this.addExtra(extraValues, output)
18927
18947
  : output;
@@ -20369,7 +20389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20369
20389
  }] } });
20370
20390
 
20371
20391
  var name = "geonetwork-ui";
20372
- var version = "2.11.0-dev.a83f0f51a";
20392
+ var version = "2.11.0-dev.ca7fb6b05";
20373
20393
  var engines = {
20374
20394
  node: ">=24"
20375
20395
  };
@@ -20399,20 +20419,21 @@ var peerDependencies = {
20399
20419
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20420
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20421
  "@ngrx/operators": "19.x || 20.x || 21.x",
20402
- "@ngx-translate/core": "16.x",
20403
- "@ngx-translate/http-loader": "16.x",
20404
- "flag-icons": "^7.3.2",
20422
+ "@ngx-translate/core": "17.x",
20423
+ "@ngx-translate/http-loader": "17.x",
20424
+ "flag-icons": "~7.5.0",
20405
20425
  rxjs: "7.x",
20406
20426
  "zone.js": "*",
20407
20427
  tailwindcss: "3.x"
20408
20428
  };
20409
20429
  var dependencies = {
20410
20430
  "@biesbjerg/ngx-translate-extract-marker": "~1.0.0",
20411
- "@camptocamp/ogc-client": "1.3.1-dev.bb345f0",
20412
- "@geospatial-sdk/core": "0.0.5-dev.61",
20413
- "@geospatial-sdk/geocoding": "0.0.5-dev.61",
20414
- "@geospatial-sdk/legend": "0.0.5-dev.61",
20415
- "@geospatial-sdk/openlayers": "0.0.5-dev.61",
20431
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20432
+ "@duckdb/duckdb-wasm": "~1.32.0",
20433
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20434
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20435
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20436
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20437
  "@ltd/j-toml": "~1.38.0",
20417
20438
  "@messageformat/core": "~3.4.0",
20418
20439
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20441,7 @@ var dependencies = {
20420
20441
  "@ng-icons/material-icons": "~32.5.0",
20421
20442
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20443
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20444
+ "apache-arrow": "~17.0.0",
20424
20445
  "chart.js": "~4.5.1",
20425
20446
  "chroma-js": "~3.2.0",
20426
20447
  "date-fns": "4.1.0",
@@ -20433,7 +20454,6 @@ var dependencies = {
20433
20454
  "ngx-dropzone": "~3.1.0",
20434
20455
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20456
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20457
  proj4: "~2.20.4",
20438
20458
  rdflib: "~2.3.5",
20439
20459
  semver: "~7.7.4",
@@ -20678,6 +20698,7 @@ class ElasticsearchService {
20678
20698
  values: uuids,
20679
20699
  },
20680
20700
  },
20701
+ size: uuids.length,
20681
20702
  };
20682
20703
  }
20683
20704
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -21229,7 +21250,7 @@ class Gn4Repository {
21229
21250
  }
21230
21251
  getRecord(uniqueIdentifier) {
21231
21252
  return this.gn4SearchApi
21232
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21253
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21233
21254
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21234
21255
  }
21235
21256
  getMultipleRecords(uniqueIdentifiers) {
@@ -21283,19 +21304,28 @@ class Gn4Repository {
21283
21304
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21284
21305
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21285
21306
  }
21286
- getSources(record) {
21287
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21288
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21289
- return this.getMultipleRecords(sourcesIdentifiers);
21290
- }
21291
- return of(null);
21292
- }
21293
- getSourceOf(record) {
21294
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21295
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21296
- return this.getMultipleRecords(sourceOfIdentifiers);
21307
+ getLinkedRecords(record) {
21308
+ const siblings = (record.extras?.['siblings'] ?? []);
21309
+ const relations = [
21310
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21311
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21312
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21313
+ [
21314
+ 'associated',
21315
+ (record.extras?.['associatedIdentifiers'] ?? []),
21316
+ ],
21317
+ ];
21318
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21319
+ if (requested.length === 0) {
21320
+ return of([]);
21297
21321
  }
21298
- return of(null);
21322
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21323
+ record,
21324
+ relation,
21325
+ associationType: relation === 'sibling'
21326
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21327
+ : undefined,
21328
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21299
21329
  }
21300
21330
  aggregate(params) {
21301
21331
  // if aggregations are empty, return an empty object right away
@@ -22556,9 +22586,8 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22556
22586
  });
22557
22587
 
22558
22588
  const DEFAULT_BASEMAP_LAYER = {
22559
- type: 'xyz',
22560
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22561
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22589
+ type: 'maplibre-style',
22590
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22562
22591
  };
22563
22592
  const DEFAULT_VIEW = {
22564
22593
  center: [0, 15],
@@ -32512,8 +32541,7 @@ const closeMetadata = createAction('[Metadata view] close');
32512
32541
  Related actions
32513
32542
  */
32514
32543
  const setRelated = createAction('[Metadata view] Set related records', props());
32515
- const setSources = createAction('[Metadata view] Set sources', props());
32516
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32544
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32517
32545
  /*
32518
32546
  ChartConfig actions
32519
32547
  */
@@ -32545,9 +32573,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32545
32573
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32546
32574
  setChartConfig: setChartConfig,
32547
32575
  setIncompleteMetadata: setIncompleteMetadata,
32548
- setRelated: setRelated,
32549
- setSourceOf: setSourceOf,
32550
- setSources: setSources
32576
+ setLinkedRecords: setLinkedRecords,
32577
+ setRelated: setRelated
32551
32578
  });
32552
32579
 
32553
32580
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32590,12 +32617,9 @@ on(loadFullMetadata, (state) => ({
32590
32617
  on(setRelated, (state, { related }) => ({
32591
32618
  ...state,
32592
32619
  related,
32593
- })), on(setSources, (state, { sources }) => ({
32594
- ...state,
32595
- sources,
32596
- })), on(setSourceOf, (state, { sourceOf }) => ({
32620
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32597
32621
  ...state,
32598
- sourceOf,
32622
+ linkedRecords,
32599
32623
  })),
32600
32624
  /*
32601
32625
  ChartConfig reducers
@@ -32657,8 +32681,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32657
32681
  Related selectors
32658
32682
  */
32659
32683
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32660
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32661
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
32684
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32662
32685
  /*
32663
32686
  Metadata selectors
32664
32687
  */
@@ -32759,212 +32782,10 @@ function parseHeaders(httpHeaders) {
32759
32782
  return result;
32760
32783
  }
32761
32784
 
32762
- async function inferDatasetType(url, typeHint) {
32763
- const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32764
- const fileExtension = fileExtensionMatches && fileExtensionMatches.length
32765
- ? fileExtensionMatches[1].toLowerCase()
32766
- : null;
32767
- // 1. type hint
32768
- if (typeHint)
32769
- return Promise.resolve(typeHint);
32770
- // 2. content-type header
32771
- const headers = await fetchHeaders(url);
32772
- if ('supportedType' in headers)
32773
- return headers.supportedType;
32774
- // 3. file extension from url
32775
- else if (SupportedTypes.indexOf(fileExtension) > -1)
32776
- return fileExtension;
32777
- // no type inferred or hinted
32778
- if ('mimeType' in headers)
32779
- throw FetchError.unsupportedType(headers.mimeType);
32780
- else
32781
- throw FetchError.unknownType();
32782
- }
32783
- function fetchHeaders(url) {
32784
- return sharedFetch(url, 'HEAD')
32785
- .catch((error) => {
32786
- throw FetchError.corsOrNetwork(error.message);
32787
- })
32788
- .then((response) => {
32789
- if (!response.ok) {
32790
- throw FetchError.http(response.status);
32791
- }
32792
- return parseHeaders(response.headers);
32793
- });
32794
- }
32795
- function fetchDataAsText(url, cacheActive) {
32796
- const fetchFactory = () => sharedFetch(url)
32797
- .catch((error) => {
32798
- throw FetchError.corsOrNetwork(error.message);
32799
- })
32800
- .then(async (response) => {
32801
- if (!response.ok) {
32802
- const clonedResponse = response.clone();
32803
- throw FetchError.http(response.status, await clonedResponse.text());
32804
- }
32805
- const clonedResponse = response.clone();
32806
- return clonedResponse.text();
32807
- });
32808
- return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
32809
- }
32810
- function fetchDataAsArrayBuffer(url, cacheActive) {
32811
- const fetchFactory = () => sharedFetch(url)
32812
- .catch((error) => {
32813
- throw FetchError.corsOrNetwork(error.message);
32814
- })
32815
- .then(async (response) => {
32816
- if (!response.ok) {
32817
- throw FetchError.http(response.status, await response.text());
32818
- }
32819
- // convert to a numeric array so that we can store the response in cache
32820
- return Array.from(new Uint8Array(await response.arrayBuffer()));
32821
- });
32822
- return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
32823
- return new Uint8Array(array).buffer;
32824
- });
32825
- }
32826
- function tryParseDate(input) {
32827
- if (typeof input !== 'string')
32828
- return null;
32829
- function tryIso(value) {
32830
- const parsed = parseISO(value);
32831
- return isNaN(parsed.getDate()) ? null : parsed;
32832
- }
32833
- function tryFormat(value, format) {
32834
- const parsed = parse$4(value, format, new Date());
32835
- return isNaN(parsed.getDate()) ? null : parsed;
32836
- }
32837
- return (tryIso(input) ||
32838
- tryFormat(input, 'dd/MM/yyyy') ||
32839
- tryFormat(input, 'dd.MM.yyyy') ||
32840
- tryFormat(input, 'MM/dd/yyyy') ||
32841
- null);
32842
- }
32843
- function tryParseNumber(input) {
32844
- if (isNaN(input))
32845
- return null;
32846
- const parsed = parseFloat(input);
32847
- return isNaN(parsed) ? null : parsed;
32848
- }
32849
- function jsonToGeojsonFeature(object) {
32850
- const { id, properties } = Object.keys(object)
32851
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32852
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32853
- ? {
32854
- ...prev,
32855
- id: object[curr],
32856
- }
32857
- : {
32858
- ...prev,
32859
- properties: { ...prev.properties, [curr]: object[curr] },
32860
- }, { id: undefined, properties: {} });
32861
- return {
32862
- type: 'Feature',
32863
- geometry: null,
32864
- properties,
32865
- ...(id !== undefined && { id }),
32866
- };
32867
- }
32868
- function mutateProperties(items, mutators) {
32869
- const mutatorKeys = Object.keys(mutators);
32870
- for (let i = 0, ii = items.length; i < ii; i++) {
32871
- const item = items[i];
32872
- for (const mutatorField of mutatorKeys) {
32873
- if (!(mutatorField in item.properties))
32874
- continue;
32875
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32876
- }
32877
- }
32878
- return items;
32879
- }
32880
- const SAMPLE_SIZE = 20;
32881
- /**
32882
- * This will infer field types from a list of data items and cast the values accordingly
32883
- * @param items
32884
- * @param inferTypes
32885
- */
32886
- function processItemProperties(items, inferTypes = false) {
32887
- const foundFields = {};
32888
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32889
- const item = items[i];
32890
- const fields = Object.keys(item.properties);
32891
- for (const field of fields) {
32892
- if (!(field in foundFields)) {
32893
- foundFields[field] = {
32894
- label: field,
32895
- name: field,
32896
- type: null,
32897
- };
32898
- }
32899
- const value = item.properties[field];
32900
- const info = foundFields[field];
32901
- if (value === undefined || value === '' || value === null)
32902
- continue;
32903
- if (!inferTypes) {
32904
- if (info.type === null && typeof value === 'number') {
32905
- info.type = 'number';
32906
- }
32907
- else if (info.type === 'number' && typeof value !== 'number') {
32908
- info.type = 'string';
32909
- }
32910
- continue;
32911
- }
32912
- const parsedNumber = tryParseNumber(value);
32913
- if (info.type === null && parsedNumber !== null) {
32914
- info.type = 'number';
32915
- continue;
32916
- }
32917
- else if (info.type === 'number' && parsedNumber === null) {
32918
- info.type = 'string';
32919
- continue;
32920
- }
32921
- const parsedDate = tryParseDate(value);
32922
- if (info.type === null && parsedDate !== null) {
32923
- info.type = 'date';
32924
- }
32925
- else if (info.type === 'date' && parsedDate === null) {
32926
- info.type = 'string';
32927
- }
32928
- }
32929
- }
32930
- const properties = [];
32931
- const mutators = {};
32932
- for (const field in foundFields) {
32933
- const info = foundFields[field];
32934
- if (info.type === 'number') {
32935
- mutators[field] = tryParseNumber;
32936
- }
32937
- else if (info.type === 'date') {
32938
- mutators[field] = tryParseDate;
32939
- }
32940
- properties.push({ ...info, type: info.type || 'string' });
32941
- }
32942
- if (inferTypes) {
32943
- mutateProperties(items, mutators);
32944
- }
32945
- return { items, properties };
32946
- }
32947
- /**
32948
- * This creates a Proxy that allows reading and writing to the data item properties
32949
- * as if it was a simple array of JSON objects
32950
- * @param items
32951
- */
32952
- function getJsonDataItemsProxy(items) {
32953
- return new Proxy(items, {
32954
- get(target, p) {
32955
- if (typeof p === 'string' &&
32956
- !Number.isNaN(parseInt(p)) &&
32957
- target[p]?.properties) {
32958
- return target[p].properties;
32959
- }
32960
- return target[p];
32961
- },
32962
- set() {
32963
- throw new Error('This object is read-only');
32964
- },
32965
- });
32785
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
32786
+ function fieldToSql(name) {
32787
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32966
32788
  }
32967
-
32968
32789
  function filterToSql(filter) {
32969
32790
  const operator = filter[0];
32970
32791
  const args = filter.slice(1);
@@ -32979,10 +32800,10 @@ function filterToSql(filter) {
32979
32800
  case '=':
32980
32801
  case '!=':
32981
32802
  case 'like':
32982
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32803
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32983
32804
  case 'in': {
32984
32805
  const values = args.slice(1);
32985
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
32806
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32986
32807
  }
32987
32808
  case 'and':
32988
32809
  case 'or': {
@@ -33001,17 +32822,18 @@ function aggregationToSql(aggregation) {
33001
32822
  const field = aggregation[1];
33002
32823
  switch (operation) {
33003
32824
  case 'average':
33004
- return `AVG([${field}]) as [average(${field})]`;
32825
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
33005
32826
  case 'sum':
33006
32827
  case 'max':
33007
32828
  case 'min':
33008
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
32829
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
33009
32830
  case 'count':
33010
- return 'COUNT(*) as [count()]';
32831
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
33011
32832
  }
33012
32833
  }
33013
32834
  /**
33014
32835
  * Leave arguments at null if not used
32836
+ * @param tableName
33015
32837
  * @param selected
33016
32838
  * @param filter
33017
32839
  * @param sort
@@ -33020,22 +32842,25 @@ function aggregationToSql(aggregation) {
33020
32842
  * @param groupBy
33021
32843
  * @param aggregations
33022
32844
  */
33023
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
32845
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33024
32846
  let sqlSelect = 'SELECT *';
33025
- const sqlFrom = ' FROM ?';
32847
+ const sqlFrom = ` FROM ${tableName}`;
33026
32848
  let sqlOrderBy = '';
33027
32849
  let sqlWhere = '';
33028
32850
  let sqlLimit = '';
33029
32851
  let sqlGroupBy = '';
33030
32852
  if (selected !== null) {
33031
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
32853
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
32854
+ }
32855
+ if (geometryColumn !== null) {
32856
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33032
32857
  }
33033
32858
  if (filter !== null) {
33034
32859
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33035
32860
  }
33036
32861
  if (sort?.length) {
33037
32862
  sqlOrderBy = ` ORDER BY ${sort
33038
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
32863
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33039
32864
  .join(', ')}`;
33040
32865
  }
33041
32866
  if (startIndex !== null && count !== null) {
@@ -33045,17 +32870,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33045
32870
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33046
32871
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33047
32872
  const sqlGroupByFields = groupedByDistinct
33048
- .map((group) => `[${group[1]}]`)
32873
+ .map((group) => fieldToSql(group[1]))
33049
32874
  .join(', ');
33050
32875
  const sqlGroupBySelect = groupedByDistinct
33051
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
32876
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33052
32877
  .join(', ');
33053
32878
  if (sqlGroupByFields && sqlGroupBySelect) {
33054
32879
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33055
32880
  sqlSelect += `, ${sqlGroupBySelect}`;
33056
32881
  }
33057
32882
  }
33058
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
32883
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
32884
+ }
32885
+
32886
+ function arrowTableToDataItems(table) {
32887
+ const fields = table.schema.fields;
32888
+ return table.toArray().map((row) => {
32889
+ const rowJson = row.toJSON();
32890
+ const feature = {
32891
+ type: 'Feature',
32892
+ geometry: null,
32893
+ properties: {},
32894
+ };
32895
+ const keys = Object.keys(rowJson);
32896
+ for (let i = 0; i < keys.length; i++) {
32897
+ const key = keys[i];
32898
+ const dataType = fields[i].type;
32899
+ let value = rowJson[key];
32900
+ // this might happen if we get an array inside a field
32901
+ if (value instanceof Vector) {
32902
+ value = Array.from(value);
32903
+ }
32904
+ // cast bigints to ints
32905
+ if (typeof value === 'bigint') {
32906
+ value = Number(value);
32907
+ }
32908
+ // rename columns with empty name
32909
+ if (!key) {
32910
+ feature.properties['unknown'] = value;
32911
+ continue;
32912
+ }
32913
+ // assign properties that look like an id to the geojson `id` field
32914
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
32915
+ (typeof value == 'string' || typeof value === 'number')) {
32916
+ feature.id = value;
32917
+ }
32918
+ // if a date is in timestamp (number) format, convert it to native
32919
+ if (typeof value === 'number' &&
32920
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
32921
+ value = new Date(value);
32922
+ }
32923
+ // if a binary field (most likely a geometry column): skip
32924
+ if (DataType.isBinary(dataType)) {
32925
+ continue;
32926
+ }
32927
+ // geometry column
32928
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
32929
+ feature.geometry = JSON.parse(value);
32930
+ continue;
32931
+ }
32932
+ feature.properties[key] = value;
32933
+ }
32934
+ return feature;
32935
+ });
32936
+ }
32937
+
32938
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
32939
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
32940
+ // from https://duckdb.org/docs/current/sql/data_types/overview
32941
+ const typesMapping = {
32942
+ INTEGER: 'number',
32943
+ SMALLINT: 'number',
32944
+ TINYINT: 'number',
32945
+ BIGINT: 'number',
32946
+ HUGEINT: 'number',
32947
+ UINTEGER: 'number',
32948
+ USMALLINT: 'number',
32949
+ UTINYINT: 'number',
32950
+ UBIGINT: 'number',
32951
+ UHUGEINT: 'number',
32952
+ DOUBLE: 'number',
32953
+ BIGNUM: 'number',
32954
+ DECIMAL: 'number',
32955
+ NUMERIC: 'number',
32956
+ FLOAT: 'number',
32957
+ REAL: 'number',
32958
+ 'TIMESTAMP WITH TIME ZONE': 'date',
32959
+ TIMESTAMP: 'date',
32960
+ DATE: 'date',
32961
+ CHAR: 'string',
32962
+ VARCHAR: 'string',
32963
+ TEXT: 'string',
32964
+ UUID: 'string',
32965
+ BLOB: 'string',
32966
+ BIT: 'string',
32967
+ INTERVAL: 'string',
32968
+ LIST: 'string',
32969
+ BOOLEAN: 'boolean',
32970
+ };
32971
+ class Engine {
32972
+ async makeInit() {
32973
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
32974
+ let worker_url = bundle.mainWorker;
32975
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
32976
+ if (worker_url.startsWith('https://')) {
32977
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
32978
+ type: 'text/javascript',
32979
+ }));
32980
+ }
32981
+ const worker = new Worker(worker_url);
32982
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
32983
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
32984
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
32985
+ // setup duckdb options
32986
+ const conn = await this.db.connect();
32987
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
32988
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
32989
+ await conn.query(`SET TimeZone = '${timezone}';`);
32990
+ await conn.query(`SET threads = 1;`);
32991
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
32992
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
32993
+ conn.close();
32994
+ return this;
32995
+ }
32996
+ isReady() {
32997
+ if (!this.init_) {
32998
+ this.init_ = this.makeInit();
32999
+ }
33000
+ return this.init_;
33001
+ }
33002
+ /**
33003
+ * Returns information about a dataset once it's loaded:
33004
+ * - a list of properties description
33005
+ * - the name of the dataset geometry column (null if no geometry present)
33006
+ * @param datasetId name of the table under which the dataset will be stored
33007
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33008
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33009
+ */
33010
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33011
+ const conn = await this.db.connect();
33012
+ let results;
33013
+ // either we want to recreate the table, or we keep it if it already exists
33014
+ const safeLoadQuery = forceReload
33015
+ ? `DROP TABLE IF EXISTS ${datasetId};
33016
+ ${loadQuery}`
33017
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33018
+ // create the table
33019
+ try {
33020
+ results = await conn.query(safeLoadQuery);
33021
+ }
33022
+ catch (e) {
33023
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33024
+ }
33025
+ // read rows count
33026
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33027
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33028
+ // read columns
33029
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33030
+ let geometryColumn = null;
33031
+ const properties = results
33032
+ .toArray()
33033
+ .map((row) => {
33034
+ const rowObj = row.toJSON();
33035
+ if (rowObj['data_type'] === 'GEOMETRY') {
33036
+ // the geometry is not part of the properties
33037
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33038
+ // all get discarded
33039
+ geometryColumn = rowObj['column_name'];
33040
+ return null;
33041
+ }
33042
+ return {
33043
+ name: rowObj['column_name'],
33044
+ label: rowObj['column_name'],
33045
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33046
+ };
33047
+ })
33048
+ .filter((prop) => prop !== null);
33049
+ conn.close();
33050
+ return {
33051
+ properties,
33052
+ geometryColumn,
33053
+ rowsCount: Number(recordsCount),
33054
+ };
33055
+ }
33056
+ // register a Uint8 buffer using a handle in the duckdb instance
33057
+ async registerData(name, buffer) {
33058
+ return this.db.registerFileBuffer(name, buffer);
33059
+ }
33060
+ /**
33061
+ * @param query duckdb-specific query for fetching items
33062
+ */
33063
+ async queryItems(query) {
33064
+ const conn = await this.db.connect();
33065
+ const results = await conn.query(query);
33066
+ conn.close();
33067
+ return arrowTableToDataItems(results);
33068
+ }
33069
+ close() {
33070
+ this.db?.terminate();
33071
+ }
33072
+ }
33073
+ let engine = null;
33074
+ async function getEngine() {
33075
+ if (!engine) {
33076
+ engine = new Engine();
33077
+ }
33078
+ return engine.isReady();
33059
33079
  }
33060
33080
 
33061
33081
  class BaseReader {
@@ -33068,10 +33088,18 @@ class BaseReader {
33068
33088
  this.sort = null;
33069
33089
  this.startIndex = null;
33070
33090
  this.count = null;
33091
+ this.loadPromise_ = Promise.resolve();
33092
+ this.cacheEnabled = false;
33093
+ }
33094
+ enableCache(enabled) {
33095
+ this.cacheEnabled = enabled;
33071
33096
  }
33072
33097
  load() {
33073
33098
  throw new Error('not implemented');
33074
33099
  }
33100
+ get isLoaded() {
33101
+ return this.loadPromise_;
33102
+ }
33075
33103
  get properties() {
33076
33104
  throw new Error('not implemented');
33077
33105
  }
@@ -33120,178 +33148,237 @@ class BaseReader {
33120
33148
  }
33121
33149
  }
33122
33150
 
33123
- class BaseCacheReader extends BaseReader {
33124
- constructor(url, cacheActive = true) {
33125
- super(url);
33126
- this.url = url;
33127
- this.cacheActive = cacheActive;
33128
- }
33129
- setCacheActive(value) {
33130
- this.cacheActive = value;
33131
- }
33132
- }
33133
-
33134
- class BaseFileReader extends BaseCacheReader {
33135
- getData() {
33151
+ /**
33152
+ * This reader handles file formats supported natively by DuckDB
33153
+ */
33154
+ class BaseFileReader extends BaseReader {
33155
+ // a table id should not exceed 63 chars
33156
+ generateDatasetId() {
33157
+ // generate a hash out of the url
33158
+ let hash = 0;
33159
+ for (const char of this.url) {
33160
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33161
+ hash = hash >>> 0; // make it unsigned
33162
+ }
33163
+ return `datafetcher_${hash.toString(16)}`;
33164
+ }
33165
+ async getLoadQuery() {
33136
33166
  throw new Error('not implemented');
33137
33167
  }
33138
- load() {
33139
- this.parseResult_ = this.getData();
33168
+ async load() {
33169
+ this.datasetId = this.generateDatasetId();
33170
+ this.loadPromise_ = getEngine()
33171
+ .then((engine) => {
33172
+ this.engine = engine;
33173
+ return this.getLoadQuery();
33174
+ })
33175
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33176
+ .then((datasetInfo) => {
33177
+ this.properties_ = datasetInfo.properties;
33178
+ this.geometryColumn = datasetInfo.geometryColumn;
33179
+ this.rowsCount = datasetInfo.rowsCount;
33180
+ // returns void for the loaded promise
33181
+ });
33140
33182
  }
33141
33183
  get properties() {
33142
- return this.parseResult_.then((result) => result.properties);
33184
+ return this.isLoaded.then(() => this.properties_);
33143
33185
  }
33144
33186
  get info() {
33145
- return this.parseResult_.then((result) => ({
33146
- itemsCount: result.items.length,
33187
+ return this.isLoaded.then(() => ({
33188
+ itemsCount: this.rowsCount,
33189
+ hasGeometry: !!this.geometryColumn,
33147
33190
  }));
33148
33191
  }
33149
33192
  async read() {
33150
- const items = (await this.parseResult_).items;
33151
- // no query defined: return the full results as is
33152
- if (this.groupedBy == null &&
33153
- this.aggregations == null &&
33154
- this.selected == null &&
33155
- this.sort == null &&
33156
- this.filter == null &&
33157
- this.startIndex == null &&
33158
- this.count == null) {
33159
- return items;
33160
- }
33161
- const jsonItems = getJsonDataItemsProxy(items);
33162
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33163
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33164
- return result.map(jsonToGeojsonFeature);
33165
- }
33166
- }
33167
-
33168
- function parseCsv(text) {
33169
- // first parse the header to guess the delimiter
33170
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33171
- let delimiter;
33172
- try {
33173
- const header = text.split('\n')[0];
33174
- const result = Papa.parse(header, {
33175
- header: false,
33176
- });
33177
- delimiter = result.meta.delimiter;
33178
- }
33179
- catch (e) {
33180
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33181
- }
33182
- const parsed = Papa.parse(text, {
33183
- header: true,
33184
- skipEmptyLines: true,
33185
- delimiter,
33186
- });
33187
- if (parsed.errors.length) {
33188
- throw new Error('CSV parsing failed for the following reasons:\n' +
33189
- parsed.errors
33190
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33191
- .join('\n'));
33193
+ await this.isLoaded;
33194
+ // if only certain fields are selected, omit the geometry
33195
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33196
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33197
+ return this.engine.queryItems(query);
33192
33198
  }
33193
- const items = parsed.data.map(jsonToGeojsonFeature);
33194
- return processItemProperties(items, true);
33195
33199
  }
33200
+
33196
33201
  class CsvReader extends BaseFileReader {
33197
- getData() {
33198
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33202
+ async getLoadQuery() {
33203
+ // first we get a list of columns hich have a detected type of VARCHAR
33204
+ // then we make sure that for those columns, we don't cast an empty string to null; instead we want to keep the empty string
33205
+ return `
33206
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33207
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33208
+ force_not_null = getvariable('strColumns'),
33209
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33210
+ );`;
33199
33211
  }
33200
33212
  }
33201
33213
 
33202
- /**
33203
- * This parser only supports arrays of simple flat objects with properties
33204
- * @param text
33205
- */
33206
- function parseJson(text) {
33207
- const parsed = JSON.parse(text);
33208
- if (!Array.isArray(parsed)) {
33209
- throw new Error('Could not parse JSON, expected an array at root level');
33210
- }
33211
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33212
- }
33213
33214
  class JsonReader extends BaseFileReader {
33214
- getData() {
33215
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33215
+ async getLoadQuery() {
33216
+ return `
33217
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33218
+ maximum_object_size = 536870912 -- 500MB
33219
+ );`;
33216
33220
  }
33217
33221
  }
33218
33222
 
33219
- /**
33220
- * This parser supports both Geojson Feature collections or arrays
33221
- * of Features
33222
- * @param text
33223
- */
33224
- function parseGeojson(text) {
33225
- const parsed = JSON.parse(text);
33226
- const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33227
- if (!Array.isArray(features)) {
33228
- throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33229
- }
33230
- return processItemProperties(features);
33231
- }
33232
33223
  class GeojsonReader extends BaseFileReader {
33233
- getData() {
33234
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33224
+ async getLoadQuery() {
33225
+ return `
33226
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33227
+ allowed_drivers = ['GeoJSON']
33228
+ );`;
33235
33229
  }
33236
33230
  }
33237
33231
 
33232
+ async function inferDatasetType(url, typeHint) {
33233
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33234
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33235
+ ? fileExtensionMatches[1].toLowerCase()
33236
+ : null;
33237
+ // 1. type hint
33238
+ if (typeHint)
33239
+ return Promise.resolve(typeHint);
33240
+ // 2. content-type header
33241
+ const headers = await fetchHeaders(url);
33242
+ if ('supportedType' in headers)
33243
+ return headers.supportedType;
33244
+ // 3. file extension from url
33245
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33246
+ return fileExtension;
33247
+ // no type inferred or hinted
33248
+ if ('mimeType' in headers)
33249
+ throw FetchError.unsupportedType(headers.mimeType);
33250
+ else
33251
+ throw FetchError.unknownType();
33252
+ }
33253
+ function fetchHeaders(url) {
33254
+ return sharedFetch(url, 'HEAD')
33255
+ .catch((error) => {
33256
+ throw FetchError.corsOrNetwork(error.message);
33257
+ })
33258
+ .then((response) => {
33259
+ if (!response.ok) {
33260
+ throw FetchError.http(response.status);
33261
+ }
33262
+ return parseHeaders(response.headers);
33263
+ });
33264
+ }
33265
+ function fetchDataAsText(url, cacheActive) {
33266
+ const fetchFactory = () => sharedFetch(url)
33267
+ .catch((error) => {
33268
+ throw FetchError.corsOrNetwork(error.message);
33269
+ })
33270
+ .then(async (response) => {
33271
+ if (!response.ok) {
33272
+ const clonedResponse = response.clone();
33273
+ throw FetchError.http(response.status, await clonedResponse.text());
33274
+ }
33275
+ const clonedResponse = response.clone();
33276
+ return clonedResponse.text();
33277
+ });
33278
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33279
+ }
33280
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33281
+ const fetchFactory = () => sharedFetch(url)
33282
+ .catch((error) => {
33283
+ throw FetchError.corsOrNetwork(error.message);
33284
+ })
33285
+ .then(async (response) => {
33286
+ if (!response.ok) {
33287
+ throw FetchError.http(response.status, await response.text());
33288
+ }
33289
+ // convert to a numeric array so that we can store the response in cache
33290
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33291
+ });
33292
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33293
+ return new Uint8Array(array).buffer;
33294
+ });
33295
+ }
33238
33296
  /**
33239
- * This will read the first sheet of the excel workbook and expect the first
33240
- * line to contain the properties names
33241
- * @param buffer
33297
+ * This creates a Proxy that allows reading and writing to the data item properties
33298
+ * as if it was a simple array of JSON objects
33299
+ * @param items
33242
33300
  */
33243
- function parseExcel(buffer) {
33244
- return import('xlsx').then(({ read, utils }) => {
33245
- const workbook = read(buffer);
33246
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33247
- let json = utils.sheet_to_json(sheet);
33248
- if (!json.length) {
33249
- json = [];
33250
- }
33251
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33301
+ function getJsonDataItemsProxy(items) {
33302
+ return new Proxy(items, {
33303
+ get(target, p) {
33304
+ if (typeof p === 'string' &&
33305
+ !Number.isNaN(parseInt(p)) &&
33306
+ target[p]?.properties) {
33307
+ return target[p].properties;
33308
+ }
33309
+ return target[p];
33310
+ },
33311
+ set() {
33312
+ throw new Error('This object is read-only');
33313
+ },
33252
33314
  });
33253
33315
  }
33316
+
33254
33317
  class ExcelReader extends BaseFileReader {
33255
- getData() {
33256
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33318
+ async getLoadQuery() {
33319
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33320
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33321
+ const bufferHandle = `B${this.datasetId}`;
33322
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33323
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33324
+ const isXls = Array.from(magicNumber)
33325
+ .map((n) => n.toString(16).toUpperCase())
33326
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33327
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33328
+ if (isXls) {
33329
+ buffer = await import('xlsx').then(({ read, utils }) => {
33330
+ const workbook = read(buffer);
33331
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33332
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33333
+ });
33334
+ }
33335
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33336
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33337
+ return `
33338
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33339
+ }
33340
+ }
33341
+
33342
+ class GmlReader extends BaseFileReader {
33343
+ async getLoadQuery() {
33344
+ return `
33345
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33346
+ allowed_drivers = ['GML']
33347
+ );`;
33257
33348
  }
33258
33349
  }
33259
33350
 
33260
- function parseGml(text, namespace, version) {
33261
- const splittedNamespace = namespace.split(':');
33262
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33351
+ const formatGeojson = new GeoJSON$1();
33352
+ function parseGeojson(text) {
33353
+ const parsed = JSON.parse(text);
33354
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33355
+ if (!Array.isArray(features)) {
33356
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33357
+ }
33358
+ return features;
33359
+ }
33360
+ function parseGml(text, featureType, version) {
33361
+ const parts = featureType.split(':');
33362
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33263
33363
  const match = regex.exec(text);
33264
33364
  if (match && match.length >= 2) {
33265
- const wf = new WFS({
33365
+ const wfs = new WFS({
33266
33366
  featureNS: match[1],
33267
- featureType: splittedNamespace[1],
33367
+ featureType: parts[1],
33268
33368
  version: version,
33269
33369
  });
33270
33370
  let features;
33271
33371
  try {
33272
- features = wf.readFeatures(text);
33372
+ features = wfs.readFeatures(text);
33273
33373
  }
33274
33374
  catch (e) {
33275
- throw Error("Couldn't parse WFS with GML features");
33375
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33276
33376
  }
33277
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33278
- return processItemProperties(geojsonItem.features, true);
33377
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33378
+ return geojsonItem.features;
33279
33379
  }
33280
33380
  throw Error("Couldn't retrieve namespace url");
33281
33381
  }
33282
- class GmlReader extends BaseFileReader {
33283
- constructor(url, namespace, version, cacheActive = true) {
33284
- super(url);
33285
- this.url = url;
33286
- this.namespace = namespace;
33287
- this.version = version;
33288
- this.cacheActive = cacheActive;
33289
- }
33290
- getData() {
33291
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33292
- }
33293
- }
33294
-
33295
33382
  async function getWfsEndpoint(wfsUrl) {
33296
33383
  try {
33297
33384
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33319,21 +33406,65 @@ async function getWfsEndpoint(wfsUrl) {
33319
33406
  }
33320
33407
  }
33321
33408
  }
33322
- class WfsReader extends BaseCacheReader {
33323
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33324
- super(url, cacheActive);
33325
- this.endpoint = wfsEndpoint;
33326
- this.featureTypeName = featureTypeName;
33327
- this.version = this.endpoint.getVersion();
33409
+ class WfsReader extends BaseReader {
33410
+ constructor(url, featureTypeName) {
33411
+ super(url);
33412
+ this.endpoint = getWfsEndpoint(url);
33413
+ this.featureType = this.endpoint
33414
+ .then((endpoint) => {
33415
+ const featureTypes = endpoint.getFeatureTypes();
33416
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33417
+ ? featureTypes[0].name
33418
+ : featureTypeName);
33419
+ })
33420
+ .then((featureType) => {
33421
+ if (!featureType) {
33422
+ throw new Error('wfs.featuretype.notfound');
33423
+ }
33424
+ return featureType;
33425
+ });
33426
+ }
33427
+ get backupReader() {
33428
+ if (this.backupReader_) {
33429
+ return this.backupReader_;
33430
+ }
33431
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33432
+ let reader;
33433
+ if (endpoint.supportsJson(featureType.name)) {
33434
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33435
+ asJson: true,
33436
+ outputCrs: 'EPSG:4326',
33437
+ }));
33438
+ }
33439
+ else {
33440
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33441
+ (featureType.defaultCrs === 'EPSG:4326' ||
33442
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33443
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33444
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33445
+ outputCrs: 'EPSG:4326',
33446
+ }));
33447
+ }
33448
+ }
33449
+ reader.enableCache(this.cacheEnabled);
33450
+ reader.load();
33451
+ return reader;
33452
+ });
33453
+ return this.backupReader_;
33328
33454
  }
33329
33455
  get properties() {
33330
- return this.endpoint
33331
- .getFeatureTypeFull(this.featureTypeName)
33332
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33456
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33333
33457
  const originalType = featureType.properties[prop];
33334
- const type = originalType === 'float' || originalType === 'integer'
33335
- ? 'number'
33336
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33458
+ let type;
33459
+ if (originalType === 'float' || originalType === 'integer') {
33460
+ type = 'number';
33461
+ }
33462
+ else if (originalType === 'boolean') {
33463
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33464
+ }
33465
+ else {
33466
+ type = originalType;
33467
+ }
33337
33468
  return {
33338
33469
  name: prop,
33339
33470
  label: prop,
@@ -33342,83 +33473,63 @@ class WfsReader extends BaseCacheReader {
33342
33473
  }));
33343
33474
  }
33344
33475
  get info() {
33345
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33476
+ return this.featureType.then((result) => ({
33346
33477
  itemsCount: result.objectCount,
33478
+ hasGeometry: !!result.geometryName,
33347
33479
  }));
33348
33480
  }
33349
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33350
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33351
- const featureTypes = wfsEndpoint.getFeatureTypes();
33352
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33353
- ? featureTypes[0].name
33354
- : featureTypeName);
33355
- if (!featureType) {
33356
- throw new Error('wfs.featuretype.notfound');
33357
- }
33358
- if (wfsEndpoint.supportsStartIndex()) {
33359
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33360
- }
33361
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33362
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33363
- asJson: true,
33364
- outputCrs: 'EPSG:4326',
33365
- }));
33366
- }
33367
- else {
33368
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33369
- (featureType.defaultCrs === 'EPSG:4326' ||
33370
- featureType.otherCrs?.includes('EPSG:4326'))) {
33371
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33372
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33373
- outputCrs: 'EPSG:4326',
33374
- }), featureType.name, wfsEndpoint.getVersion());
33375
- }
33376
- throw new Error('wfs.geojsongml.notsupported');
33377
- }
33481
+ load() {
33482
+ // Nothing to load for Wfs
33378
33483
  }
33379
- async getData(aggregation, groupedBy) {
33380
- if (aggregation || groupedBy) {
33381
- return { items: await this.getQueryData() };
33484
+ async read() {
33485
+ const endpoint = await this.endpoint;
33486
+ const featureType = await this.featureType;
33487
+ // if we can't use the WFS protocol we fall back to the backup reader
33488
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33489
+ const backupReader = await this.backupReader;
33490
+ backupReader.selectAll();
33491
+ if (this.selected) {
33492
+ backupReader.select(...this.selected);
33493
+ }
33494
+ if (this.aggregations) {
33495
+ backupReader.aggregate(...this.aggregations);
33496
+ }
33497
+ if (this.groupedBy) {
33498
+ backupReader.groupBy(...this.groupedBy);
33499
+ }
33500
+ if (this.sort) {
33501
+ backupReader.orderBy(...this.sort);
33502
+ }
33503
+ if (this.startIndex !== null && this.count !== null) {
33504
+ backupReader.limit(this.startIndex, this.count);
33505
+ }
33506
+ return backupReader.read();
33382
33507
  }
33383
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33508
+ const asJson = endpoint.supportsJson(featureType.name);
33384
33509
  const attributes = this.selected ?? undefined;
33385
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33510
+ let sortBy = null;
33511
+ if (this.sort) {
33512
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33513
+ sortBy = Array.isArray(this.sort[0])
33514
+ ? this.sort.map(mapSort)
33515
+ : mapSort(this.sort);
33516
+ }
33517
+ const url = endpoint.getFeatureUrl(featureType.name, {
33386
33518
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33387
33519
  ...(this.count !== null && { maxFeatures: this.count }),
33388
33520
  asJson,
33389
33521
  outputCrs: 'EPSG:4326',
33390
33522
  attributes,
33391
- // sortBy: this.sort // TODO: no sort in ogc-client?
33523
+ sortBy,
33392
33524
  });
33393
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33394
- const finalUrl = new URL(url);
33395
- const sorts = this.sort
33396
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33397
- .join(',');
33398
- // Direct update on string url to prevent encoding of +A and +D
33399
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33400
- }
33401
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33525
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33402
33526
  ? parseGeojson(text)
33403
- : parseGml(text, this.featureTypeName, this.version));
33404
- }
33405
- async getQueryData() {
33406
- const items = (await this.getData()).items;
33407
- const jsonItems = getJsonDataItemsProxy(items);
33408
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33409
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33410
- return result.map(jsonToGeojsonFeature);
33411
- }
33412
- load() {
33413
- // Nothing to load for Wfs
33414
- }
33415
- async read() {
33416
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33527
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33417
33528
  }
33418
33529
  }
33419
33530
 
33420
- async function openDataset(url, typeHint, options, cacheActive) {
33421
- const fileType = await inferDatasetType(url, typeHint);
33531
+ async function openDataset(url, options) {
33532
+ const fileType = await inferDatasetType(url, options?.typeHint);
33422
33533
  let reader;
33423
33534
  try {
33424
33535
  switch (fileType) {
@@ -33435,18 +33546,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33435
33546
  reader = new ExcelReader(url);
33436
33547
  break;
33437
33548
  case 'gml':
33438
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33549
+ reader = new GmlReader(url);
33439
33550
  break;
33440
33551
  case 'wfs':
33441
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33552
+ reader = new WfsReader(url, options?.wfsFeatureType);
33442
33553
  break;
33443
33554
  }
33444
- reader.setCacheActive(cacheActive);
33555
+ reader.enableCache(options?.enableCache ?? true);
33445
33556
  reader.load();
33446
33557
  return reader;
33447
33558
  }
33448
33559
  catch (e) {
33449
- //WfsReader may already raise a FetchError
33560
+ // WfsReader may already raise a FetchError
33450
33561
  if (e instanceof FetchError)
33451
33562
  throw e;
33452
33563
  else
@@ -33457,13 +33568,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33457
33568
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33458
33569
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33459
33570
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33460
- * File type is determined liked so:
33571
+ * File type is determined like so:
33461
33572
  * 1. if a type hint is given, use it
33462
33573
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33463
33574
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33464
33575
  */
33465
- async function readDataset(url, typeHint, options, cacheActive = true) {
33466
- const reader = await openDataset(url, typeHint, options, cacheActive);
33576
+ async function readDataset(url, options) {
33577
+ const reader = await openDataset(url, options);
33467
33578
  try {
33468
33579
  return await reader.read();
33469
33580
  }
@@ -33694,9 +33805,11 @@ class DataService {
33694
33805
  getDataset(link, cacheActive) {
33695
33806
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33696
33807
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33697
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
33808
+ return from(openDataset(wfsUrlEndpoint, {
33809
+ typeHint: 'wfs',
33698
33810
  wfsFeatureType: link.name,
33699
- }, cacheActive));
33811
+ enableCache: cacheActive,
33812
+ }));
33700
33813
  }
33701
33814
  else if (link.type === 'download') {
33702
33815
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33704,12 +33817,15 @@ class DataService {
33704
33817
  const supportedType = SupportedTypes.indexOf(format) > -1
33705
33818
  ? format
33706
33819
  : undefined;
33707
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
33820
+ return from(openDataset(linkProxifiedUrl, {
33821
+ typeHint: supportedType,
33822
+ enableCache: cacheActive,
33823
+ })).pipe();
33708
33824
  }
33709
33825
  else if (link.type === 'service' &&
33710
33826
  link.accessServiceProtocol === 'esriRest') {
33711
33827
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33712
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
33828
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33713
33829
  }
33714
33830
  else if (link.type === 'service' &&
33715
33831
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33723,7 +33839,10 @@ class DataService {
33723
33839
  }
33724
33840
  const urlWithoutLimit = new URL(geojsonUrl);
33725
33841
  urlWithoutLimit.searchParams.delete('limit');
33726
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
33842
+ return openDataset(urlWithoutLimit.toString(), {
33843
+ typeHint: 'geojson',
33844
+ enableCache: cacheActive,
33845
+ });
33727
33846
  }));
33728
33847
  }
33729
33848
  return throwError(() => 'protocol not supported');
@@ -33975,20 +34094,31 @@ class DataTableComponent {
33975
34094
  this.eltRef = inject(ElementRef);
33976
34095
  this.cdr = inject(ChangeDetectorRef);
33977
34096
  this.translateService = inject(TranslateService);
33978
- this._featureAttributes = [];
34097
+ this.columnsFromFeatureCatalog = null;
34098
+ this.columnsFromDataset = [];
33979
34099
  this.selected = new EventEmitter();
33980
- this.properties$ = new BehaviorSubject(null);
33981
34100
  this.loading$ = new BehaviorSubject(false);
33982
34101
  this.error = null;
33983
34102
  }
33984
34103
  set featureAttributes(value) {
33985
- this._featureAttributes = value;
33986
- this.properties$.next(value.map((attr) => attr.value));
34104
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34105
+ name: attrs.value,
34106
+ label: attrs.label,
34107
+ }));
33987
34108
  }
33988
34109
  set dataset(value) {
33989
34110
  this.dataset_ = value;
33990
34111
  this.dataset_.load();
33991
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34112
+ this.dataset_.info.then((info) => {
34113
+ this.count = info.itemsCount;
34114
+ this.cdr.detectChanges();
34115
+ });
34116
+ }
34117
+ get columns() {
34118
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34119
+ }
34120
+ get columnNames() {
34121
+ return this.columns.map((c) => c.name);
33992
34122
  }
33993
34123
  ngOnInit() {
33994
34124
  this.dataSource = new DataTableDataSource();
@@ -34023,11 +34153,12 @@ class DataTableComponent {
34023
34153
  }
34024
34154
  async readData() {
34025
34155
  this.loading$.next(true);
34026
- // wait for properties to be read
34027
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34028
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34029
- this.dataset_.select(...propsWithoutGeom);
34030
34156
  try {
34157
+ // wait for properties to be read
34158
+ if (!this.columnsFromFeatureCatalog) {
34159
+ this.columnsFromDataset = await this.dataset_.properties;
34160
+ }
34161
+ this.dataset_.select(...this.columnNames);
34031
34162
  await this.dataSource.showData(this.dataset_.read());
34032
34163
  this.error = null;
34033
34164
  }
@@ -34057,7 +34188,7 @@ class DataTableComponent {
34057
34188
  }
34058
34189
  }
34059
34190
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34060
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34191
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34061
34192
  }
34062
34193
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34063
34194
  type: Component,
@@ -34069,10 +34200,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34069
34200
  CommonModule,
34070
34201
  LoadingMaskComponent,
34071
34202
  PopupAlertComponent,
34072
- LetDirective,
34073
34203
  TranslatePipe,
34074
34204
  TranslateDirective,
34075
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34205
+ ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34076
34206
  }], propDecorators: { featureAttributes: [{
34077
34207
  type: Input
34078
34208
  }], dataset: [{
@@ -34175,7 +34305,7 @@ class ChartViewComponent {
34175
34305
  }), shareReplay$1(1));
34176
34306
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34177
34307
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34178
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34308
+ .filter((prop) => prop.type === 'number')
34179
34309
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34180
34310
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34181
34311
  const newProp = choices[0]?.value || '';
@@ -34202,6 +34332,7 @@ class ChartViewComponent {
34202
34332
  this.aggregation$,
34203
34333
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34204
34334
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34335
+ this.loading = true;
34205
34336
  return dataset
34206
34337
  .groupBy(['distinct', xProp])
34207
34338
  .aggregate(fieldAgg)
@@ -34443,6 +34574,7 @@ class GeoTableViewComponent {
34443
34574
  }));
34444
34575
  }
34445
34576
  async initMapContext() {
34577
+ this.dataset.load();
34446
34578
  this.dataset.selectAll();
34447
34579
  return {
34448
34580
  layers: [
@@ -34616,8 +34748,7 @@ class MdViewFacade {
34616
34748
  }));
34617
34749
  this.error$ = this.store.pipe(select(getMetadataError));
34618
34750
  this.related$ = this.store.pipe(select(getRelated));
34619
- this.sources$ = this.store.pipe(select(getSources));
34620
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
34751
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34621
34752
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34622
34753
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34623
34754
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34736,12 +34867,9 @@ class MdViewEffects {
34736
34867
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34737
34868
  return setRelated({ related });
34738
34869
  }), catchError(() => of(setRelated({ related: null })))));
34739
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34740
- return setSources({ sources });
34741
- }), catchError(() => of(setSources({ sources: null })))));
34742
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34743
- return setSourceOf({ sourceOf });
34744
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
34870
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
34871
+ return setLinkedRecords({ linkedRecords });
34872
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34745
34873
  /*
34746
34874
  UserFeedback effects
34747
34875
  */
@@ -40377,5 +40505,5 @@ const CHART_TYPE_VALUES = [
40377
40505
  * Generated bundle index. Do not edit.
40378
40506
  */
40379
40507
 
40380
- export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40508
+ export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_BASEMAP_LAYER, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40381
40509
  //# sourceMappingURL=geonetwork-ui.mjs.map