geonetwork-ui 2.11.0-dev.96d0c6ce2 → 2.11.0-dev.9ba9c4d3d

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 (81) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1243 -687
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +227 -81
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +70 -10
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +13 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  18. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  19. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  20. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  21. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  22. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  23. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  24. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  25. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  26. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  27. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  28. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  29. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  30. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  31. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  32. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  33. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  34. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  35. package/src/libs/ui/inputs/src/index.ts +2 -0
  36. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  37. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  38. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  39. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  40. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  41. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  42. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  43. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  44. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  45. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  46. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  47. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  48. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  49. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  50. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  51. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  52. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  53. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  54. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  55. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  56. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  57. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  58. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  59. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  60. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  61. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  62. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  63. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  64. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  65. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  66. package/src/libs/util/shared/src/index.ts +1 -0
  67. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  68. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  69. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  70. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  71. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  72. package/tailwind.base.css +5 -0
  73. package/translations/de.json +14 -3
  74. package/translations/en.json +13 -2
  75. package/translations/es.json +11 -0
  76. package/translations/fr.json +14 -3
  77. package/translations/it.json +12 -1
  78. package/translations/nl.json +11 -0
  79. package/translations/pt.json +11 -0
  80. package/translations/sk.json +12 -1
  81. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -6,9 +6,9 @@ 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
- 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';
11
+ import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, afterNextRender, booleanAttribute, Input, Directive, HostListener, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
13
13
  import * as i4 from '@ngx-translate/core';
14
14
  import { TranslateLoader, TranslateCompiler, TranslateDefaultParser, TranslateParser, TranslateService, provideTranslateService, TranslateDirective, TranslatePipe, TranslateModule } from '@ngx-translate/core';
@@ -18,7 +18,7 @@ import { map as map$1, shareReplay as shareReplay$1, catchError, tap as tap$1, f
18
18
  import { lt, valid, coerce, satisfies, ltr } from 'semver';
19
19
  import chroma from 'chroma-js';
20
20
  import * as i1$1 from '@angular/common';
21
- import { Location, CommonModule, NgClass, NgTemplateOutlet, DatePipe, AsyncPipe } from '@angular/common';
21
+ import { Location, CommonModule, NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common';
22
22
  import { formatDistance } from 'date-fns/formatDistance';
23
23
  import { Scroll, NavigationEnd, Router, RouteReuseStrategy } from '@angular/router';
24
24
  import { WmtsEndpoint, WmsEndpoint, WfsEndpoint, OgcApiEndpoint, sharedFetch, useCache, StacEndpoint, TmsEndpoint } from '@camptocamp/ogc-client';
@@ -41,10 +41,10 @@ import * as TOML from '@ltd/j-toml';
41
41
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
42
  import CircleStyle from 'ol/style/Circle.js';
43
43
  import EmblaCarousel from 'embla-carousel';
44
- import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirImport, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirCheckCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
44
+ import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirTrash, iconoirSquareDashed, iconoirImport, iconoirCheckCircle, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
45
45
  import { MatButtonModule } from '@angular/material/button';
46
- import * as i1$8 from '@angular/cdk/overlay';
47
- import { OverlayContainer, ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
46
+ import * as i1$7 from '@angular/cdk/overlay';
47
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
48
  import * as i1$3 from '@angular/forms';
49
49
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
50
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +52,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
52
  import tippy from 'tippy.js';
53
53
  import * as i2 from '@angular/material/progress-spinner';
54
54
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
55
- import { matClose, matContentCopy, matExpandLess, matExpandMore, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
55
+ import { matClose, matContentCopy, matExpandMore, matExpandLess, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
56
56
  import * as i1$4 from '@angular/material/checkbox';
57
57
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
58
  import * as i1$5 from '@angular/material/tooltip';
59
59
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
60
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
61
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
62
+ import { enUS } from 'date-fns/locale/en-US';
61
63
  import * as i1$6 from '@angular/material/datepicker';
62
64
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
65
+ import * as i1$8 from 'ngx-dropzone';
66
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
67
  import * as i1$9 from '@angular/material/button-toggle';
66
68
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
69
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -79,10 +81,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
81
  import Duration from 'duration-relativetimeformat';
80
82
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
83
  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';
84
+ import * as duckdb from '@duckdb/duckdb-wasm';
85
+ import { Vector, DataType } from 'apache-arrow';
86
+ import WFS from 'ol/format/WFS';
87
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
88
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
89
  import * as i2$4 from '@angular/material/sort';
88
90
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +92,6 @@ import * as i1$b from '@angular/material/table';
90
92
  import { MatTableModule } from '@angular/material/table';
91
93
  import * as i3 from '@angular/material/paginator';
92
94
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
95
  import { Meta } from '@angular/platform-browser';
95
96
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
97
  import * as i3$1 from '@angular/material/radio';
@@ -18907,14 +18908,29 @@ class Gn4FieldMapper {
18907
18908
  featureTypes: selectField(source, 'featureTypes'),
18908
18909
  }, output),
18909
18910
  related: (output, source) => {
18910
- const fcatSource = selectField(getFirstValue(selectField(selectField(source, 'related'), 'fcats')) ?? {}, '_source');
18911
+ const related = selectField(source, 'related');
18912
+ const fcatSource = selectField(getFirstValue(selectField(related, 'fcats')) ?? {}, '_source');
18911
18913
  const featureCatalogIdentifier = selectField(fcatSource, 'uuid');
18912
- const sourceOfLinks = getAsArray(selectField(selectField(source, 'related'), 'hassources'));
18914
+ const sourceOfLinks = getAsArray(selectField(related, 'hassources'));
18913
18915
  const sourceOfIdentifiers = sourceOfLinks
18914
18916
  .filter((link) => link['origin'] === 'catalog')
18915
18917
  .map((link) => {
18916
18918
  return selectField(selectField(link, '_source'), 'uuid');
18917
18919
  });
18920
+ const siblingLinks = getAsArray(selectField(related, 'siblings'));
18921
+ const siblings = siblingLinks
18922
+ .filter((link) => link['origin'] === 'catalog')
18923
+ .map((link) => ({
18924
+ uniqueIdentifier: selectField(selectField(link, '_source'), 'uuid'),
18925
+ associationType: getAssociationTypeFromCode(selectField(selectField(link, 'properties'), 'associationType')),
18926
+ }));
18927
+ const associatedLinks = getAsArray(selectField(related, 'associated'));
18928
+ const associatedIdentifiers = associatedLinks
18929
+ .filter((link) => link['origin'] === 'catalog')
18930
+ .map((link) => {
18931
+ return selectField(selectField(link, '_source'), 'uuid');
18932
+ })
18933
+ .filter((uuid) => !siblings.some((sibling) => sibling.uniqueIdentifier === uuid));
18918
18934
  const extraValues = {};
18919
18935
  if (featureCatalogIdentifier) {
18920
18936
  extraValues.featureCatalogIdentifier = featureCatalogIdentifier;
@@ -18922,6 +18938,12 @@ class Gn4FieldMapper {
18922
18938
  if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
18923
18939
  extraValues.sourceOfIdentifiers = sourceOfIdentifiers;
18924
18940
  }
18941
+ if (associatedIdentifiers && associatedIdentifiers.length > 0) {
18942
+ extraValues.associatedIdentifiers = associatedIdentifiers;
18943
+ }
18944
+ if (siblings && siblings.length > 0) {
18945
+ extraValues.siblings = siblings;
18946
+ }
18925
18947
  return Object.keys(extraValues).length > 0
18926
18948
  ? this.addExtra(extraValues, output)
18927
18949
  : output;
@@ -19447,10 +19469,10 @@ class DateService {
19447
19469
  const dateObj = this.getDateObject(date);
19448
19470
  return { locale, dateObj };
19449
19471
  }
19450
- async getDateLocale() {
19472
+ async getDateFnsLocale() {
19451
19473
  const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE;
19452
19474
  const locales = await this.dateLocales;
19453
- return locales[lang];
19475
+ return locales[lang] ?? locales[DEFAULT_LANGUAGE];
19454
19476
  }
19455
19477
  formatDate(date, options) {
19456
19478
  const { locale, dateObj } = this.getLocaleAndDate(date);
@@ -19463,7 +19485,7 @@ class DateService {
19463
19485
  async formatRelativeDateTime(date) {
19464
19486
  const dateObj = this.getDateObject(date);
19465
19487
  const now = new Date();
19466
- const locale = await this.getDateLocale();
19488
+ const locale = await this.getDateFnsLocale();
19467
19489
  return formatDistance(dateObj, now, {
19468
19490
  addSuffix: true,
19469
19491
  locale: locale,
@@ -19498,6 +19520,18 @@ function propagateToDocumentOnly(event) {
19498
19520
  }, 0);
19499
19521
  }
19500
19522
 
19523
+ function isFileExtensionValid(fileName, acceptedExtensions) {
19524
+ return acceptedExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
19525
+ }
19526
+ function readFileAsText(file) {
19527
+ return new Promise((resolve, reject) => {
19528
+ const reader = new FileReader();
19529
+ reader.onload = () => resolve(reader.result);
19530
+ reader.onerror = () => reject(reader.error);
19531
+ reader.readAsText(file);
19532
+ });
19533
+ }
19534
+
19501
19535
  function formatUserInfo(userInfo, displayCount = false) {
19502
19536
  const infos = (typeof userInfo === 'string' ? userInfo : '').split('|');
19503
19537
  const count = displayCount ? ` ${infos[3].split(' ')[1]}` : '';
@@ -19557,6 +19591,11 @@ function getGeometryFromGeoJSON(data) {
19557
19591
  }
19558
19592
  return null;
19559
19593
  }
19594
+ function isBoundingBox(value) {
19595
+ return (Array.isArray(value) &&
19596
+ value.length === 4 &&
19597
+ value.every((item) => typeof item === 'number'));
19598
+ }
19560
19599
  function getGeometryBoundingBox(geometry) {
19561
19600
  // use the bounding box if specified in the GeoJSON object
19562
19601
  if (geometry.bbox) {
@@ -20342,6 +20381,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20342
20381
  }]
20343
20382
  }] });
20344
20383
 
20384
+ class AutofocusDirective {
20385
+ constructor() {
20386
+ this.el = inject(ElementRef);
20387
+ this.injector = inject(Injector);
20388
+ }
20389
+ set autofocus(active) {
20390
+ if (!active)
20391
+ return;
20392
+ afterNextRender(() => this.el.nativeElement.focus(), {
20393
+ injector: this.injector,
20394
+ });
20395
+ }
20396
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20397
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.3.19", type: AutofocusDirective, isStandalone: true, selector: "[gnUiAutofocus]", inputs: { autofocus: ["gnUiAutofocus", "autofocus", booleanAttribute] }, ngImport: i0 }); }
20398
+ }
20399
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20400
+ type: Directive,
20401
+ args: [{
20402
+ selector: '[gnUiAutofocus]',
20403
+ standalone: true,
20404
+ }]
20405
+ }], propDecorators: { autofocus: [{
20406
+ type: Input,
20407
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20408
+ }] } });
20409
+
20345
20410
  class ImageFallbackDirective {
20346
20411
  constructor() {
20347
20412
  this.el = inject(ElementRef);
@@ -20369,7 +20434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20369
20434
  }] } });
20370
20435
 
20371
20436
  var name = "geonetwork-ui";
20372
- var version = "2.11.0-dev.96d0c6ce2";
20437
+ var version = "2.11.0-dev.9ba9c4d3d";
20373
20438
  var engines = {
20374
20439
  node: ">=24"
20375
20440
  };
@@ -20390,6 +20455,7 @@ var peerDependencies = {
20390
20455
  "@angular/core": "19.x || 20.x || 21.x",
20391
20456
  "@angular/forms": "19.x || 20.x || 21.x",
20392
20457
  "@angular/material": "19.x || 20.x || 21.x",
20458
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20393
20459
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20394
20460
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20395
20461
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20399,20 +20465,21 @@ var peerDependencies = {
20399
20465
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20466
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20467
  "@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",
20468
+ "@ngx-translate/core": "17.x",
20469
+ "@ngx-translate/http-loader": "17.x",
20470
+ "flag-icons": "~7.5.0",
20405
20471
  rxjs: "7.x",
20406
20472
  "zone.js": "*",
20407
20473
  tailwindcss: "3.x"
20408
20474
  };
20409
20475
  var dependencies = {
20410
20476
  "@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",
20477
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20478
+ "@duckdb/duckdb-wasm": "~1.32.0",
20479
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20480
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20481
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20482
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20483
  "@ltd/j-toml": "~1.38.0",
20417
20484
  "@messageformat/core": "~3.4.0",
20418
20485
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20487,7 @@ var dependencies = {
20420
20487
  "@ng-icons/material-icons": "~32.5.0",
20421
20488
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20489
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20490
+ "apache-arrow": "~17.0.0",
20424
20491
  "chart.js": "~4.5.1",
20425
20492
  "chroma-js": "~3.2.0",
20426
20493
  "date-fns": "4.1.0",
@@ -20433,7 +20500,6 @@ var dependencies = {
20433
20500
  "ngx-dropzone": "~3.1.0",
20434
20501
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20502
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20503
  proj4: "~2.20.4",
20438
20504
  rdflib: "~2.3.5",
20439
20505
  semver: "~7.7.4",
@@ -20678,6 +20744,7 @@ class ElasticsearchService {
20678
20744
  values: uuids,
20679
20745
  },
20680
20746
  },
20747
+ size: uuids.length,
20681
20748
  };
20682
20749
  }
20683
20750
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20728,10 +20795,28 @@ class ElasticsearchService {
20728
20795
  };
20729
20796
  }
20730
20797
  buildPayloadSort(sortBy) {
20731
- if (sortBy === null)
20798
+ if (!sortBy || sortBy.length === 0)
20732
20799
  return undefined;
20733
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy];
20734
- return fields.map((field) => ({ [field[1]]: field[0] }));
20800
+ const fields = Array.isArray(sortBy[0])
20801
+ ? sortBy
20802
+ : [sortBy];
20803
+ return fields.map((field) => {
20804
+ // Sort by nested array of dates only works with the explicit syntax
20805
+ if (field[1].endsWith('.date')) {
20806
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'));
20807
+ return {
20808
+ [field[1]]: {
20809
+ order: field[0],
20810
+ mode: field[0] === 'desc' ? 'max' : 'min',
20811
+ missing: '_last',
20812
+ nested: {
20813
+ path: nestedPath,
20814
+ },
20815
+ },
20816
+ };
20817
+ }
20818
+ return { [field[1]]: field[0] };
20819
+ });
20735
20820
  }
20736
20821
  injectLangInQueryStringFields(queryFieldsPriority) {
20737
20822
  const queryLang = this.getQueryLang();
@@ -20767,7 +20852,13 @@ class ElasticsearchService {
20767
20852
  isCurrentSearchLang() {
20768
20853
  return this.metadataLang === 'current';
20769
20854
  }
20770
- filtersToQuery(filters) {
20855
+ findSpatialFilterExtent(filters) {
20856
+ if (typeof filters === 'string') {
20857
+ return undefined;
20858
+ }
20859
+ return Object.values(filters).find(isBoundingBox);
20860
+ }
20861
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20771
20862
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20772
20863
  const makeQuery = (filter) => {
20773
20864
  if (typeof filter === 'string') {
@@ -20786,7 +20877,9 @@ class ElasticsearchService {
20786
20877
  ? filters
20787
20878
  : Object.keys(filters)
20788
20879
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20880
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20789
20881
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20882
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20790
20883
  .filter((fieldname) => filters[fieldname] &&
20791
20884
  JSON.stringify(filters[fieldname]) !== '{}')
20792
20885
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20822,6 +20915,21 @@ class ElasticsearchService {
20822
20915
  },
20823
20916
  },
20824
20917
  },
20918
+ spatialFilterExtent && {
20919
+ geo_shape: {
20920
+ geom: {
20921
+ shape: {
20922
+ type: 'envelope',
20923
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20924
+ coordinates: [
20925
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20926
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20927
+ ],
20928
+ },
20929
+ relation: 'intersects',
20930
+ },
20931
+ },
20932
+ },
20825
20933
  ].filter(Boolean);
20826
20934
  return queryParts.length > 0 ? queryParts : undefined;
20827
20935
  }
@@ -20848,7 +20956,9 @@ class ElasticsearchService {
20848
20956
  },
20849
20957
  });
20850
20958
  }
20851
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20959
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20960
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20961
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20852
20962
  if (queryFilters) {
20853
20963
  filter.push(...queryFilters);
20854
20964
  }
@@ -20859,7 +20969,10 @@ class ElasticsearchService {
20859
20969
  },
20860
20970
  });
20861
20971
  }
20862
- if (geometry) {
20972
+ const boostGeometry = spatialFilterExtent
20973
+ ? bboxToPolygon(spatialFilterExtent)
20974
+ : geometry;
20975
+ if (boostGeometry) {
20863
20976
  // boosts applied using the filter geometry:
20864
20977
  // * records completely within the geometry receive a boost of 5
20865
20978
  // * records intersecting the geometry receive a boost of 2
@@ -20868,7 +20981,7 @@ class ElasticsearchService {
20868
20981
  should.push({
20869
20982
  geo_shape: {
20870
20983
  geom: {
20871
- shape: geometry,
20984
+ shape: boostGeometry,
20872
20985
  relation: 'within',
20873
20986
  },
20874
20987
  boost: 5.0,
@@ -20876,7 +20989,7 @@ class ElasticsearchService {
20876
20989
  }, {
20877
20990
  geo_shape: {
20878
20991
  geom: {
20879
- shape: geometry,
20992
+ shape: boostGeometry,
20880
20993
  relation: 'intersects',
20881
20994
  },
20882
20995
  boost: 2.0,
@@ -20885,7 +20998,7 @@ class ElasticsearchService {
20885
20998
  // this will boost the results variably depending on their distance from the given geometry
20886
20999
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20887
21000
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20888
- const bbox = getGeometryBoundingBox(geometry);
21001
+ const bbox = getGeometryBoundingBox(boostGeometry);
20889
21002
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20890
21003
  const northToCenter = new LineString([
20891
21004
  [center[0], bbox[3]],
@@ -21211,7 +21324,7 @@ class Gn4Repository {
21211
21324
  }
21212
21325
  getRecord(uniqueIdentifier) {
21213
21326
  return this.gn4SearchApi
21214
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21327
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21215
21328
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21216
21329
  }
21217
21330
  getMultipleRecords(uniqueIdentifiers) {
@@ -21265,19 +21378,28 @@ class Gn4Repository {
21265
21378
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21266
21379
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21267
21380
  }
21268
- getSources(record) {
21269
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21270
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21271
- return this.getMultipleRecords(sourcesIdentifiers);
21272
- }
21273
- return of(null);
21274
- }
21275
- getSourceOf(record) {
21276
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21277
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21278
- return this.getMultipleRecords(sourceOfIdentifiers);
21381
+ getLinkedRecords(record) {
21382
+ const siblings = (record.extras?.['siblings'] ?? []);
21383
+ const relations = [
21384
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21385
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21386
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21387
+ [
21388
+ 'associated',
21389
+ (record.extras?.['associatedIdentifiers'] ?? []),
21390
+ ],
21391
+ ];
21392
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21393
+ if (requested.length === 0) {
21394
+ return of([]);
21279
21395
  }
21280
- return of(null);
21396
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21397
+ record,
21398
+ relation,
21399
+ associationType: relation === 'sibling'
21400
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21401
+ : undefined,
21402
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21281
21403
  }
21282
21404
  aggregate(params) {
21283
21405
  // if aggregations are empty, return an empty object right away
@@ -22538,9 +22660,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22538
22660
  });
22539
22661
 
22540
22662
  const DEFAULT_BASEMAP_LAYER = {
22541
- type: 'xyz',
22542
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22543
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22663
+ type: 'maplibre-style',
22664
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22665
+ clickable: false,
22666
+ hoverable: false,
22544
22667
  };
22545
22668
  const DEFAULT_VIEW = {
22546
22669
  center: [0, 15],
@@ -23255,6 +23378,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23255
23378
  'search_preset',
23256
23379
  'advanced_filters',
23257
23380
  'limit',
23381
+ 'spatial_extent_max_file_size',
23258
23382
  ], warnings, errors);
23259
23383
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23260
23384
  searchConfig =
@@ -23272,6 +23396,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23272
23396
  })),
23273
23397
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23274
23398
  LIMIT: parsedSearchSection.limit,
23399
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23275
23400
  };
23276
23401
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23277
23402
  metadataQualityConfig =
@@ -24529,12 +24654,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24529
24654
  type: Input
24530
24655
  }] } });
24531
24656
 
24657
+ function provideLocalizedDateAdapter() {
24658
+ return [
24659
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24660
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24661
+ {
24662
+ provide: DateAdapter,
24663
+ useFactory: () => {
24664
+ const dateService = inject(DateService);
24665
+ const adapter = new DateFnsAdapter();
24666
+ dateService
24667
+ .getDateFnsLocale()
24668
+ .then((locale) => adapter.setLocale(locale));
24669
+ return adapter;
24670
+ },
24671
+ },
24672
+ ];
24673
+ }
24674
+
24532
24675
  class DatePickerComponent {
24533
24676
  constructor() {
24534
- this.dateAdapter = inject(DateAdapter);
24535
- this.translate = inject(TranslateService);
24536
24677
  this.dateChange = new EventEmitter();
24537
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24538
24678
  }
24539
24679
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24540
24680
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DatePickerComponent, isStandalone: true, selector: "gn-ui-date-picker", inputs: { date: "date" }, outputs: { dateChange: "dateChange" }, providers: [
@@ -24542,30 +24682,19 @@ class DatePickerComponent {
24542
24682
  provideNgIconsConfig({
24543
24683
  size: '1.5rem',
24544
24684
  }),
24545
- {
24546
- provide: MAT_DATE_LOCALE,
24547
- useFactory: (locale) => locale,
24548
- },
24549
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24685
+ provideLocalizedDateAdapter(),
24686
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24550
24687
  }
24551
24688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24552
24689
  type: Component,
24553
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24554
- MatNativeDateModule,
24555
- MatDatepickerModule,
24556
- ButtonComponent,
24557
- NgIconComponent,
24558
- ], providers: [
24690
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24559
24691
  provideIcons({ iconoirCalendar }),
24560
24692
  provideNgIconsConfig({
24561
24693
  size: '1.5rem',
24562
24694
  }),
24563
- {
24564
- provide: MAT_DATE_LOCALE,
24565
- useFactory: (locale) => locale,
24566
- },
24695
+ provideLocalizedDateAdapter(),
24567
24696
  ], template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24568
- }], ctorParameters: () => [], propDecorators: { date: [{
24697
+ }], propDecorators: { date: [{
24569
24698
  type: Input
24570
24699
  }], dateChange: [{
24571
24700
  type: Output
@@ -24573,58 +24702,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24573
24702
 
24574
24703
  class DateRangeDropdownComponent {
24575
24704
  constructor() {
24576
- this.overlayContainer = inject(OverlayContainer);
24705
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24706
+ this.dateAdapter = inject(DateAdapter);
24707
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24577
24708
  this.cdr = inject(ChangeDetectorRef);
24578
- this.startDateChange = new EventEmitter();
24579
- this.endDateChange = new EventEmitter();
24580
- this.isPickerDisplayed = false;
24709
+ this.dateRange = {};
24710
+ this.dateRangeChange = new EventEmitter();
24711
+ this.overlayPositions = [
24712
+ {
24713
+ originX: 'start',
24714
+ originY: 'bottom',
24715
+ overlayX: 'start',
24716
+ overlayY: 'top',
24717
+ offsetY: 8,
24718
+ },
24719
+ {
24720
+ originX: 'start',
24721
+ originY: 'top',
24722
+ overlayX: 'start',
24723
+ overlayY: 'bottom',
24724
+ offsetY: -8,
24725
+ },
24726
+ ];
24727
+ this.scrollStrategy = this.scrollStrategies.reposition();
24728
+ this.overlayOpen = false;
24729
+ this.expandedBound = 'start';
24730
+ this.invalidBounds = { start: false, end: false };
24731
+ // redraw when the lazily loaded UI locale reaches the adapter
24732
+ this.dateAdapter.localeChanges
24733
+ .pipe(takeUntilDestroyed())
24734
+ .subscribe(() => this.cdr.markForCheck());
24581
24735
  }
24582
- ngAfterViewChecked() {
24583
- this.checkPickerOverlay();
24736
+ get startDate() {
24737
+ return this.dateRange.start;
24584
24738
  }
24585
- checkPickerOverlay() {
24586
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24587
- setTimeout(() => {
24588
- this.isPickerDisplayed =
24589
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24590
- null;
24591
- this.cdr.detectChanges();
24592
- }, 200); // FIXME: find a better way to deal with animation delay
24739
+ get endDate() {
24740
+ return this.dateRange.end;
24741
+ }
24742
+ get selectedDatesCount() {
24743
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24744
+ }
24745
+ openOverlay() {
24746
+ this.expandedBound = 'start';
24747
+ this.overlayOpen = true;
24748
+ }
24749
+ closeOverlay() {
24750
+ this.overlayOpen = false;
24751
+ }
24752
+ toggleBound(bound) {
24753
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24754
+ }
24755
+ formatDate(date) {
24756
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24757
+ }
24758
+ onDateInput(bound, event) {
24759
+ const typedText = event.target.value.trim();
24760
+ const date = typedText
24761
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24762
+ : null;
24763
+ if (!this.isAcceptable(bound, typedText, date)) {
24764
+ // keep the text as typed and flag it; the filter stays on its last value
24765
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24766
+ return;
24767
+ }
24768
+ if (bound === 'start')
24769
+ this.setStartDate(date);
24770
+ else
24771
+ this.setEndDate(date);
24772
+ }
24773
+ normalizeDateInput(bound, event) {
24774
+ if (this.invalidBounds[bound])
24775
+ return;
24776
+ const date = bound === 'start' ? this.startDate : this.endDate;
24777
+ const input = event.target;
24778
+ input.value = date ? this.formatDate(date) : '';
24779
+ }
24780
+ isAcceptable(bound, typedText, date) {
24781
+ if (!typedText)
24782
+ return true;
24783
+ if (!date || !this.dateAdapter.isValid(date))
24784
+ return false;
24785
+ return bound === 'start'
24786
+ ? !this.endDate || date <= this.endDate
24787
+ : !this.startDate || date >= this.startDate;
24788
+ }
24789
+ setStartDate(date) {
24790
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24791
+ this.applyDateRange({ ...this.dateRange, start: date });
24792
+ this.expandedBound = 'end';
24793
+ }
24794
+ setEndDate(date) {
24795
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24796
+ this.applyDateRange({ ...this.dateRange, end: date });
24797
+ this.expandedBound = null;
24798
+ }
24799
+ clearDates(event) {
24800
+ this.invalidBounds = { start: false, end: false };
24801
+ this.expandedBound = 'start';
24802
+ this.applyDateRange({});
24803
+ propagateToDocumentOnly(event);
24804
+ }
24805
+ applyDateRange(dateRange) {
24806
+ this.dateRange = {
24807
+ ...(dateRange.start && { start: dateRange.start }),
24808
+ ...(dateRange.end && { end: dateRange.end }),
24809
+ };
24810
+ this.dateRangeChange.emit(this.dateRange);
24593
24811
  }
24594
24812
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24595
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", startDate: "startDate", endDate: "endDate" }, outputs: { startDateChange: "startDateChange", endDateChange: "endDateChange" }, providers: [
24813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", dateRange: "dateRange" }, outputs: { dateRangeChange: "dateRangeChange" }, providers: [
24596
24814
  provideIcons({
24597
- matExpandMore,
24815
+ iconoirCalendar,
24816
+ matClose,
24598
24817
  matExpandLess,
24818
+ matExpandMore,
24599
24819
  }),
24600
- ], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"], dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }] }); }
24820
+ provideNgIconsConfig({
24821
+ size: '1.5rem',
24822
+ }),
24823
+ provideLocalizedDateAdapter(),
24824
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: AutofocusDirective, selector: "[gnUiAutofocus]", inputs: ["gnUiAutofocus"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24601
24825
  }
24602
24826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24603
24827
  type: Component,
24604
24828
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24605
- NgIconComponent,
24606
- MatNativeDateModule,
24607
- MatDatepickerModule,
24829
+ AutofocusDirective,
24608
24830
  ButtonComponent,
24831
+ MatDatepickerModule,
24832
+ NgIcon,
24833
+ NgTemplateOutlet,
24834
+ OverlayModule,
24835
+ TranslateDirective,
24836
+ TranslatePipe,
24609
24837
  ], providers: [
24610
24838
  provideIcons({
24611
- matExpandMore,
24839
+ iconoirCalendar,
24840
+ matClose,
24612
24841
  matExpandLess,
24842
+ matExpandMore,
24613
24843
  }),
24614
- ], template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"] }]
24615
- }], propDecorators: { title: [{
24616
- type: Input
24617
- }], startDate: [{
24844
+ provideNgIconsConfig({
24845
+ size: '1.5rem',
24846
+ }),
24847
+ provideLocalizedDateAdapter(),
24848
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"] }]
24849
+ }], ctorParameters: () => [], propDecorators: { title: [{
24618
24850
  type: Input
24619
- }], endDate: [{
24851
+ }], dateRange: [{
24620
24852
  type: Input
24621
- }], startDateChange: [{
24853
+ }], dateRangeChange: [{
24622
24854
  type: Output
24623
- }], endDateChange: [{
24624
- type: Output
24625
- }], picker: [{
24855
+ }], overlayOrigin: [{
24626
24856
  type: ViewChild,
24627
- args: ['picker']
24857
+ args: ['overlayOrigin']
24628
24858
  }] } });
24629
24859
 
24630
24860
  class DateRangePickerComponent {
@@ -24638,20 +24868,17 @@ class DateRangePickerComponent {
24638
24868
  provideNgIconsConfig({
24639
24869
  size: '1.5rem',
24640
24870
  }),
24641
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24871
+ provideLocalizedDateAdapter(),
24872
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24642
24873
  }
24643
24874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24644
24875
  type: Component,
24645
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24646
- MatNativeDateModule,
24647
- MatDatepickerModule,
24648
- ButtonComponent,
24649
- NgIconComponent,
24650
- ], providers: [
24876
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24651
24877
  provideIcons({ iconoirCalendar }),
24652
24878
  provideNgIconsConfig({
24653
24879
  size: '1.5rem',
24654
24880
  }),
24881
+ provideLocalizedDateAdapter(),
24655
24882
  ], template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24656
24883
  }], propDecorators: { startDate: [{
24657
24884
  type: Input
@@ -24669,28 +24896,68 @@ class DragAndDropFileInputComponent {
24669
24896
  constructor() {
24670
24897
  this.placeholder = placeholder;
24671
24898
  this.accept = '*';
24899
+ this.maxFileSizeMb = null;
24900
+ this.icon = null;
24901
+ this.dropzoneBackgroundColor = null;
24902
+ this.textClass = '';
24903
+ this.extraClass = '';
24904
+ this.showFileName = true;
24672
24905
  this.fileChange = new EventEmitter();
24906
+ this.errorChange = new EventEmitter();
24673
24907
  this.selectedFile = null;
24674
24908
  }
24675
24909
  get fileName() {
24676
24910
  return this.selectedFile && this.selectedFile.name;
24677
24911
  }
24912
+ get maxFileSizeBytes() {
24913
+ return typeof this.maxFileSizeMb === 'number'
24914
+ ? megabytesToBytes(this.maxFileSizeMb)
24915
+ : null;
24916
+ }
24678
24917
  selectFile(event) {
24918
+ if (event.rejectedFiles?.length) {
24919
+ const reason = event.rejectedFiles[0].reason;
24920
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24921
+ return;
24922
+ }
24679
24923
  this.selectedFile = event.addedFiles[0];
24680
24924
  this.fileChange.emit(this.selectedFile);
24681
24925
  }
24926
+ openFileSelector() {
24927
+ this.dropzone.showFileSelector();
24928
+ }
24929
+ clear() {
24930
+ this.selectedFile = null;
24931
+ }
24682
24932
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24683
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept" }, outputs: { fileChange: "fileChange" }, ngImport: i0, template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$7.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }] }); }
24933
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept", maxFileSizeMb: "maxFileSizeMb", icon: "icon", dropzoneBackgroundColor: "dropzoneBackgroundColor", textClass: "textClass", extraClass: "extraClass", showFileName: "showFileName" }, outputs: { fileChange: "fileChange", errorChange: "errorChange" }, viewQueries: [{ propertyName: "dropzone", first: true, predicate: NgxDropzoneComponent, descendants: true }], ngImport: i0, template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$8.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
24684
24934
  }
24685
24935
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24686
24936
  type: Component,
24687
- args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule], template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"] }]
24937
+ args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule, NgIcon], template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"] }]
24688
24938
  }], propDecorators: { placeholder: [{
24689
24939
  type: Input
24690
24940
  }], accept: [{
24691
24941
  type: Input
24942
+ }], maxFileSizeMb: [{
24943
+ type: Input
24944
+ }], icon: [{
24945
+ type: Input
24946
+ }], dropzoneBackgroundColor: [{
24947
+ type: Input
24948
+ }], textClass: [{
24949
+ type: Input
24950
+ }], extraClass: [{
24951
+ type: Input
24952
+ }], showFileName: [{
24953
+ type: Input
24692
24954
  }], fileChange: [{
24693
24955
  type: Output
24956
+ }], errorChange: [{
24957
+ type: Output
24958
+ }], dropzone: [{
24959
+ type: ViewChild,
24960
+ args: [NgxDropzoneComponent]
24694
24961
  }] } });
24695
24962
 
24696
24963
  class DropdownMultiselectComponent {
@@ -24845,7 +25112,7 @@ class DropdownMultiselectComponent {
24845
25112
  matExpandMore,
24846
25113
  matExpandLess,
24847
25114
  }),
24848
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25115
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24849
25116
  }
24850
25117
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24851
25118
  type: Component,
@@ -24855,7 +25122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24855
25122
  matExpandMore,
24856
25123
  matExpandLess,
24857
25124
  }),
24858
- ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
25125
+ ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
24859
25126
  }], propDecorators: { title: [{
24860
25127
  type: Input
24861
25128
  }], choices: [{
@@ -25027,7 +25294,7 @@ class DropdownSelectorComponent {
25027
25294
  provideNgIconsConfig({
25028
25295
  size: '1.5em',
25029
25296
  }),
25030
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25297
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25031
25298
  }
25032
25299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25033
25300
  type: Component,
@@ -25767,6 +26034,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25767
26034
  type: Output
25768
26035
  }] } });
25769
26036
 
26037
+ marker('search.filters.spatialExtent.import');
26038
+ marker('search.filters.spatialExtent.helpText');
26039
+ marker('search.filters.spatialExtent.error.title');
26040
+ marker('search.filters.spatialExtent.bboxPrefix');
26041
+ marker('search.filters.spatialExtent.bboxDelete');
26042
+ class SpatialExtentDropdownComponent {
26043
+ constructor() {
26044
+ this.cd = inject(ChangeDetectorRef);
26045
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26046
+ this.maxFileSizeMb = null;
26047
+ this.bboxChange = new EventEmitter();
26048
+ this.errorChange = new EventEmitter();
26049
+ this.bbox = null;
26050
+ this.fileName = '';
26051
+ this.overlayPositions = [
26052
+ {
26053
+ originX: 'start',
26054
+ originY: 'bottom',
26055
+ overlayX: 'start',
26056
+ overlayY: 'top',
26057
+ offsetY: 8,
26058
+ },
26059
+ {
26060
+ originX: 'start',
26061
+ originY: 'top',
26062
+ overlayX: 'start',
26063
+ overlayY: 'bottom',
26064
+ offsetY: -8,
26065
+ },
26066
+ ];
26067
+ this.scrollStrategy = this.scrollStrategies.reposition();
26068
+ this.overlayOpen = false;
26069
+ this.overlayMinWidth = 'none';
26070
+ this.errorKey = null;
26071
+ }
26072
+ get hasSelection() {
26073
+ return !!this.bbox;
26074
+ }
26075
+ openOverlay() {
26076
+ this.overlayMinWidth =
26077
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26078
+ .width + 'px';
26079
+ this.overlayOpen = true;
26080
+ }
26081
+ closeOverlay() {
26082
+ this.overlayOpen = false;
26083
+ }
26084
+ toggleOverlay() {
26085
+ if (this.overlayOpen) {
26086
+ this.closeOverlay();
26087
+ }
26088
+ else {
26089
+ this.openOverlay();
26090
+ }
26091
+ }
26092
+ async handleFileSelected(file) {
26093
+ this.errorKey = null;
26094
+ let content;
26095
+ try {
26096
+ content = await readFileAsText(file);
26097
+ const parsed = JSON.parse(content);
26098
+ const geometry = getGeometryFromGeoJSON(parsed);
26099
+ if (!geometry) {
26100
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26101
+ return;
26102
+ }
26103
+ const bbox = getGeometryBoundingBox(geometry);
26104
+ this.bbox = bbox;
26105
+ this.fileName = file.name;
26106
+ this.bboxChange.emit(bbox);
26107
+ this.cd.markForCheck();
26108
+ }
26109
+ catch {
26110
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26111
+ return;
26112
+ }
26113
+ }
26114
+ handleFileError(error) {
26115
+ if (error === 'file-too-large') {
26116
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26117
+ maxSize: this.maxFileSizeMb,
26118
+ });
26119
+ }
26120
+ else {
26121
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26122
+ }
26123
+ }
26124
+ setError(errorKey, params) {
26125
+ this.errorKey = errorKey;
26126
+ this.errorChange.emit({ key: errorKey, params });
26127
+ this.cd.markForCheck();
26128
+ }
26129
+ removeSelection(event) {
26130
+ this.bbox = null;
26131
+ this.fileName = '';
26132
+ this.errorKey = null;
26133
+ this.fileInput?.clear();
26134
+ this.bboxChange.emit(null);
26135
+ propagateToDocumentOnly(event);
26136
+ }
26137
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26138
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SpatialExtentDropdownComponent, isStandalone: true, selector: "gn-ui-spatial-extent-dropdown", inputs: { title: "title", maxFileSizeMb: "maxFileSizeMb" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26139
+ provideIcons({
26140
+ iconoirCheckCircle,
26141
+ iconoirImport,
26142
+ iconoirSquareDashed,
26143
+ iconoirTrash,
26144
+ matClose,
26145
+ matExpandLess,
26146
+ matExpandMore,
26147
+ }),
26148
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "fileInput", first: true, predicate: DragAndDropFileInputComponent, descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26149
+ }
26150
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26151
+ type: Component,
26152
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26153
+ ButtonComponent,
26154
+ NgIcon,
26155
+ OverlayModule,
26156
+ TranslatePipe,
26157
+ DragAndDropFileInputComponent,
26158
+ ], providers: [
26159
+ provideIcons({
26160
+ iconoirCheckCircle,
26161
+ iconoirImport,
26162
+ iconoirSquareDashed,
26163
+ iconoirTrash,
26164
+ matClose,
26165
+ matExpandLess,
26166
+ matExpandMore,
26167
+ }),
26168
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n" }]
26169
+ }], propDecorators: { title: [{
26170
+ type: Input
26171
+ }], maxFileSizeMb: [{
26172
+ type: Input
26173
+ }], bboxChange: [{
26174
+ type: Output
26175
+ }], errorChange: [{
26176
+ type: Output
26177
+ }], overlayOrigin: [{
26178
+ type: ViewChild,
26179
+ args: ['overlayOrigin']
26180
+ }], overlay: [{
26181
+ type: ViewChild,
26182
+ args: [CdkConnectedOverlay]
26183
+ }], fileInput: [{
26184
+ type: ViewChild,
26185
+ args: [DragAndDropFileInputComponent]
26186
+ }] } });
26187
+
25770
26188
  class CellPopinComponent {
25771
26189
  constructor() {
25772
26190
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25843,7 +26261,7 @@ class CellPopinComponent {
25843
26261
  this.isOpen = false;
25844
26262
  }
25845
26263
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25846
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
26264
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
25847
26265
  }
25848
26266
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25849
26267
  type: Component,
@@ -27933,6 +28351,23 @@ class DateRangeSearchField extends SimpleSearchField {
27933
28351
  return 'dateRange';
27934
28352
  }
27935
28353
  }
28354
+ class BoundingBoxSearchField extends SimpleSearchField {
28355
+ getAvailableValues() {
28356
+ return of([]);
28357
+ }
28358
+ getFiltersForValues(values) {
28359
+ return of({
28360
+ [this.esFieldName]: values.map(Number),
28361
+ });
28362
+ }
28363
+ getValuesForFilter(filters) {
28364
+ const filter = filters[this.esFieldName];
28365
+ return of(isBoundingBox(filter) ? filter : []);
28366
+ }
28367
+ getType() {
28368
+ return 'spatialExtent';
28369
+ }
28370
+ }
27936
28371
  marker('search.filters.availableServices.view');
27937
28372
  marker('search.filters.availableServices.download');
27938
28373
  class AvailableServicesField extends SimpleSearchField {
@@ -28077,6 +28512,7 @@ marker('search.filters.producerOrg');
28077
28512
  marker('search.filters.publisherOrg');
28078
28513
  marker('search.filters.user');
28079
28514
  marker('search.filters.changeDate');
28515
+ marker('search.filters.spatialExtent');
28080
28516
  class FieldsService {
28081
28517
  constructor() {
28082
28518
  this.injector = inject(Injector);
@@ -28100,6 +28536,7 @@ class FieldsService {
28100
28536
  user: new UserSearchField(this.injector),
28101
28537
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28102
28538
  availableServices: new AvailableServicesField(this.injector),
28539
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28103
28540
  };
28104
28541
  }
28105
28542
  get supportedFields() {
@@ -29362,7 +29799,7 @@ class ContactPillComponent {
29362
29799
  this.overlayOpen = false;
29363
29800
  }
29364
29801
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29365
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29802
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29366
29803
  }
29367
29804
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29368
29805
  type: Component,
@@ -31284,19 +31721,111 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31284
31721
  type: Output
31285
31722
  }] } });
31286
31723
 
31724
+ class NotificationsService {
31725
+ constructor() {
31726
+ this.notifications$ = new BehaviorSubject([]);
31727
+ }
31728
+ showNotification(content, timeoutMs, error) {
31729
+ error && console.error(error);
31730
+ const id = Math.floor(Math.random() * 1000000);
31731
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31732
+ if (typeof timeoutMs !== 'undefined') {
31733
+ setTimeout(() => {
31734
+ this.removeNotificationById(id);
31735
+ }, timeoutMs);
31736
+ }
31737
+ return id;
31738
+ }
31739
+ removeNotificationById(id) {
31740
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31741
+ }
31742
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31743
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31744
+ }
31745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31746
+ type: Injectable,
31747
+ args: [{
31748
+ providedIn: 'root',
31749
+ }]
31750
+ }] });
31751
+
31752
+ class NotificationsContainerComponent {
31753
+ constructor() {
31754
+ this.notificationsService = inject(NotificationsService);
31755
+ }
31756
+ trackById(index, notification) {
31757
+ return notification.id;
31758
+ }
31759
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31760
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31761
+ trigger('enterExit', [
31762
+ transition(':enter', [
31763
+ animate('150ms', keyframes([
31764
+ style({ transform: 'scale(1)', opacity: 0 }),
31765
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31766
+ style({ transform: 'scale(1)', opacity: 1 }),
31767
+ ])),
31768
+ ]),
31769
+ transition(':leave', [
31770
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31771
+ ]),
31772
+ ]),
31773
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31774
+ }
31775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31776
+ type: Component,
31777
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31778
+ trigger('enterExit', [
31779
+ transition(':enter', [
31780
+ animate('150ms', keyframes([
31781
+ style({ transform: 'scale(1)', opacity: 0 }),
31782
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31783
+ style({ transform: 'scale(1)', opacity: 1 }),
31784
+ ])),
31785
+ ]),
31786
+ transition(':leave', [
31787
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31788
+ ]),
31789
+ ]),
31790
+ ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31791
+ }] });
31792
+
31287
31793
  class FilterDropdownComponent {
31288
31794
  constructor() {
31289
31795
  this.searchFacade = inject(SearchFacade);
31290
31796
  this.searchService = inject(SearchService);
31291
31797
  this.fieldsService = inject(FieldsService);
31798
+ this.notificationsService = inject(NotificationsService);
31799
+ this.translateService = inject(TranslateService);
31800
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31292
31801
  this.selected$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$1((fieldValues) => fieldValues[this.fieldName]), filter$1((selected) => !!selected), startWith$1([]), catchError(() => of([])));
31293
- this.selectedDateRange$ = this.selected$.pipe(map$1((selectedDateRange) => selectedDateRange));
31802
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31803
+ this.spatialExtentErrorNotificationId = null;
31294
31804
  }
31295
31805
  onSelectedValues(values) {
31296
31806
  this.fieldsService
31297
31807
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31298
31808
  .subscribe((filters) => this.searchService.updateFilters(filters));
31299
31809
  }
31810
+ onBboxChange(bbox) {
31811
+ console.log(bbox);
31812
+ this.clearSpatialExtentErrorNotification();
31813
+ }
31814
+ onSpatialExtentError(error) {
31815
+ this.clearSpatialExtentErrorNotification();
31816
+ this.spatialExtentErrorNotificationId =
31817
+ this.notificationsService.showNotification({
31818
+ type: 'error',
31819
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31820
+ text: this.translateService.instant(error.key, error.params),
31821
+ });
31822
+ }
31823
+ clearSpatialExtentErrorNotification() {
31824
+ if (this.spatialExtentErrorNotificationId === null)
31825
+ return;
31826
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31827
+ this.spatialExtentErrorNotificationId = null;
31828
+ }
31300
31829
  ngOnInit() {
31301
31830
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31302
31831
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31304,27 +31833,15 @@ class FilterDropdownComponent {
31304
31833
  value: v.value.toString(), // converting to string for the dropdown
31305
31834
  }))), catchError(() => of([])));
31306
31835
  }
31307
- onStartDateChange(start) {
31308
- if (!start)
31309
- return;
31310
- this.dateRange = { ...this.dateRange, start };
31311
- }
31312
- onEndDateChange(end) {
31313
- if (!end)
31314
- return;
31315
- this.dateRange = { ...this.dateRange, end };
31316
- if (this.dateRange.start && this.dateRange.end) {
31317
- this.fieldsService
31318
- .buildFiltersFromFieldValues({
31319
- [this.fieldName]: this.dateRange,
31320
- })
31321
- .subscribe((filters) => {
31322
- return this.searchService.updateFilters(filters);
31323
- });
31324
- }
31836
+ onDateRangeChange(dateRange) {
31837
+ this.fieldsService
31838
+ .buildFiltersFromFieldValues({
31839
+ [this.fieldName]: dateRange,
31840
+ })
31841
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31325
31842
  }
31326
31843
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31327
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31844
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31328
31845
  }
31329
31846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31330
31847
  type: Component,
@@ -31332,7 +31849,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31332
31849
  CommonModule,
31333
31850
  DateRangeDropdownComponent,
31334
31851
  DropdownMultiselectComponent,
31335
- ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31852
+ SpatialExtentDropdownComponent,
31853
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31336
31854
  }], propDecorators: { fieldName: [{
31337
31855
  type: Input
31338
31856
  }], title: [{
@@ -31802,11 +32320,7 @@ const SortByEnum = {
31802
32320
  RELEVANCY: ['desc', '_score'],
31803
32321
  QUALITY_SCORE: ['desc', 'qualityScore'],
31804
32322
  CHANGE_DATE: ['desc', 'changeDate'],
31805
- RESOURCE_DATES: [
31806
- ['desc', 'revisionDateForResource'],
31807
- ['desc', 'publicationDateForResource'],
31808
- ['desc', 'creationDateForResource'],
31809
- ],
32323
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
31810
32324
  };
31811
32325
 
31812
32326
  class SortByComponent {
@@ -31820,7 +32334,7 @@ class SortByComponent {
31820
32334
  },
31821
32335
  {
31822
32336
  label: marker('results.sortBy.dateStamp'),
31823
- value: SortByEnum.RESOURCE_DATES,
32337
+ value: SortByEnum.RESOURCE_DATE,
31824
32338
  },
31825
32339
  {
31826
32340
  label: marker('results.sortBy.popularity'),
@@ -31872,74 +32386,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31872
32386
  args: ['gnUiSearchStateContainer']
31873
32387
  }] } });
31874
32388
 
31875
- class NotificationsService {
31876
- constructor() {
31877
- this.notifications$ = new BehaviorSubject([]);
31878
- }
31879
- showNotification(content, timeoutMs, error) {
31880
- error && console.error(error);
31881
- const id = Math.floor(Math.random() * 1000000);
31882
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31883
- if (typeof timeoutMs === 'undefined')
31884
- return;
31885
- setTimeout(() => {
31886
- this.removeNotificationById(id);
31887
- }, timeoutMs);
31888
- }
31889
- removeNotificationById(id) {
31890
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31891
- }
31892
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31893
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31894
- }
31895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31896
- type: Injectable,
31897
- args: [{
31898
- providedIn: 'root',
31899
- }]
31900
- }] });
31901
-
31902
- class NotificationsContainerComponent {
31903
- constructor() {
31904
- this.notificationsService = inject(NotificationsService);
31905
- }
31906
- trackById(index, notification) {
31907
- return notification.id;
31908
- }
31909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31910
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31911
- trigger('enterExit', [
31912
- transition(':enter', [
31913
- animate('150ms', keyframes([
31914
- style({ transform: 'scale(1)', opacity: 0 }),
31915
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31916
- style({ transform: 'scale(1)', opacity: 1 }),
31917
- ])),
31918
- ]),
31919
- transition(':leave', [
31920
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31921
- ]),
31922
- ]),
31923
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31924
- }
31925
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31926
- type: Component,
31927
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31928
- trigger('enterExit', [
31929
- transition(':enter', [
31930
- animate('150ms', keyframes([
31931
- style({ transform: 'scale(1)', opacity: 0 }),
31932
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31933
- style({ transform: 'scale(1)', opacity: 1 }),
31934
- ])),
31935
- ]),
31936
- transition(':leave', [
31937
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31938
- ]),
31939
- ]),
31940
- ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31941
- }] });
31942
-
31943
32389
  class ResultsTableContainerComponent {
31944
32390
  constructor() {
31945
32391
  this.searchFacade = inject(SearchFacade);
@@ -32024,12 +32470,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32024
32470
 
32025
32471
  marker('search.filters.summaryLabel.user');
32026
32472
  marker('search.filters.summaryLabel.changeDate');
32473
+ const OPEN_BOUND = '…';
32027
32474
  class SearchFiltersSummaryItemComponent {
32028
32475
  constructor() {
32029
32476
  this.searchFacade = inject(SearchFacade);
32030
32477
  this.searchService = inject(SearchService);
32031
32478
  this.fieldsService = inject(FieldsService);
32032
- this.datePipe = inject(DatePipe);
32479
+ this.dateService = inject(DateService);
32033
32480
  this.translate = inject(TranslateService);
32034
32481
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32035
32482
  ? fieldValues[this.fieldName]
@@ -32056,9 +32503,10 @@ class SearchFiltersSummaryItemComponent {
32056
32503
  getReadableValues(fieldValues) {
32057
32504
  return fieldValues.map((value) => {
32058
32505
  if (this.fieldType === 'dateRange') {
32506
+ const { start, end } = value;
32059
32507
  return {
32060
32508
  value,
32061
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32509
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32062
32510
  };
32063
32511
  }
32064
32512
  else if (this.fieldName === 'user') {
@@ -32069,6 +32517,15 @@ class SearchFiltersSummaryItemComponent {
32069
32517
  }
32070
32518
  });
32071
32519
  }
32520
+ formatBound(date) {
32521
+ return date
32522
+ ? this.dateService.formatDate(date, {
32523
+ day: '2-digit',
32524
+ month: '2-digit',
32525
+ year: 'numeric',
32526
+ })
32527
+ : OPEN_BOUND;
32528
+ }
32072
32529
  async removeFilterValue(fieldValue) {
32073
32530
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32074
32531
  const updatedFieldValues = currentFieldValues
@@ -32081,11 +32538,11 @@ class SearchFiltersSummaryItemComponent {
32081
32538
  .subscribe((filters) => this.searchService.updateFilters(filters));
32082
32539
  }
32083
32540
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32084
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, providers: [DatePipe], ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32541
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32085
32542
  }
32086
32543
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32087
32544
  type: Component,
32088
- args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], providers: [DatePipe], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32545
+ args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32089
32546
  }], propDecorators: { fieldName: [{
32090
32547
  type: Input
32091
32548
  }] } });
@@ -32329,11 +32786,11 @@ class AddLayerFromFileComponent {
32329
32786
  }, 5000);
32330
32787
  }
32331
32788
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32332
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept"], outputs: ["fileChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32789
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32333
32790
  }
32334
32791
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32335
32792
  type: Component,
32336
- args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32793
+ args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32337
32794
  }] });
32338
32795
 
32339
32796
  class LayersPanelComponent {
@@ -32498,8 +32955,7 @@ const closeMetadata = createAction('[Metadata view] close');
32498
32955
  Related actions
32499
32956
  */
32500
32957
  const setRelated = createAction('[Metadata view] Set related records', props());
32501
- const setSources = createAction('[Metadata view] Set sources', props());
32502
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32958
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32503
32959
  /*
32504
32960
  ChartConfig actions
32505
32961
  */
@@ -32531,9 +32987,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32531
32987
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32532
32988
  setChartConfig: setChartConfig,
32533
32989
  setIncompleteMetadata: setIncompleteMetadata,
32534
- setRelated: setRelated,
32535
- setSourceOf: setSourceOf,
32536
- setSources: setSources
32990
+ setLinkedRecords: setLinkedRecords,
32991
+ setRelated: setRelated
32537
32992
  });
32538
32993
 
32539
32994
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32576,12 +33031,9 @@ on(loadFullMetadata, (state) => ({
32576
33031
  on(setRelated, (state, { related }) => ({
32577
33032
  ...state,
32578
33033
  related,
32579
- })), on(setSources, (state, { sources }) => ({
33034
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32580
33035
  ...state,
32581
- sources,
32582
- })), on(setSourceOf, (state, { sourceOf }) => ({
32583
- ...state,
32584
- sourceOf,
33036
+ linkedRecords,
32585
33037
  })),
32586
33038
  /*
32587
33039
  ChartConfig reducers
@@ -32643,8 +33095,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32643
33095
  Related selectors
32644
33096
  */
32645
33097
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32646
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32647
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33098
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32648
33099
  /*
32649
33100
  Metadata selectors
32650
33101
  */
@@ -32745,212 +33196,10 @@ function parseHeaders(httpHeaders) {
32745
33196
  return result;
32746
33197
  }
32747
33198
 
32748
- async function inferDatasetType(url, typeHint) {
32749
- const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32750
- const fileExtension = fileExtensionMatches && fileExtensionMatches.length
32751
- ? fileExtensionMatches[1].toLowerCase()
32752
- : null;
32753
- // 1. type hint
32754
- if (typeHint)
32755
- return Promise.resolve(typeHint);
32756
- // 2. content-type header
32757
- const headers = await fetchHeaders(url);
32758
- if ('supportedType' in headers)
32759
- return headers.supportedType;
32760
- // 3. file extension from url
32761
- else if (SupportedTypes.indexOf(fileExtension) > -1)
32762
- return fileExtension;
32763
- // no type inferred or hinted
32764
- if ('mimeType' in headers)
32765
- throw FetchError.unsupportedType(headers.mimeType);
32766
- else
32767
- throw FetchError.unknownType();
32768
- }
32769
- function fetchHeaders(url) {
32770
- return sharedFetch(url, 'HEAD')
32771
- .catch((error) => {
32772
- throw FetchError.corsOrNetwork(error.message);
32773
- })
32774
- .then((response) => {
32775
- if (!response.ok) {
32776
- throw FetchError.http(response.status);
32777
- }
32778
- return parseHeaders(response.headers);
32779
- });
32780
- }
32781
- function fetchDataAsText(url, cacheActive) {
32782
- const fetchFactory = () => sharedFetch(url)
32783
- .catch((error) => {
32784
- throw FetchError.corsOrNetwork(error.message);
32785
- })
32786
- .then(async (response) => {
32787
- if (!response.ok) {
32788
- const clonedResponse = response.clone();
32789
- throw FetchError.http(response.status, await clonedResponse.text());
32790
- }
32791
- const clonedResponse = response.clone();
32792
- return clonedResponse.text();
32793
- });
32794
- return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
32795
- }
32796
- function fetchDataAsArrayBuffer(url, cacheActive) {
32797
- const fetchFactory = () => sharedFetch(url)
32798
- .catch((error) => {
32799
- throw FetchError.corsOrNetwork(error.message);
32800
- })
32801
- .then(async (response) => {
32802
- if (!response.ok) {
32803
- throw FetchError.http(response.status, await response.text());
32804
- }
32805
- // convert to a numeric array so that we can store the response in cache
32806
- return Array.from(new Uint8Array(await response.arrayBuffer()));
32807
- });
32808
- return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
32809
- return new Uint8Array(array).buffer;
32810
- });
32811
- }
32812
- function tryParseDate(input) {
32813
- if (typeof input !== 'string')
32814
- return null;
32815
- function tryIso(value) {
32816
- const parsed = parseISO(value);
32817
- return isNaN(parsed.getDate()) ? null : parsed;
32818
- }
32819
- function tryFormat(value, format) {
32820
- const parsed = parse$4(value, format, new Date());
32821
- return isNaN(parsed.getDate()) ? null : parsed;
32822
- }
32823
- return (tryIso(input) ||
32824
- tryFormat(input, 'dd/MM/yyyy') ||
32825
- tryFormat(input, 'dd.MM.yyyy') ||
32826
- tryFormat(input, 'MM/dd/yyyy') ||
32827
- null);
32828
- }
32829
- function tryParseNumber(input) {
32830
- if (isNaN(input))
32831
- return null;
32832
- const parsed = parseFloat(input);
32833
- return isNaN(parsed) ? null : parsed;
32834
- }
32835
- function jsonToGeojsonFeature(object) {
32836
- const { id, properties } = Object.keys(object)
32837
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32838
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32839
- ? {
32840
- ...prev,
32841
- id: object[curr],
32842
- }
32843
- : {
32844
- ...prev,
32845
- properties: { ...prev.properties, [curr]: object[curr] },
32846
- }, { id: undefined, properties: {} });
32847
- return {
32848
- type: 'Feature',
32849
- geometry: null,
32850
- properties,
32851
- ...(id !== undefined && { id }),
32852
- };
32853
- }
32854
- function mutateProperties(items, mutators) {
32855
- const mutatorKeys = Object.keys(mutators);
32856
- for (let i = 0, ii = items.length; i < ii; i++) {
32857
- const item = items[i];
32858
- for (const mutatorField of mutatorKeys) {
32859
- if (!(mutatorField in item.properties))
32860
- continue;
32861
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32862
- }
32863
- }
32864
- return items;
32865
- }
32866
- const SAMPLE_SIZE = 20;
32867
- /**
32868
- * This will infer field types from a list of data items and cast the values accordingly
32869
- * @param items
32870
- * @param inferTypes
32871
- */
32872
- function processItemProperties(items, inferTypes = false) {
32873
- const foundFields = {};
32874
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32875
- const item = items[i];
32876
- const fields = Object.keys(item.properties);
32877
- for (const field of fields) {
32878
- if (!(field in foundFields)) {
32879
- foundFields[field] = {
32880
- label: field,
32881
- name: field,
32882
- type: null,
32883
- };
32884
- }
32885
- const value = item.properties[field];
32886
- const info = foundFields[field];
32887
- if (value === undefined || value === '' || value === null)
32888
- continue;
32889
- if (!inferTypes) {
32890
- if (info.type === null && typeof value === 'number') {
32891
- info.type = 'number';
32892
- }
32893
- else if (info.type === 'number' && typeof value !== 'number') {
32894
- info.type = 'string';
32895
- }
32896
- continue;
32897
- }
32898
- const parsedNumber = tryParseNumber(value);
32899
- if (info.type === null && parsedNumber !== null) {
32900
- info.type = 'number';
32901
- continue;
32902
- }
32903
- else if (info.type === 'number' && parsedNumber === null) {
32904
- info.type = 'string';
32905
- continue;
32906
- }
32907
- const parsedDate = tryParseDate(value);
32908
- if (info.type === null && parsedDate !== null) {
32909
- info.type = 'date';
32910
- }
32911
- else if (info.type === 'date' && parsedDate === null) {
32912
- info.type = 'string';
32913
- }
32914
- }
32915
- }
32916
- const properties = [];
32917
- const mutators = {};
32918
- for (const field in foundFields) {
32919
- const info = foundFields[field];
32920
- if (info.type === 'number') {
32921
- mutators[field] = tryParseNumber;
32922
- }
32923
- else if (info.type === 'date') {
32924
- mutators[field] = tryParseDate;
32925
- }
32926
- properties.push({ ...info, type: info.type || 'string' });
32927
- }
32928
- if (inferTypes) {
32929
- mutateProperties(items, mutators);
32930
- }
32931
- return { items, properties };
33199
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33200
+ function fieldToSql(name) {
33201
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32932
33202
  }
32933
- /**
32934
- * This creates a Proxy that allows reading and writing to the data item properties
32935
- * as if it was a simple array of JSON objects
32936
- * @param items
32937
- */
32938
- function getJsonDataItemsProxy(items) {
32939
- return new Proxy(items, {
32940
- get(target, p) {
32941
- if (typeof p === 'string' &&
32942
- !Number.isNaN(parseInt(p)) &&
32943
- target[p]?.properties) {
32944
- return target[p].properties;
32945
- }
32946
- return target[p];
32947
- },
32948
- set() {
32949
- throw new Error('This object is read-only');
32950
- },
32951
- });
32952
- }
32953
-
32954
33203
  function filterToSql(filter) {
32955
33204
  const operator = filter[0];
32956
33205
  const args = filter.slice(1);
@@ -32965,10 +33214,10 @@ function filterToSql(filter) {
32965
33214
  case '=':
32966
33215
  case '!=':
32967
33216
  case 'like':
32968
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33217
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32969
33218
  case 'in': {
32970
33219
  const values = args.slice(1);
32971
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33220
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32972
33221
  }
32973
33222
  case 'and':
32974
33223
  case 'or': {
@@ -32987,17 +33236,18 @@ function aggregationToSql(aggregation) {
32987
33236
  const field = aggregation[1];
32988
33237
  switch (operation) {
32989
33238
  case 'average':
32990
- return `AVG([${field}]) as [average(${field})]`;
33239
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
32991
33240
  case 'sum':
32992
33241
  case 'max':
32993
33242
  case 'min':
32994
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33243
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
32995
33244
  case 'count':
32996
- return 'COUNT(*) as [count()]';
33245
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
32997
33246
  }
32998
33247
  }
32999
33248
  /**
33000
33249
  * Leave arguments at null if not used
33250
+ * @param tableName
33001
33251
  * @param selected
33002
33252
  * @param filter
33003
33253
  * @param sort
@@ -33006,22 +33256,25 @@ function aggregationToSql(aggregation) {
33006
33256
  * @param groupBy
33007
33257
  * @param aggregations
33008
33258
  */
33009
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33259
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33010
33260
  let sqlSelect = 'SELECT *';
33011
- const sqlFrom = ' FROM ?';
33261
+ const sqlFrom = ` FROM ${tableName}`;
33012
33262
  let sqlOrderBy = '';
33013
33263
  let sqlWhere = '';
33014
33264
  let sqlLimit = '';
33015
33265
  let sqlGroupBy = '';
33016
33266
  if (selected !== null) {
33017
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33267
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33268
+ }
33269
+ if (geometryColumn !== null) {
33270
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33018
33271
  }
33019
33272
  if (filter !== null) {
33020
33273
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33021
33274
  }
33022
33275
  if (sort?.length) {
33023
33276
  sqlOrderBy = ` ORDER BY ${sort
33024
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33277
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33025
33278
  .join(', ')}`;
33026
33279
  }
33027
33280
  if (startIndex !== null && count !== null) {
@@ -33031,17 +33284,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33031
33284
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33032
33285
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33033
33286
  const sqlGroupByFields = groupedByDistinct
33034
- .map((group) => `[${group[1]}]`)
33287
+ .map((group) => fieldToSql(group[1]))
33035
33288
  .join(', ');
33036
33289
  const sqlGroupBySelect = groupedByDistinct
33037
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33290
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33038
33291
  .join(', ');
33039
33292
  if (sqlGroupByFields && sqlGroupBySelect) {
33040
33293
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33041
33294
  sqlSelect += `, ${sqlGroupBySelect}`;
33042
33295
  }
33043
33296
  }
33044
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33297
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33298
+ }
33299
+
33300
+ function arrowTableToDataItems(table) {
33301
+ const fields = table.schema.fields;
33302
+ return table.toArray().map((row) => {
33303
+ const rowJson = row.toJSON();
33304
+ const feature = {
33305
+ type: 'Feature',
33306
+ geometry: null,
33307
+ properties: {},
33308
+ };
33309
+ const keys = Object.keys(rowJson);
33310
+ for (let i = 0; i < keys.length; i++) {
33311
+ const key = keys[i];
33312
+ const dataType = fields[i].type;
33313
+ let value = rowJson[key];
33314
+ // this might happen if we get an array inside a field
33315
+ if (value instanceof Vector) {
33316
+ value = Array.from(value);
33317
+ }
33318
+ // cast bigints to ints
33319
+ if (typeof value === 'bigint') {
33320
+ value = Number(value);
33321
+ }
33322
+ // rename columns with empty name
33323
+ if (!key) {
33324
+ feature.properties['unknown'] = value;
33325
+ continue;
33326
+ }
33327
+ // assign properties that look like an id to the geojson `id` field
33328
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33329
+ (typeof value == 'string' || typeof value === 'number')) {
33330
+ feature.id = value;
33331
+ }
33332
+ // if a date is in timestamp (number) format, convert it to native
33333
+ if (typeof value === 'number' &&
33334
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33335
+ value = new Date(value);
33336
+ }
33337
+ // if a binary field (most likely a geometry column): skip
33338
+ if (DataType.isBinary(dataType)) {
33339
+ continue;
33340
+ }
33341
+ // geometry column
33342
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33343
+ feature.geometry = JSON.parse(value);
33344
+ continue;
33345
+ }
33346
+ feature.properties[key] = value;
33347
+ }
33348
+ return feature;
33349
+ });
33350
+ }
33351
+
33352
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33353
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33354
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33355
+ const typesMapping = {
33356
+ INTEGER: 'number',
33357
+ SMALLINT: 'number',
33358
+ TINYINT: 'number',
33359
+ BIGINT: 'number',
33360
+ HUGEINT: 'number',
33361
+ UINTEGER: 'number',
33362
+ USMALLINT: 'number',
33363
+ UTINYINT: 'number',
33364
+ UBIGINT: 'number',
33365
+ UHUGEINT: 'number',
33366
+ DOUBLE: 'number',
33367
+ BIGNUM: 'number',
33368
+ DECIMAL: 'number',
33369
+ NUMERIC: 'number',
33370
+ FLOAT: 'number',
33371
+ REAL: 'number',
33372
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33373
+ TIMESTAMP: 'date',
33374
+ DATE: 'date',
33375
+ CHAR: 'string',
33376
+ VARCHAR: 'string',
33377
+ TEXT: 'string',
33378
+ UUID: 'string',
33379
+ BLOB: 'string',
33380
+ BIT: 'string',
33381
+ INTERVAL: 'string',
33382
+ LIST: 'string',
33383
+ BOOLEAN: 'boolean',
33384
+ };
33385
+ class Engine {
33386
+ async makeInit() {
33387
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33388
+ let worker_url = bundle.mainWorker;
33389
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33390
+ if (worker_url.startsWith('https://')) {
33391
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33392
+ type: 'text/javascript',
33393
+ }));
33394
+ }
33395
+ const worker = new Worker(worker_url);
33396
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33397
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33398
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33399
+ // setup duckdb options
33400
+ const conn = await this.db.connect();
33401
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33402
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33403
+ await conn.query(`SET TimeZone = '${timezone}';`);
33404
+ await conn.query(`SET threads = 1;`);
33405
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33406
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33407
+ conn.close();
33408
+ return this;
33409
+ }
33410
+ isReady() {
33411
+ if (!this.init_) {
33412
+ this.init_ = this.makeInit();
33413
+ }
33414
+ return this.init_;
33415
+ }
33416
+ /**
33417
+ * Returns information about a dataset once it's loaded:
33418
+ * - a list of properties description
33419
+ * - the name of the dataset geometry column (null if no geometry present)
33420
+ * @param datasetId name of the table under which the dataset will be stored
33421
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33422
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33423
+ */
33424
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33425
+ const conn = await this.db.connect();
33426
+ let results;
33427
+ // either we want to recreate the table, or we keep it if it already exists
33428
+ const safeLoadQuery = forceReload
33429
+ ? `DROP TABLE IF EXISTS ${datasetId};
33430
+ ${loadQuery}`
33431
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33432
+ // create the table
33433
+ try {
33434
+ results = await conn.query(safeLoadQuery);
33435
+ }
33436
+ catch (e) {
33437
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33438
+ }
33439
+ // read rows count
33440
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33441
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33442
+ // read columns
33443
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33444
+ let geometryColumn = null;
33445
+ const properties = results
33446
+ .toArray()
33447
+ .map((row) => {
33448
+ const rowObj = row.toJSON();
33449
+ if (rowObj['data_type'] === 'GEOMETRY') {
33450
+ // the geometry is not part of the properties
33451
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33452
+ // all get discarded
33453
+ geometryColumn = rowObj['column_name'];
33454
+ return null;
33455
+ }
33456
+ return {
33457
+ name: rowObj['column_name'],
33458
+ label: rowObj['column_name'],
33459
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33460
+ };
33461
+ })
33462
+ .filter((prop) => prop !== null);
33463
+ conn.close();
33464
+ return {
33465
+ properties,
33466
+ geometryColumn,
33467
+ rowsCount: Number(recordsCount),
33468
+ };
33469
+ }
33470
+ // register a Uint8 buffer using a handle in the duckdb instance
33471
+ async registerData(name, buffer) {
33472
+ return this.db.registerFileBuffer(name, buffer);
33473
+ }
33474
+ /**
33475
+ * @param query duckdb-specific query for fetching items
33476
+ */
33477
+ async queryItems(query) {
33478
+ const conn = await this.db.connect();
33479
+ const results = await conn.query(query);
33480
+ conn.close();
33481
+ return arrowTableToDataItems(results);
33482
+ }
33483
+ close() {
33484
+ this.db?.terminate();
33485
+ }
33486
+ }
33487
+ let engine = null;
33488
+ async function getEngine() {
33489
+ if (!engine) {
33490
+ engine = new Engine();
33491
+ }
33492
+ return engine.isReady();
33045
33493
  }
33046
33494
 
33047
33495
  class BaseReader {
@@ -33054,10 +33502,18 @@ class BaseReader {
33054
33502
  this.sort = null;
33055
33503
  this.startIndex = null;
33056
33504
  this.count = null;
33505
+ this.loadPromise_ = Promise.resolve();
33506
+ this.cacheEnabled = false;
33507
+ }
33508
+ enableCache(enabled) {
33509
+ this.cacheEnabled = enabled;
33057
33510
  }
33058
33511
  load() {
33059
33512
  throw new Error('not implemented');
33060
33513
  }
33514
+ get isLoaded() {
33515
+ return this.loadPromise_;
33516
+ }
33061
33517
  get properties() {
33062
33518
  throw new Error('not implemented');
33063
33519
  }
@@ -33106,178 +33562,237 @@ class BaseReader {
33106
33562
  }
33107
33563
  }
33108
33564
 
33109
- class BaseCacheReader extends BaseReader {
33110
- constructor(url, cacheActive = true) {
33111
- super(url);
33112
- this.url = url;
33113
- this.cacheActive = cacheActive;
33114
- }
33115
- setCacheActive(value) {
33116
- this.cacheActive = value;
33117
- }
33118
- }
33119
-
33120
- class BaseFileReader extends BaseCacheReader {
33121
- getData() {
33565
+ /**
33566
+ * This reader handles file formats supported natively by DuckDB
33567
+ */
33568
+ class BaseFileReader extends BaseReader {
33569
+ // a table id should not exceed 63 chars
33570
+ generateDatasetId() {
33571
+ // generate a hash out of the url
33572
+ let hash = 0;
33573
+ for (const char of this.url) {
33574
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33575
+ hash = hash >>> 0; // make it unsigned
33576
+ }
33577
+ return `datafetcher_${hash.toString(16)}`;
33578
+ }
33579
+ async getLoadQuery() {
33122
33580
  throw new Error('not implemented');
33123
33581
  }
33124
- load() {
33125
- this.parseResult_ = this.getData();
33582
+ async load() {
33583
+ this.datasetId = this.generateDatasetId();
33584
+ this.loadPromise_ = getEngine()
33585
+ .then((engine) => {
33586
+ this.engine = engine;
33587
+ return this.getLoadQuery();
33588
+ })
33589
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33590
+ .then((datasetInfo) => {
33591
+ this.properties_ = datasetInfo.properties;
33592
+ this.geometryColumn = datasetInfo.geometryColumn;
33593
+ this.rowsCount = datasetInfo.rowsCount;
33594
+ // returns void for the loaded promise
33595
+ });
33126
33596
  }
33127
33597
  get properties() {
33128
- return this.parseResult_.then((result) => result.properties);
33598
+ return this.isLoaded.then(() => this.properties_);
33129
33599
  }
33130
33600
  get info() {
33131
- return this.parseResult_.then((result) => ({
33132
- itemsCount: result.items.length,
33601
+ return this.isLoaded.then(() => ({
33602
+ itemsCount: this.rowsCount,
33603
+ hasGeometry: !!this.geometryColumn,
33133
33604
  }));
33134
33605
  }
33135
33606
  async read() {
33136
- const items = (await this.parseResult_).items;
33137
- // no query defined: return the full results as is
33138
- if (this.groupedBy == null &&
33139
- this.aggregations == null &&
33140
- this.selected == null &&
33141
- this.sort == null &&
33142
- this.filter == null &&
33143
- this.startIndex == null &&
33144
- this.count == null) {
33145
- return items;
33146
- }
33147
- const jsonItems = getJsonDataItemsProxy(items);
33148
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33149
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33150
- return result.map(jsonToGeojsonFeature);
33151
- }
33152
- }
33153
-
33154
- function parseCsv(text) {
33155
- // first parse the header to guess the delimiter
33156
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33157
- let delimiter;
33158
- try {
33159
- const header = text.split('\n')[0];
33160
- const result = Papa.parse(header, {
33161
- header: false,
33162
- });
33163
- delimiter = result.meta.delimiter;
33607
+ await this.isLoaded;
33608
+ // if only certain fields are selected, omit the geometry
33609
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33610
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33611
+ return this.engine.queryItems(query);
33164
33612
  }
33165
- catch (e) {
33166
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33167
- }
33168
- const parsed = Papa.parse(text, {
33169
- header: true,
33170
- skipEmptyLines: true,
33171
- delimiter,
33172
- });
33173
- if (parsed.errors.length) {
33174
- throw new Error('CSV parsing failed for the following reasons:\n' +
33175
- parsed.errors
33176
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33177
- .join('\n'));
33178
- }
33179
- const items = parsed.data.map(jsonToGeojsonFeature);
33180
- return processItemProperties(items, true);
33181
33613
  }
33614
+
33182
33615
  class CsvReader extends BaseFileReader {
33183
- getData() {
33184
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33616
+ async getLoadQuery() {
33617
+ // first we get a list of columns hich have a detected type of VARCHAR
33618
+ // 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
33619
+ return `
33620
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33621
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33622
+ force_not_null = getvariable('strColumns'),
33623
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33624
+ );`;
33185
33625
  }
33186
33626
  }
33187
33627
 
33188
- /**
33189
- * This parser only supports arrays of simple flat objects with properties
33190
- * @param text
33191
- */
33192
- function parseJson(text) {
33193
- const parsed = JSON.parse(text);
33194
- if (!Array.isArray(parsed)) {
33195
- throw new Error('Could not parse JSON, expected an array at root level');
33196
- }
33197
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33198
- }
33199
33628
  class JsonReader extends BaseFileReader {
33200
- getData() {
33201
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33629
+ async getLoadQuery() {
33630
+ return `
33631
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33632
+ maximum_object_size = 536870912 -- 500MB
33633
+ );`;
33202
33634
  }
33203
33635
  }
33204
33636
 
33205
- /**
33206
- * This parser supports both Geojson Feature collections or arrays
33207
- * of Features
33208
- * @param text
33209
- */
33210
- function parseGeojson(text) {
33211
- const parsed = JSON.parse(text);
33212
- const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33213
- if (!Array.isArray(features)) {
33214
- throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33215
- }
33216
- return processItemProperties(features);
33217
- }
33218
33637
  class GeojsonReader extends BaseFileReader {
33219
- getData() {
33220
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33638
+ async getLoadQuery() {
33639
+ return `
33640
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33641
+ allowed_drivers = ['GeoJSON']
33642
+ );`;
33221
33643
  }
33222
33644
  }
33223
33645
 
33646
+ async function inferDatasetType(url, typeHint) {
33647
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33648
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33649
+ ? fileExtensionMatches[1].toLowerCase()
33650
+ : null;
33651
+ // 1. type hint
33652
+ if (typeHint)
33653
+ return Promise.resolve(typeHint);
33654
+ // 2. content-type header
33655
+ const headers = await fetchHeaders(url);
33656
+ if ('supportedType' in headers)
33657
+ return headers.supportedType;
33658
+ // 3. file extension from url
33659
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33660
+ return fileExtension;
33661
+ // no type inferred or hinted
33662
+ if ('mimeType' in headers)
33663
+ throw FetchError.unsupportedType(headers.mimeType);
33664
+ else
33665
+ throw FetchError.unknownType();
33666
+ }
33667
+ function fetchHeaders(url) {
33668
+ return sharedFetch(url, 'HEAD')
33669
+ .catch((error) => {
33670
+ throw FetchError.corsOrNetwork(error.message);
33671
+ })
33672
+ .then((response) => {
33673
+ if (!response.ok) {
33674
+ throw FetchError.http(response.status);
33675
+ }
33676
+ return parseHeaders(response.headers);
33677
+ });
33678
+ }
33679
+ function fetchDataAsText(url, cacheActive) {
33680
+ const fetchFactory = () => sharedFetch(url)
33681
+ .catch((error) => {
33682
+ throw FetchError.corsOrNetwork(error.message);
33683
+ })
33684
+ .then(async (response) => {
33685
+ if (!response.ok) {
33686
+ const clonedResponse = response.clone();
33687
+ throw FetchError.http(response.status, await clonedResponse.text());
33688
+ }
33689
+ const clonedResponse = response.clone();
33690
+ return clonedResponse.text();
33691
+ });
33692
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33693
+ }
33694
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33695
+ const fetchFactory = () => sharedFetch(url)
33696
+ .catch((error) => {
33697
+ throw FetchError.corsOrNetwork(error.message);
33698
+ })
33699
+ .then(async (response) => {
33700
+ if (!response.ok) {
33701
+ throw FetchError.http(response.status, await response.text());
33702
+ }
33703
+ // convert to a numeric array so that we can store the response in cache
33704
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33705
+ });
33706
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33707
+ return new Uint8Array(array).buffer;
33708
+ });
33709
+ }
33224
33710
  /**
33225
- * This will read the first sheet of the excel workbook and expect the first
33226
- * line to contain the properties names
33227
- * @param buffer
33711
+ * This creates a Proxy that allows reading and writing to the data item properties
33712
+ * as if it was a simple array of JSON objects
33713
+ * @param items
33228
33714
  */
33229
- function parseExcel(buffer) {
33230
- return import('xlsx').then(({ read, utils }) => {
33231
- const workbook = read(buffer);
33232
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33233
- let json = utils.sheet_to_json(sheet);
33234
- if (!json.length) {
33235
- json = [];
33236
- }
33237
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33715
+ function getJsonDataItemsProxy(items) {
33716
+ return new Proxy(items, {
33717
+ get(target, p) {
33718
+ if (typeof p === 'string' &&
33719
+ !Number.isNaN(parseInt(p)) &&
33720
+ target[p]?.properties) {
33721
+ return target[p].properties;
33722
+ }
33723
+ return target[p];
33724
+ },
33725
+ set() {
33726
+ throw new Error('This object is read-only');
33727
+ },
33238
33728
  });
33239
33729
  }
33730
+
33240
33731
  class ExcelReader extends BaseFileReader {
33241
- getData() {
33242
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33732
+ async getLoadQuery() {
33733
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33734
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33735
+ const bufferHandle = `B${this.datasetId}`;
33736
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33737
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33738
+ const isXls = Array.from(magicNumber)
33739
+ .map((n) => n.toString(16).toUpperCase())
33740
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33741
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33742
+ if (isXls) {
33743
+ buffer = await import('xlsx').then(({ read, utils }) => {
33744
+ const workbook = read(buffer);
33745
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33746
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33747
+ });
33748
+ }
33749
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33750
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33751
+ return `
33752
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33753
+ }
33754
+ }
33755
+
33756
+ class GmlReader extends BaseFileReader {
33757
+ async getLoadQuery() {
33758
+ return `
33759
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33760
+ allowed_drivers = ['GML']
33761
+ );`;
33243
33762
  }
33244
33763
  }
33245
33764
 
33246
- function parseGml(text, namespace, version) {
33247
- const splittedNamespace = namespace.split(':');
33248
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33765
+ const formatGeojson = new GeoJSON$1();
33766
+ function parseGeojson(text) {
33767
+ const parsed = JSON.parse(text);
33768
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33769
+ if (!Array.isArray(features)) {
33770
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33771
+ }
33772
+ return features;
33773
+ }
33774
+ function parseGml(text, featureType, version) {
33775
+ const parts = featureType.split(':');
33776
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33249
33777
  const match = regex.exec(text);
33250
33778
  if (match && match.length >= 2) {
33251
- const wf = new WFS({
33779
+ const wfs = new WFS({
33252
33780
  featureNS: match[1],
33253
- featureType: splittedNamespace[1],
33781
+ featureType: parts[1],
33254
33782
  version: version,
33255
33783
  });
33256
33784
  let features;
33257
33785
  try {
33258
- features = wf.readFeatures(text);
33786
+ features = wfs.readFeatures(text);
33259
33787
  }
33260
33788
  catch (e) {
33261
- throw Error("Couldn't parse WFS with GML features");
33789
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33262
33790
  }
33263
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33264
- return processItemProperties(geojsonItem.features, true);
33791
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33792
+ return geojsonItem.features;
33265
33793
  }
33266
33794
  throw Error("Couldn't retrieve namespace url");
33267
33795
  }
33268
- class GmlReader extends BaseFileReader {
33269
- constructor(url, namespace, version, cacheActive = true) {
33270
- super(url);
33271
- this.url = url;
33272
- this.namespace = namespace;
33273
- this.version = version;
33274
- this.cacheActive = cacheActive;
33275
- }
33276
- getData() {
33277
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33278
- }
33279
- }
33280
-
33281
33796
  async function getWfsEndpoint(wfsUrl) {
33282
33797
  try {
33283
33798
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33305,21 +33820,65 @@ async function getWfsEndpoint(wfsUrl) {
33305
33820
  }
33306
33821
  }
33307
33822
  }
33308
- class WfsReader extends BaseCacheReader {
33309
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33310
- super(url, cacheActive);
33311
- this.endpoint = wfsEndpoint;
33312
- this.featureTypeName = featureTypeName;
33313
- this.version = this.endpoint.getVersion();
33823
+ class WfsReader extends BaseReader {
33824
+ constructor(url, featureTypeName) {
33825
+ super(url);
33826
+ this.endpoint = getWfsEndpoint(url);
33827
+ this.featureType = this.endpoint
33828
+ .then((endpoint) => {
33829
+ const featureTypes = endpoint.getFeatureTypes();
33830
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33831
+ ? featureTypes[0].name
33832
+ : featureTypeName);
33833
+ })
33834
+ .then((featureType) => {
33835
+ if (!featureType) {
33836
+ throw new Error('wfs.featuretype.notfound');
33837
+ }
33838
+ return featureType;
33839
+ });
33840
+ }
33841
+ get backupReader() {
33842
+ if (this.backupReader_) {
33843
+ return this.backupReader_;
33844
+ }
33845
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33846
+ let reader;
33847
+ if (endpoint.supportsJson(featureType.name)) {
33848
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33849
+ asJson: true,
33850
+ outputCrs: 'EPSG:4326',
33851
+ }));
33852
+ }
33853
+ else {
33854
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33855
+ (featureType.defaultCrs === 'EPSG:4326' ||
33856
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33857
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33858
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33859
+ outputCrs: 'EPSG:4326',
33860
+ }));
33861
+ }
33862
+ }
33863
+ reader.enableCache(this.cacheEnabled);
33864
+ reader.load();
33865
+ return reader;
33866
+ });
33867
+ return this.backupReader_;
33314
33868
  }
33315
33869
  get properties() {
33316
- return this.endpoint
33317
- .getFeatureTypeFull(this.featureTypeName)
33318
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33870
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33319
33871
  const originalType = featureType.properties[prop];
33320
- const type = originalType === 'float' || originalType === 'integer'
33321
- ? 'number'
33322
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33872
+ let type;
33873
+ if (originalType === 'float' || originalType === 'integer') {
33874
+ type = 'number';
33875
+ }
33876
+ else if (originalType === 'boolean') {
33877
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33878
+ }
33879
+ else {
33880
+ type = originalType;
33881
+ }
33323
33882
  return {
33324
33883
  name: prop,
33325
33884
  label: prop,
@@ -33328,83 +33887,63 @@ class WfsReader extends BaseCacheReader {
33328
33887
  }));
33329
33888
  }
33330
33889
  get info() {
33331
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33890
+ return this.featureType.then((result) => ({
33332
33891
  itemsCount: result.objectCount,
33892
+ hasGeometry: !!result.geometryName,
33333
33893
  }));
33334
33894
  }
33335
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33336
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33337
- const featureTypes = wfsEndpoint.getFeatureTypes();
33338
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33339
- ? featureTypes[0].name
33340
- : featureTypeName);
33341
- if (!featureType) {
33342
- throw new Error('wfs.featuretype.notfound');
33343
- }
33344
- if (wfsEndpoint.supportsStartIndex()) {
33345
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33346
- }
33347
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33348
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33349
- asJson: true,
33350
- outputCrs: 'EPSG:4326',
33351
- }));
33352
- }
33353
- else {
33354
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33355
- (featureType.defaultCrs === 'EPSG:4326' ||
33356
- featureType.otherCrs?.includes('EPSG:4326'))) {
33357
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33358
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33359
- outputCrs: 'EPSG:4326',
33360
- }), featureType.name, wfsEndpoint.getVersion());
33361
- }
33362
- throw new Error('wfs.geojsongml.notsupported');
33363
- }
33895
+ load() {
33896
+ // Nothing to load for Wfs
33364
33897
  }
33365
- async getData(aggregation, groupedBy) {
33366
- if (aggregation || groupedBy) {
33367
- return { items: await this.getQueryData() };
33898
+ async read() {
33899
+ const endpoint = await this.endpoint;
33900
+ const featureType = await this.featureType;
33901
+ // if we can't use the WFS protocol we fall back to the backup reader
33902
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33903
+ const backupReader = await this.backupReader;
33904
+ backupReader.selectAll();
33905
+ if (this.selected) {
33906
+ backupReader.select(...this.selected);
33907
+ }
33908
+ if (this.aggregations) {
33909
+ backupReader.aggregate(...this.aggregations);
33910
+ }
33911
+ if (this.groupedBy) {
33912
+ backupReader.groupBy(...this.groupedBy);
33913
+ }
33914
+ if (this.sort) {
33915
+ backupReader.orderBy(...this.sort);
33916
+ }
33917
+ if (this.startIndex !== null && this.count !== null) {
33918
+ backupReader.limit(this.startIndex, this.count);
33919
+ }
33920
+ return backupReader.read();
33368
33921
  }
33369
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33922
+ const asJson = endpoint.supportsJson(featureType.name);
33370
33923
  const attributes = this.selected ?? undefined;
33371
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33924
+ let sortBy = null;
33925
+ if (this.sort) {
33926
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33927
+ sortBy = Array.isArray(this.sort[0])
33928
+ ? this.sort.map(mapSort)
33929
+ : mapSort(this.sort);
33930
+ }
33931
+ const url = endpoint.getFeatureUrl(featureType.name, {
33372
33932
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33373
33933
  ...(this.count !== null && { maxFeatures: this.count }),
33374
33934
  asJson,
33375
33935
  outputCrs: 'EPSG:4326',
33376
33936
  attributes,
33377
- // sortBy: this.sort // TODO: no sort in ogc-client?
33937
+ sortBy,
33378
33938
  });
33379
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33380
- const finalUrl = new URL(url);
33381
- const sorts = this.sort
33382
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33383
- .join(',');
33384
- // Direct update on string url to prevent encoding of +A and +D
33385
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33386
- }
33387
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33939
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33388
33940
  ? parseGeojson(text)
33389
- : parseGml(text, this.featureTypeName, this.version));
33390
- }
33391
- async getQueryData() {
33392
- const items = (await this.getData()).items;
33393
- const jsonItems = getJsonDataItemsProxy(items);
33394
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33395
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33396
- return result.map(jsonToGeojsonFeature);
33397
- }
33398
- load() {
33399
- // Nothing to load for Wfs
33400
- }
33401
- async read() {
33402
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33941
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33403
33942
  }
33404
33943
  }
33405
33944
 
33406
- async function openDataset(url, typeHint, options, cacheActive) {
33407
- const fileType = await inferDatasetType(url, typeHint);
33945
+ async function openDataset(url, options) {
33946
+ const fileType = await inferDatasetType(url, options?.typeHint);
33408
33947
  let reader;
33409
33948
  try {
33410
33949
  switch (fileType) {
@@ -33421,18 +33960,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33421
33960
  reader = new ExcelReader(url);
33422
33961
  break;
33423
33962
  case 'gml':
33424
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33963
+ reader = new GmlReader(url);
33425
33964
  break;
33426
33965
  case 'wfs':
33427
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33966
+ reader = new WfsReader(url, options?.wfsFeatureType);
33428
33967
  break;
33429
33968
  }
33430
- reader.setCacheActive(cacheActive);
33969
+ reader.enableCache(options?.enableCache ?? true);
33431
33970
  reader.load();
33432
33971
  return reader;
33433
33972
  }
33434
33973
  catch (e) {
33435
- //WfsReader may already raise a FetchError
33974
+ // WfsReader may already raise a FetchError
33436
33975
  if (e instanceof FetchError)
33437
33976
  throw e;
33438
33977
  else
@@ -33443,13 +33982,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33443
33982
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33444
33983
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33445
33984
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33446
- * File type is determined liked so:
33985
+ * File type is determined like so:
33447
33986
  * 1. if a type hint is given, use it
33448
33987
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33449
33988
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33450
33989
  */
33451
- async function readDataset(url, typeHint, options, cacheActive = true) {
33452
- const reader = await openDataset(url, typeHint, options, cacheActive);
33990
+ async function readDataset(url, options) {
33991
+ const reader = await openDataset(url, options);
33453
33992
  try {
33454
33993
  return await reader.read();
33455
33994
  }
@@ -33680,9 +34219,11 @@ class DataService {
33680
34219
  getDataset(link, cacheActive) {
33681
34220
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33682
34221
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33683
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34222
+ return from(openDataset(wfsUrlEndpoint, {
34223
+ typeHint: 'wfs',
33684
34224
  wfsFeatureType: link.name,
33685
- }, cacheActive));
34225
+ enableCache: cacheActive,
34226
+ }));
33686
34227
  }
33687
34228
  else if (link.type === 'download') {
33688
34229
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33690,12 +34231,15 @@ class DataService {
33690
34231
  const supportedType = SupportedTypes.indexOf(format) > -1
33691
34232
  ? format
33692
34233
  : undefined;
33693
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34234
+ return from(openDataset(linkProxifiedUrl, {
34235
+ typeHint: supportedType,
34236
+ enableCache: cacheActive,
34237
+ })).pipe();
33694
34238
  }
33695
34239
  else if (link.type === 'service' &&
33696
34240
  link.accessServiceProtocol === 'esriRest') {
33697
34241
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33698
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34242
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33699
34243
  }
33700
34244
  else if (link.type === 'service' &&
33701
34245
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33709,7 +34253,10 @@ class DataService {
33709
34253
  }
33710
34254
  const urlWithoutLimit = new URL(geojsonUrl);
33711
34255
  urlWithoutLimit.searchParams.delete('limit');
33712
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34256
+ return openDataset(urlWithoutLimit.toString(), {
34257
+ typeHint: 'geojson',
34258
+ enableCache: cacheActive,
34259
+ });
33713
34260
  }));
33714
34261
  }
33715
34262
  return throwError(() => 'protocol not supported');
@@ -33961,20 +34508,31 @@ class DataTableComponent {
33961
34508
  this.eltRef = inject(ElementRef);
33962
34509
  this.cdr = inject(ChangeDetectorRef);
33963
34510
  this.translateService = inject(TranslateService);
33964
- this._featureAttributes = [];
34511
+ this.columnsFromFeatureCatalog = null;
34512
+ this.columnsFromDataset = [];
33965
34513
  this.selected = new EventEmitter();
33966
- this.properties$ = new BehaviorSubject(null);
33967
34514
  this.loading$ = new BehaviorSubject(false);
33968
34515
  this.error = null;
33969
34516
  }
33970
34517
  set featureAttributes(value) {
33971
- this._featureAttributes = value;
33972
- this.properties$.next(value.map((attr) => attr.value));
34518
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34519
+ name: attrs.value,
34520
+ label: attrs.label,
34521
+ }));
33973
34522
  }
33974
34523
  set dataset(value) {
33975
34524
  this.dataset_ = value;
33976
34525
  this.dataset_.load();
33977
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34526
+ this.dataset_.info.then((info) => {
34527
+ this.count = info.itemsCount;
34528
+ this.cdr.detectChanges();
34529
+ });
34530
+ }
34531
+ get columns() {
34532
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34533
+ }
34534
+ get columnNames() {
34535
+ return this.columns.map((c) => c.name);
33978
34536
  }
33979
34537
  ngOnInit() {
33980
34538
  this.dataSource = new DataTableDataSource();
@@ -34009,11 +34567,12 @@ class DataTableComponent {
34009
34567
  }
34010
34568
  async readData() {
34011
34569
  this.loading$.next(true);
34012
- // wait for properties to be read
34013
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34014
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34015
- this.dataset_.select(...propsWithoutGeom);
34016
34570
  try {
34571
+ // wait for properties to be read
34572
+ if (!this.columnsFromFeatureCatalog) {
34573
+ this.columnsFromDataset = await this.dataset_.properties;
34574
+ }
34575
+ this.dataset_.select(...this.columnNames);
34017
34576
  await this.dataSource.showData(this.dataset_.read());
34018
34577
  this.error = null;
34019
34578
  }
@@ -34043,7 +34602,7 @@ class DataTableComponent {
34043
34602
  }
34044
34603
  }
34045
34604
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34046
- 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 }); }
34605
+ 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 }); }
34047
34606
  }
34048
34607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34049
34608
  type: Component,
@@ -34055,10 +34614,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34055
34614
  CommonModule,
34056
34615
  LoadingMaskComponent,
34057
34616
  PopupAlertComponent,
34058
- LetDirective,
34059
34617
  TranslatePipe,
34060
34618
  TranslateDirective,
34061
- ], 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"] }]
34619
+ ], 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"] }]
34062
34620
  }], propDecorators: { featureAttributes: [{
34063
34621
  type: Input
34064
34622
  }], dataset: [{
@@ -34161,7 +34719,7 @@ class ChartViewComponent {
34161
34719
  }), shareReplay$1(1));
34162
34720
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34163
34721
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34164
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34722
+ .filter((prop) => prop.type === 'number')
34165
34723
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34166
34724
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34167
34725
  const newProp = choices[0]?.value || '';
@@ -34188,6 +34746,7 @@ class ChartViewComponent {
34188
34746
  this.aggregation$,
34189
34747
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34190
34748
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34749
+ this.loading = true;
34191
34750
  return dataset
34192
34751
  .groupBy(['distinct', xProp])
34193
34752
  .aggregate(fieldAgg)
@@ -34429,6 +34988,7 @@ class GeoTableViewComponent {
34429
34988
  }));
34430
34989
  }
34431
34990
  async initMapContext() {
34991
+ this.dataset.load();
34432
34992
  this.dataset.selectAll();
34433
34993
  return {
34434
34994
  layers: [
@@ -34602,8 +35162,7 @@ class MdViewFacade {
34602
35162
  }));
34603
35163
  this.error$ = this.store.pipe(select(getMetadataError));
34604
35164
  this.related$ = this.store.pipe(select(getRelated));
34605
- this.sources$ = this.store.pipe(select(getSources));
34606
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35165
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34607
35166
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34608
35167
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34609
35168
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34722,12 +35281,9 @@ class MdViewEffects {
34722
35281
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34723
35282
  return setRelated({ related });
34724
35283
  }), catchError(() => of(setRelated({ related: null })))));
34725
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34726
- return setSources({ sources });
34727
- }), catchError(() => of(setSources({ sources: null })))));
34728
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34729
- return setSourceOf({ sourceOf });
34730
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
35284
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35285
+ return setLinkedRecords({ linkedRecords });
35286
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34731
35287
  /*
34732
35288
  UserFeedback effects
34733
35289
  */
@@ -39946,7 +40502,7 @@ class RouterService {
39946
40502
  return ROUTER_ROUTE_ORGANIZATION;
39947
40503
  }
39948
40504
  getDefaultSort() {
39949
- return SortByEnum.RESOURCE_DATES;
40505
+ return SortByEnum.RESOURCE_DATE;
39950
40506
  }
39951
40507
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39952
40508
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, providedIn: 'root' }); }
@@ -40006,7 +40562,7 @@ function flattenQueryParams(params) {
40006
40562
  const start = flattened[key].start;
40007
40563
  const end = flattened[key].end;
40008
40564
  flattened[key] = [
40009
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40565
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40010
40566
  ];
40011
40567
  }
40012
40568
  }
@@ -40363,5 +40919,5 @@ const CHART_TYPE_VALUES = [
40363
40919
  * Generated bundle index. Do not edit.
40364
40920
  */
40365
40921
 
40366
- 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 };
40922
+ export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AutofocusDirective, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, BoundingBoxSearchField, 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, SpatialExtentDropdownComponent, 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, isBoundingBox, isConfigLoaded, isDateRange, isFileExtensionValid, 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, provideLocalizedDateAdapter, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readFileAsText, 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 };
40367
40923
  //# sourceMappingURL=geonetwork-ui.mjs.map