geonetwork-ui 2.11.0-dev.e7b8ec331 → 2.11.0-dev.f5ee66b24

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 (91) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1482 -928
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +280 -124
  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/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +108 -34
  8. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  9. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  10. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  11. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  12. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  13. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  14. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  15. package/src/libs/feature/map/src/index.ts +0 -1
  16. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  17. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  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/state/query-params.utils.ts +1 -1
  25. package/src/libs/feature/search/src/index.ts +2 -0
  26. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  27. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  28. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +5 -1
  29. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  30. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  31. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  32. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  33. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  34. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  35. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  36. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  37. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  38. package/src/libs/ui/inputs/src/index.ts +2 -0
  39. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  40. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  41. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  42. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  43. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  44. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  45. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  46. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  47. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  48. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  49. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  50. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  51. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  52. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  53. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  54. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
  55. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  56. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  57. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  58. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  59. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  60. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  61. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  62. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  63. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  64. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  65. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  66. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  67. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  68. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  69. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  70. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  71. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  72. package/src/libs/util/shared/src/index.ts +1 -0
  73. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  74. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  75. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  76. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  77. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  78. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  79. package/tailwind.base.css +5 -0
  80. package/translations/de.json +17 -3
  81. package/translations/en.json +16 -2
  82. package/translations/es.json +15 -1
  83. package/translations/fr.json +17 -3
  84. package/translations/it.json +15 -1
  85. package/translations/nl.json +15 -1
  86. package/translations/pt.json +15 -1
  87. package/translations/sk.json +15 -1
  88. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  89. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  90. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  91. /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
@@ -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, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1, takeUntil } 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';
@@ -26,7 +26,6 @@ import { getLength } from 'ol/sphere.js';
26
26
  import { LineString } from 'ol/geom.js';
27
27
  import * as i1 from '@ngrx/store';
28
28
  import { createAction, props, createReducer, on, createFeatureSelector, createSelector, Store, select, StoreModule } from '@ngrx/store';
29
- import { queryDataGouvFr, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
30
29
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
31
30
  import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, MapExtentChangeEventType, SourceLoadErrorType, computeMapContextDiff, createViewFromLayer } from '@geospatial-sdk/core';
32
31
  import { createMapFromContext, applyContextDiffToMap, listen } from '@geospatial-sdk/openlayers';
@@ -41,10 +40,10 @@ import * as TOML from '@ltd/j-toml';
41
40
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
41
  import CircleStyle from 'ol/style/Circle.js';
43
42
  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';
43
+ 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
44
  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';
45
+ import * as i1$7 from '@angular/cdk/overlay';
46
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
47
  import * as i1$3 from '@angular/forms';
49
48
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
49
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +51,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
51
  import tippy from 'tippy.js';
53
52
  import * as i2 from '@angular/material/progress-spinner';
54
53
  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';
54
+ 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
55
  import * as i1$4 from '@angular/material/checkbox';
57
56
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
57
  import * as i1$5 from '@angular/material/tooltip';
59
58
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
59
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
60
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
61
+ import { enUS } from 'date-fns/locale/en-US';
61
62
  import * as i1$6 from '@angular/material/datepicker';
62
63
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
64
+ import * as i1$8 from 'ngx-dropzone';
65
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
66
  import * as i1$9 from '@angular/material/button-toggle';
66
67
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
68
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -72,6 +73,7 @@ import { MatTabsModule } from '@angular/material/tabs';
72
73
  import * as i2$2 from '@ngrx/effects';
73
74
  import { Actions, createEffect, ofType, EffectsModule } from '@ngrx/effects';
74
75
  import { valid as valid$1 } from 'geojson-validation';
76
+ import { queryDataGouvFr, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
75
77
  import { TemplatePortal } from '@angular/cdk/portal';
76
78
  import { parse as parse$3 } from 'marked';
77
79
  import * as i2$3 from '@angular/cdk/scrolling';
@@ -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) {
@@ -20225,7 +20264,7 @@ function getLinkLabel(link) {
20225
20264
  const label = link.description || ('name' in link ? link.name : '');
20226
20265
  return format ? `${label} (${format})` : label;
20227
20266
  }
20228
- async function getLayers(url, serviceProtocol) {
20267
+ async function getLayers(url, serviceProtocol, deep = false) {
20229
20268
  switch (serviceProtocol) {
20230
20269
  case 'ogcFeatures': {
20231
20270
  const layers = await new OgcApiEndpoint(url).allCollections;
@@ -20233,7 +20272,10 @@ async function getLayers(url, serviceProtocol) {
20233
20272
  }
20234
20273
  case 'wfs': {
20235
20274
  const endpointWfs = await new WfsEndpoint(url).isReady();
20236
- const featureTypes = await endpointWfs.getFeatureTypes();
20275
+ const featureTypes = endpointWfs.getFeatureTypes();
20276
+ if (!deep) {
20277
+ return featureTypes;
20278
+ }
20237
20279
  const layers = (await Promise.allSettled(featureTypes.map((collection) => {
20238
20280
  return endpointWfs.getFeatureTypeFull(collection.name);
20239
20281
  })))
@@ -20243,12 +20285,14 @@ async function getLayers(url, serviceProtocol) {
20243
20285
  }
20244
20286
  case 'wms': {
20245
20287
  const endpointWms = await new WmsEndpoint(url).isReady();
20246
- const layers = (await endpointWms
20288
+ const layersSummary = endpointWms
20247
20289
  .getLayers()
20248
20290
  .flatMap(wmsLayerFlatten)
20249
- .filter((l) => l.name)).map((collection) => {
20250
- return endpointWms.getLayerByName(collection.name);
20251
- });
20291
+ .filter((l) => l.name);
20292
+ if (!deep) {
20293
+ return layersSummary;
20294
+ }
20295
+ const layers = layersSummary.map((collection) => endpointWms.getLayerByName(collection.name));
20252
20296
  return layers;
20253
20297
  }
20254
20298
  case 'wmts': {
@@ -20342,6 +20386,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20342
20386
  }]
20343
20387
  }] });
20344
20388
 
20389
+ class AutofocusDirective {
20390
+ constructor() {
20391
+ this.el = inject(ElementRef);
20392
+ this.injector = inject(Injector);
20393
+ }
20394
+ set autofocus(active) {
20395
+ if (!active)
20396
+ return;
20397
+ afterNextRender(() => this.el.nativeElement.focus(), {
20398
+ injector: this.injector,
20399
+ });
20400
+ }
20401
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20402
+ 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 }); }
20403
+ }
20404
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20405
+ type: Directive,
20406
+ args: [{
20407
+ selector: '[gnUiAutofocus]',
20408
+ standalone: true,
20409
+ }]
20410
+ }], propDecorators: { autofocus: [{
20411
+ type: Input,
20412
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20413
+ }] } });
20414
+
20345
20415
  class ImageFallbackDirective {
20346
20416
  constructor() {
20347
20417
  this.el = inject(ElementRef);
@@ -20369,7 +20439,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20369
20439
  }] } });
20370
20440
 
20371
20441
  var name = "geonetwork-ui";
20372
- var version = "2.11.0-dev.e7b8ec331";
20442
+ var version = "2.11.0-dev.f5ee66b24";
20373
20443
  var engines = {
20374
20444
  node: ">=24"
20375
20445
  };
@@ -20390,6 +20460,7 @@ var peerDependencies = {
20390
20460
  "@angular/core": "19.x || 20.x || 21.x",
20391
20461
  "@angular/forms": "19.x || 20.x || 21.x",
20392
20462
  "@angular/material": "19.x || 20.x || 21.x",
20463
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20393
20464
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20394
20465
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20395
20466
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20399,20 +20470,21 @@ var peerDependencies = {
20399
20470
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20471
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20472
  "@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",
20473
+ "@ngx-translate/core": "17.x",
20474
+ "@ngx-translate/http-loader": "17.x",
20475
+ "flag-icons": "~7.5.0",
20405
20476
  rxjs: "7.x",
20406
20477
  "zone.js": "*",
20407
20478
  tailwindcss: "3.x"
20408
20479
  };
20409
20480
  var dependencies = {
20410
20481
  "@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",
20482
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20483
+ "@duckdb/duckdb-wasm": "~1.32.0",
20484
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20485
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20486
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20487
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20488
  "@ltd/j-toml": "~1.38.0",
20417
20489
  "@messageformat/core": "~3.4.0",
20418
20490
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20492,7 @@ var dependencies = {
20420
20492
  "@ng-icons/material-icons": "~32.5.0",
20421
20493
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20494
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20495
+ "apache-arrow": "~17.0.0",
20424
20496
  "chart.js": "~4.5.1",
20425
20497
  "chroma-js": "~3.2.0",
20426
20498
  "date-fns": "4.1.0",
@@ -20433,7 +20505,6 @@ var dependencies = {
20433
20505
  "ngx-dropzone": "~3.1.0",
20434
20506
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20507
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20508
  proj4: "~2.20.4",
20438
20509
  rdflib: "~2.3.5",
20439
20510
  semver: "~7.7.4",
@@ -20631,8 +20702,8 @@ class ElasticsearchService {
20631
20702
  if (!payload.runtime_mappings)
20632
20703
  payload.runtime_mappings = {};
20633
20704
  payload.runtime_mappings[fieldName] = {
20634
- type: 'keyword',
20635
- script: this.runtimeFields[fieldName],
20705
+ type: this.runtimeFields[fieldName].type,
20706
+ script: this.runtimeFields[fieldName].script,
20636
20707
  };
20637
20708
  };
20638
20709
  const lookForField = (node) => {
@@ -20652,6 +20723,12 @@ class ElasticsearchService {
20652
20723
  (node[runtimeField] === 'asc' || node[runtimeField] === 'desc')) {
20653
20724
  addMapping(runtimeField);
20654
20725
  }
20726
+ if (runtimeField in node &&
20727
+ typeof node[runtimeField] === 'object' &&
20728
+ node[runtimeField] !== null &&
20729
+ ('gte' in node[runtimeField] || 'lte' in node[runtimeField])) {
20730
+ addMapping(runtimeField);
20731
+ }
20655
20732
  if ('query' in node &&
20656
20733
  typeof node.query === 'string' &&
20657
20734
  node.query.indexOf(runtimeField + ':') > -1) {
@@ -20668,8 +20745,8 @@ class ElasticsearchService {
20668
20745
  lookForField(payload.query);
20669
20746
  return payload;
20670
20747
  }
20671
- registerRuntimeField(fieldName, expression) {
20672
- this.runtimeFields[fieldName] = expression;
20748
+ registerRuntimeField(fieldName, expression, type = 'keyword') {
20749
+ this.runtimeFields[fieldName] = { script: expression, type };
20673
20750
  }
20674
20751
  getMetadataByIdsPayload(uuids) {
20675
20752
  return {
@@ -20678,6 +20755,7 @@ class ElasticsearchService {
20678
20755
  values: uuids,
20679
20756
  },
20680
20757
  },
20758
+ size: uuids.length,
20681
20759
  };
20682
20760
  }
20683
20761
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20785,7 +20863,49 @@ class ElasticsearchService {
20785
20863
  isCurrentSearchLang() {
20786
20864
  return this.metadataLang === 'current';
20787
20865
  }
20788
- filtersToQuery(filters) {
20866
+ findSpatialFilterExtent(filters) {
20867
+ if (typeof filters === 'string') {
20868
+ return undefined;
20869
+ }
20870
+ return Object.values(filters).find(isBoundingBox);
20871
+ }
20872
+ // if a field registered min/max runtime fields, its dates form an interval
20873
+ // the record matches if that interval intersects the filter range
20874
+ buildDateRangeQuery(searchField, dateRange) {
20875
+ const minField = `${searchField}Min`;
20876
+ const maxField = `${searchField}Max`;
20877
+ if (minField in this.runtimeFields && maxField in this.runtimeFields) {
20878
+ const filter = [
20879
+ dateRange.start && {
20880
+ range: {
20881
+ [maxField]: {
20882
+ gte: formatDate(dateRange.start),
20883
+ format: 'yyyy-MM-dd',
20884
+ },
20885
+ },
20886
+ },
20887
+ dateRange.end && {
20888
+ range: {
20889
+ [minField]: {
20890
+ lte: formatDate(dateRange.end),
20891
+ format: 'yyyy-MM-dd',
20892
+ },
20893
+ },
20894
+ },
20895
+ ].filter(Boolean);
20896
+ return { bool: { filter } };
20897
+ }
20898
+ return {
20899
+ range: {
20900
+ [searchField]: {
20901
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
20902
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
20903
+ format: 'yyyy-MM-dd',
20904
+ },
20905
+ },
20906
+ };
20907
+ }
20908
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20789
20909
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20790
20910
  const makeQuery = (filter) => {
20791
20911
  if (typeof filter === 'string') {
@@ -20804,7 +20924,9 @@ class ElasticsearchService {
20804
20924
  ? filters
20805
20925
  : Object.keys(filters)
20806
20926
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20927
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20807
20928
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20929
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20808
20930
  .filter((fieldname) => filters[fieldname] &&
20809
20931
  JSON.stringify(filters[fieldname]) !== '{}')
20810
20932
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20812,31 +20934,26 @@ class ElasticsearchService {
20812
20934
  if (filters['gn-ui-crossFieldFilter']) {
20813
20935
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`;
20814
20936
  }
20815
- const queryRange = Object.entries(filters)
20816
- .filter(([, value]) => isDateRange(value))
20817
- .map(([searchField, dateRange]) => {
20818
- return {
20819
- searchField,
20820
- dateRange,
20821
- };
20822
- })[0];
20937
+ const queryRanges = Object.entries(filters).filter(([, value]) => isDateRange(value));
20823
20938
  const queryParts = [
20824
20939
  queryString && {
20825
20940
  query_string: {
20826
20941
  query: queryString,
20827
20942
  },
20828
20943
  },
20829
- queryRange &&
20830
- queryRange.dateRange && {
20831
- range: {
20832
- [queryRange.searchField]: {
20833
- ...(queryRange.dateRange.start && {
20834
- gte: formatDate(queryRange.dateRange.start),
20835
- }),
20836
- ...(queryRange.dateRange.end && {
20837
- lte: formatDate(queryRange.dateRange.end),
20838
- }),
20839
- format: 'yyyy-MM-dd',
20944
+ ...queryRanges.map(([searchField, dateRange]) => this.buildDateRangeQuery(searchField, dateRange)),
20945
+ spatialFilterExtent && {
20946
+ geo_shape: {
20947
+ geom: {
20948
+ shape: {
20949
+ type: 'envelope',
20950
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20951
+ coordinates: [
20952
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20953
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20954
+ ],
20955
+ },
20956
+ relation: 'intersects',
20840
20957
  },
20841
20958
  },
20842
20959
  },
@@ -20866,7 +20983,9 @@ class ElasticsearchService {
20866
20983
  },
20867
20984
  });
20868
20985
  }
20869
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20986
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20987
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20988
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20870
20989
  if (queryFilters) {
20871
20990
  filter.push(...queryFilters);
20872
20991
  }
@@ -20877,7 +20996,10 @@ class ElasticsearchService {
20877
20996
  },
20878
20997
  });
20879
20998
  }
20880
- if (geometry) {
20999
+ const boostGeometry = spatialFilterExtent
21000
+ ? bboxToPolygon(spatialFilterExtent)
21001
+ : geometry;
21002
+ if (boostGeometry) {
20881
21003
  // boosts applied using the filter geometry:
20882
21004
  // * records completely within the geometry receive a boost of 5
20883
21005
  // * records intersecting the geometry receive a boost of 2
@@ -20886,7 +21008,7 @@ class ElasticsearchService {
20886
21008
  should.push({
20887
21009
  geo_shape: {
20888
21010
  geom: {
20889
- shape: geometry,
21011
+ shape: boostGeometry,
20890
21012
  relation: 'within',
20891
21013
  },
20892
21014
  boost: 5.0,
@@ -20894,7 +21016,7 @@ class ElasticsearchService {
20894
21016
  }, {
20895
21017
  geo_shape: {
20896
21018
  geom: {
20897
- shape: geometry,
21019
+ shape: boostGeometry,
20898
21020
  relation: 'intersects',
20899
21021
  },
20900
21022
  boost: 2.0,
@@ -20903,7 +21025,7 @@ class ElasticsearchService {
20903
21025
  // this will boost the results variably depending on their distance from the given geometry
20904
21026
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20905
21027
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20906
- const bbox = getGeometryBoundingBox(geometry);
21028
+ const bbox = getGeometryBoundingBox(boostGeometry);
20907
21029
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20908
21030
  const northToCenter = new LineString([
20909
21031
  [center[0], bbox[3]],
@@ -21229,7 +21351,7 @@ class Gn4Repository {
21229
21351
  }
21230
21352
  getRecord(uniqueIdentifier) {
21231
21353
  return this.gn4SearchApi
21232
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21354
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21233
21355
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21234
21356
  }
21235
21357
  getMultipleRecords(uniqueIdentifiers) {
@@ -21283,19 +21405,28 @@ class Gn4Repository {
21283
21405
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21284
21406
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21285
21407
  }
21286
- getSources(record) {
21287
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21288
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21289
- return this.getMultipleRecords(sourcesIdentifiers);
21290
- }
21291
- return of(null);
21292
- }
21293
- getSourceOf(record) {
21294
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21295
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21296
- return this.getMultipleRecords(sourceOfIdentifiers);
21408
+ getLinkedRecords(record) {
21409
+ const siblings = (record.extras?.['siblings'] ?? []);
21410
+ const relations = [
21411
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21412
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21413
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21414
+ [
21415
+ 'associated',
21416
+ (record.extras?.['associatedIdentifiers'] ?? []),
21417
+ ],
21418
+ ];
21419
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21420
+ if (requested.length === 0) {
21421
+ return of([]);
21297
21422
  }
21298
- return of(null);
21423
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21424
+ record,
21425
+ relation,
21426
+ associationType: relation === 'sibling'
21427
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21428
+ : undefined,
21429
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21299
21430
  }
21300
21431
  aggregate(params) {
21301
21432
  // if aggregations are empty, return an empty object right away
@@ -22484,48 +22615,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22484
22615
  type: Injectable
22485
22616
  }] });
22486
22617
 
22487
- const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider');
22488
- class GeocodingService {
22489
- constructor() {
22490
- this.provider = inject(GEOCODING_PROVIDER);
22491
- }
22492
- query(text) {
22493
- let queryObservable;
22494
- switch (this.provider[0]) {
22495
- case 'geoadmin':
22496
- queryObservable = from(queryGeoadmin(text, this.provider[1]));
22497
- break;
22498
- case 'geonames':
22499
- queryObservable = from(queryGeonames(text, this.provider[1]));
22500
- break;
22501
- case 'data-gouv-fr':
22502
- queryObservable = from(queryDataGouvFr(text, this.provider[1]));
22503
- break;
22504
- default:
22505
- return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
22506
- }
22507
- return queryObservable.pipe(catchError((error) => throwError(error)));
22508
- }
22509
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22510
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
22511
- }
22512
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
22513
- type: Injectable,
22514
- args: [{
22515
- providedIn: 'root',
22516
- }]
22517
- }] });
22518
-
22519
22618
  class FeatureMapModule {
22520
22619
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
22521
22620
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, imports: [i1.StoreFeatureModule] }); }
22522
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [
22523
- MapFacade,
22524
- {
22525
- provide: GEOCODING_PROVIDER,
22526
- useValue: ['geonames', { maxRows: 5 }],
22527
- },
22528
- ], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22621
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [MapFacade], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22529
22622
  }
22530
22623
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, decorators: [{
22531
22624
  type: NgModule,
@@ -22533,13 +22626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22533
22626
  imports: [
22534
22627
  StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer),
22535
22628
  ],
22536
- providers: [
22537
- MapFacade,
22538
- {
22539
- provide: GEOCODING_PROVIDER,
22540
- useValue: ['geonames', { maxRows: 5 }],
22541
- },
22542
- ],
22629
+ providers: [MapFacade],
22543
22630
  }]
22544
22631
  }] });
22545
22632
 
@@ -22556,9 +22643,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22556
22643
  });
22557
22644
 
22558
22645
  const DEFAULT_BASEMAP_LAYER = {
22559
- type: 'xyz',
22560
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22561
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22646
+ type: 'maplibre-style',
22647
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22648
+ clickable: false,
22649
+ hoverable: false,
22562
22650
  };
22563
22651
  const DEFAULT_VIEW = {
22564
22652
  center: [0, 15],
@@ -23273,6 +23361,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23273
23361
  'search_preset',
23274
23362
  'advanced_filters',
23275
23363
  'limit',
23364
+ 'spatial_extent_max_file_size',
23276
23365
  ], warnings, errors);
23277
23366
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23278
23367
  searchConfig =
@@ -23290,6 +23379,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23290
23379
  })),
23291
23380
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23292
23381
  LIMIT: parsedSearchSection.limit,
23382
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23293
23383
  };
23294
23384
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23295
23385
  metadataQualityConfig =
@@ -24347,7 +24437,7 @@ class AutocompleteComponent {
24347
24437
  this.enterBtnPosition = event.target['value'].length * 8 + 80;
24348
24438
  }
24349
24439
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24350
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24440
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithTemplate: "displayWithTemplate", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24351
24441
  provideIcons({
24352
24442
  iconoirSearch,
24353
24443
  matClose,
@@ -24356,7 +24446,7 @@ class AutocompleteComponent {
24356
24446
  provideNgIconsConfig({
24357
24447
  size: '1.75rem',
24358
24448
  }),
24359
- ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24449
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24360
24450
  }
24361
24451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, decorators: [{
24362
24452
  type: Component,
@@ -24377,7 +24467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24377
24467
  provideNgIconsConfig({
24378
24468
  size: '1.75rem',
24379
24469
  }),
24380
- ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24470
+ ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24381
24471
  }], propDecorators: { placeholder: [{
24382
24472
  type: Input
24383
24473
  }], enterButton: [{
@@ -24415,6 +24505,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24415
24505
  }], inputRef: [{
24416
24506
  type: ViewChild,
24417
24507
  args: ['searchInput']
24508
+ }], displayWithTemplate: [{
24509
+ type: Input
24418
24510
  }], displayWithFn: [{
24419
24511
  type: Input
24420
24512
  }] } });
@@ -24547,12 +24639,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24547
24639
  type: Input
24548
24640
  }] } });
24549
24641
 
24642
+ function provideLocalizedDateAdapter() {
24643
+ return [
24644
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24645
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24646
+ {
24647
+ provide: DateAdapter,
24648
+ useFactory: () => {
24649
+ const dateService = inject(DateService);
24650
+ const adapter = new DateFnsAdapter();
24651
+ dateService
24652
+ .getDateFnsLocale()
24653
+ .then((locale) => adapter.setLocale(locale));
24654
+ return adapter;
24655
+ },
24656
+ },
24657
+ ];
24658
+ }
24659
+
24550
24660
  class DatePickerComponent {
24551
24661
  constructor() {
24552
- this.dateAdapter = inject(DateAdapter);
24553
- this.translate = inject(TranslateService);
24554
24662
  this.dateChange = new EventEmitter();
24555
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24556
24663
  }
24557
24664
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24558
24665
  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: [
@@ -24560,30 +24667,19 @@ class DatePickerComponent {
24560
24667
  provideNgIconsConfig({
24561
24668
  size: '1.5rem',
24562
24669
  }),
24563
- {
24564
- provide: MAT_DATE_LOCALE,
24565
- useFactory: (locale) => locale,
24566
- },
24567
- ], 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 }); }
24670
+ provideLocalizedDateAdapter(),
24671
+ ], 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 }); }
24568
24672
  }
24569
24673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24570
24674
  type: Component,
24571
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24572
- MatNativeDateModule,
24573
- MatDatepickerModule,
24574
- ButtonComponent,
24575
- NgIconComponent,
24576
- ], providers: [
24675
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24577
24676
  provideIcons({ iconoirCalendar }),
24578
24677
  provideNgIconsConfig({
24579
24678
  size: '1.5rem',
24580
24679
  }),
24581
- {
24582
- provide: MAT_DATE_LOCALE,
24583
- useFactory: (locale) => locale,
24584
- },
24680
+ provideLocalizedDateAdapter(),
24585
24681
  ], 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"] }]
24586
- }], ctorParameters: () => [], propDecorators: { date: [{
24682
+ }], propDecorators: { date: [{
24587
24683
  type: Input
24588
24684
  }], dateChange: [{
24589
24685
  type: Output
@@ -24591,58 +24687,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24591
24687
 
24592
24688
  class DateRangeDropdownComponent {
24593
24689
  constructor() {
24594
- this.overlayContainer = inject(OverlayContainer);
24690
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24691
+ this.dateAdapter = inject(DateAdapter);
24692
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24595
24693
  this.cdr = inject(ChangeDetectorRef);
24596
- this.startDateChange = new EventEmitter();
24597
- this.endDateChange = new EventEmitter();
24598
- this.isPickerDisplayed = false;
24694
+ this.dateRange = {};
24695
+ this.dateRangeChange = new EventEmitter();
24696
+ this.overlayPositions = [
24697
+ {
24698
+ originX: 'start',
24699
+ originY: 'bottom',
24700
+ overlayX: 'start',
24701
+ overlayY: 'top',
24702
+ offsetY: 8,
24703
+ },
24704
+ {
24705
+ originX: 'start',
24706
+ originY: 'top',
24707
+ overlayX: 'start',
24708
+ overlayY: 'bottom',
24709
+ offsetY: -8,
24710
+ },
24711
+ ];
24712
+ this.scrollStrategy = this.scrollStrategies.reposition();
24713
+ this.overlayOpen = false;
24714
+ this.expandedBound = 'start';
24715
+ this.invalidBounds = { start: false, end: false };
24716
+ // redraw when the lazily loaded UI locale reaches the adapter
24717
+ this.dateAdapter.localeChanges
24718
+ .pipe(takeUntilDestroyed())
24719
+ .subscribe(() => this.cdr.markForCheck());
24599
24720
  }
24600
- ngAfterViewChecked() {
24601
- this.checkPickerOverlay();
24721
+ get startDate() {
24722
+ return this.dateRange.start;
24602
24723
  }
24603
- checkPickerOverlay() {
24604
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24605
- setTimeout(() => {
24606
- this.isPickerDisplayed =
24607
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24608
- null;
24609
- this.cdr.detectChanges();
24610
- }, 200); // FIXME: find a better way to deal with animation delay
24724
+ get endDate() {
24725
+ return this.dateRange.end;
24726
+ }
24727
+ get selectedDatesCount() {
24728
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24729
+ }
24730
+ openOverlay() {
24731
+ this.expandedBound = 'start';
24732
+ this.overlayOpen = true;
24733
+ }
24734
+ closeOverlay() {
24735
+ this.overlayOpen = false;
24736
+ }
24737
+ toggleBound(bound) {
24738
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24739
+ }
24740
+ formatDate(date) {
24741
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24742
+ }
24743
+ onDateInput(bound, event) {
24744
+ const typedText = event.target.value.trim();
24745
+ const date = typedText
24746
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24747
+ : null;
24748
+ if (!this.isAcceptable(bound, typedText, date)) {
24749
+ // keep the text as typed and flag it; the filter stays on its last value
24750
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24751
+ return;
24752
+ }
24753
+ if (bound === 'start')
24754
+ this.setStartDate(date);
24755
+ else
24756
+ this.setEndDate(date);
24757
+ }
24758
+ normalizeDateInput(bound, event) {
24759
+ if (this.invalidBounds[bound])
24760
+ return;
24761
+ const date = bound === 'start' ? this.startDate : this.endDate;
24762
+ const input = event.target;
24763
+ input.value = date ? this.formatDate(date) : '';
24764
+ }
24765
+ isAcceptable(bound, typedText, date) {
24766
+ if (!typedText)
24767
+ return true;
24768
+ if (!date || !this.dateAdapter.isValid(date))
24769
+ return false;
24770
+ return bound === 'start'
24771
+ ? !this.endDate || date <= this.endDate
24772
+ : !this.startDate || date >= this.startDate;
24773
+ }
24774
+ setStartDate(date) {
24775
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24776
+ this.applyDateRange({ ...this.dateRange, start: date });
24777
+ this.expandedBound = 'end';
24778
+ }
24779
+ setEndDate(date) {
24780
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24781
+ this.applyDateRange({ ...this.dateRange, end: date });
24782
+ this.expandedBound = null;
24783
+ }
24784
+ clearDates(event) {
24785
+ this.invalidBounds = { start: false, end: false };
24786
+ this.expandedBound = 'start';
24787
+ this.applyDateRange({});
24788
+ propagateToDocumentOnly(event);
24789
+ }
24790
+ applyDateRange(dateRange) {
24791
+ this.dateRange = {
24792
+ ...(dateRange.start && { start: dateRange.start }),
24793
+ ...(dateRange.end && { end: dateRange.end }),
24794
+ };
24795
+ this.dateRangeChange.emit(this.dateRange);
24611
24796
  }
24612
24797
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24613
- 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: [
24798
+ 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: [
24614
24799
  provideIcons({
24615
- matExpandMore,
24800
+ iconoirCalendar,
24801
+ matClose,
24616
24802
  matExpandLess,
24803
+ matExpandMore,
24617
24804
  }),
24618
- ], 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"] }] }); }
24805
+ provideNgIconsConfig({
24806
+ size: '1.5rem',
24807
+ }),
24808
+ provideLocalizedDateAdapter(),
24809
+ ], 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 }); }
24619
24810
  }
24620
24811
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24621
24812
  type: Component,
24622
24813
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24623
- NgIconComponent,
24624
- MatNativeDateModule,
24625
- MatDatepickerModule,
24814
+ AutofocusDirective,
24626
24815
  ButtonComponent,
24816
+ MatDatepickerModule,
24817
+ NgIcon,
24818
+ NgTemplateOutlet,
24819
+ OverlayModule,
24820
+ TranslateDirective,
24821
+ TranslatePipe,
24627
24822
  ], providers: [
24628
24823
  provideIcons({
24629
- matExpandMore,
24824
+ iconoirCalendar,
24825
+ matClose,
24630
24826
  matExpandLess,
24827
+ matExpandMore,
24631
24828
  }),
24632
- ], 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"] }]
24633
- }], propDecorators: { title: [{
24829
+ provideNgIconsConfig({
24830
+ size: '1.5rem',
24831
+ }),
24832
+ provideLocalizedDateAdapter(),
24833
+ ], 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"] }]
24834
+ }], ctorParameters: () => [], propDecorators: { title: [{
24634
24835
  type: Input
24635
- }], startDate: [{
24836
+ }], dateRange: [{
24636
24837
  type: Input
24637
- }], endDate: [{
24638
- type: Input
24639
- }], startDateChange: [{
24640
- type: Output
24641
- }], endDateChange: [{
24838
+ }], dateRangeChange: [{
24642
24839
  type: Output
24643
- }], picker: [{
24840
+ }], overlayOrigin: [{
24644
24841
  type: ViewChild,
24645
- args: ['picker']
24842
+ args: ['overlayOrigin']
24646
24843
  }] } });
24647
24844
 
24648
24845
  class DateRangePickerComponent {
@@ -24656,20 +24853,17 @@ class DateRangePickerComponent {
24656
24853
  provideNgIconsConfig({
24657
24854
  size: '1.5rem',
24658
24855
  }),
24659
- ], 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 }); }
24856
+ provideLocalizedDateAdapter(),
24857
+ ], 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 }); }
24660
24858
  }
24661
24859
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24662
24860
  type: Component,
24663
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24664
- MatNativeDateModule,
24665
- MatDatepickerModule,
24666
- ButtonComponent,
24667
- NgIconComponent,
24668
- ], providers: [
24861
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24669
24862
  provideIcons({ iconoirCalendar }),
24670
24863
  provideNgIconsConfig({
24671
24864
  size: '1.5rem',
24672
24865
  }),
24866
+ provideLocalizedDateAdapter(),
24673
24867
  ], 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"] }]
24674
24868
  }], propDecorators: { startDate: [{
24675
24869
  type: Input
@@ -24687,28 +24881,68 @@ class DragAndDropFileInputComponent {
24687
24881
  constructor() {
24688
24882
  this.placeholder = placeholder;
24689
24883
  this.accept = '*';
24884
+ this.maxFileSizeMb = null;
24885
+ this.icon = null;
24886
+ this.dropzoneBackgroundColor = null;
24887
+ this.textClass = '';
24888
+ this.extraClass = '';
24889
+ this.showFileName = true;
24690
24890
  this.fileChange = new EventEmitter();
24891
+ this.errorChange = new EventEmitter();
24691
24892
  this.selectedFile = null;
24692
24893
  }
24693
24894
  get fileName() {
24694
24895
  return this.selectedFile && this.selectedFile.name;
24695
24896
  }
24897
+ get maxFileSizeBytes() {
24898
+ return typeof this.maxFileSizeMb === 'number'
24899
+ ? megabytesToBytes(this.maxFileSizeMb)
24900
+ : null;
24901
+ }
24696
24902
  selectFile(event) {
24903
+ if (event.rejectedFiles?.length) {
24904
+ const reason = event.rejectedFiles[0].reason;
24905
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24906
+ return;
24907
+ }
24697
24908
  this.selectedFile = event.addedFiles[0];
24698
24909
  this.fileChange.emit(this.selectedFile);
24699
24910
  }
24911
+ openFileSelector() {
24912
+ this.dropzone.showFileSelector();
24913
+ }
24914
+ clear() {
24915
+ this.selectedFile = null;
24916
+ }
24700
24917
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24701
- 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"] }] }); }
24918
+ 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"] }] }); }
24702
24919
  }
24703
24920
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24704
24921
  type: Component,
24705
- 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"] }]
24922
+ 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"] }]
24706
24923
  }], propDecorators: { placeholder: [{
24707
24924
  type: Input
24708
24925
  }], accept: [{
24709
24926
  type: Input
24927
+ }], maxFileSizeMb: [{
24928
+ type: Input
24929
+ }], icon: [{
24930
+ type: Input
24931
+ }], dropzoneBackgroundColor: [{
24932
+ type: Input
24933
+ }], textClass: [{
24934
+ type: Input
24935
+ }], extraClass: [{
24936
+ type: Input
24937
+ }], showFileName: [{
24938
+ type: Input
24710
24939
  }], fileChange: [{
24711
24940
  type: Output
24941
+ }], errorChange: [{
24942
+ type: Output
24943
+ }], dropzone: [{
24944
+ type: ViewChild,
24945
+ args: [NgxDropzoneComponent]
24712
24946
  }] } });
24713
24947
 
24714
24948
  class DropdownMultiselectComponent {
@@ -24757,7 +24991,7 @@ class DropdownMultiselectComponent {
24757
24991
  }
24758
24992
  setFocus() {
24759
24993
  setTimeout(() => {
24760
- this.searchFieldInput.nativeElement.focus();
24994
+ this.searchFieldInput?.nativeElement.focus();
24761
24995
  }, 0);
24762
24996
  }
24763
24997
  openOverlay() {
@@ -24863,7 +25097,10 @@ class DropdownMultiselectComponent {
24863
25097
  matExpandMore,
24864
25098
  matExpandLess,
24865
25099
  }),
24866
- ], 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 }); }
25100
+ provideNgIconsConfig({
25101
+ size: '1.5rem',
25102
+ }),
25103
+ ], 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\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 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-1 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\"\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 }); }
24867
25104
  }
24868
25105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24869
25106
  type: Component,
@@ -24873,7 +25110,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24873
25110
  matExpandMore,
24874
25111
  matExpandLess,
24875
25112
  }),
24876
- ], 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" }]
25113
+ provideNgIconsConfig({
25114
+ size: '1.5rem',
25115
+ }),
25116
+ ], 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\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 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-1 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\"\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" }]
24877
25117
  }], propDecorators: { title: [{
24878
25118
  type: Input
24879
25119
  }], choices: [{
@@ -25045,7 +25285,7 @@ class DropdownSelectorComponent {
25045
25285
  provideNgIconsConfig({
25046
25286
  size: '1.5em',
25047
25287
  }),
25048
- ], 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 }); }
25288
+ ], 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 }); }
25049
25289
  }
25050
25290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25051
25291
  type: Component,
@@ -25785,6 +26025,175 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25785
26025
  type: Output
25786
26026
  }] } });
25787
26027
 
26028
+ marker('search.filters.spatialExtent.import');
26029
+ marker('search.filters.spatialExtent.helpText');
26030
+ marker('search.filters.spatialExtent.error.title');
26031
+ const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile');
26032
+ const LABEL_FROM_FILE_DELETE = marker('search.filters.spatialExtent.bboxFromFileDelete');
26033
+ const LABEL_INITIAL = marker('search.filters.spatialExtent.bboxInitial');
26034
+ const LABEL_INITIAL_DELETE = marker('search.filters.spatialExtent.bboxInitialDelete');
26035
+ class SpatialExtentDropdownComponent {
26036
+ constructor() {
26037
+ this.cd = inject(ChangeDetectorRef);
26038
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26039
+ this.maxFileSizeMb = null;
26040
+ this.bboxChange = new EventEmitter();
26041
+ this.errorChange = new EventEmitter();
26042
+ this.bbox = null;
26043
+ this.fileName = '';
26044
+ this.overlayPositions = [
26045
+ {
26046
+ originX: 'start',
26047
+ originY: 'bottom',
26048
+ overlayX: 'start',
26049
+ overlayY: 'top',
26050
+ offsetY: 8,
26051
+ },
26052
+ {
26053
+ originX: 'start',
26054
+ originY: 'top',
26055
+ overlayX: 'start',
26056
+ overlayY: 'bottom',
26057
+ offsetY: -8,
26058
+ },
26059
+ ];
26060
+ this.scrollStrategy = this.scrollStrategies.reposition();
26061
+ this.overlayOpen = false;
26062
+ this.overlayMinWidth = 'none';
26063
+ this.errorKey = null;
26064
+ }
26065
+ set initialBbox(value) {
26066
+ if (!this.bbox && value) {
26067
+ this.bbox = value;
26068
+ }
26069
+ }
26070
+ get hasSelection() {
26071
+ return !!this.bbox;
26072
+ }
26073
+ get selectionLabelKey() {
26074
+ return this.fileName ? LABEL_FROM_FILE : LABEL_INITIAL;
26075
+ }
26076
+ get selectionDeleteLabelKey() {
26077
+ return this.fileName ? LABEL_FROM_FILE_DELETE : LABEL_INITIAL_DELETE;
26078
+ }
26079
+ get selectionLabelParams() {
26080
+ return { fileName: this.fileName };
26081
+ }
26082
+ openOverlay() {
26083
+ this.overlayMinWidth =
26084
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26085
+ .width + 'px';
26086
+ this.overlayOpen = true;
26087
+ }
26088
+ closeOverlay() {
26089
+ this.overlayOpen = false;
26090
+ }
26091
+ toggleOverlay() {
26092
+ if (this.overlayOpen) {
26093
+ this.closeOverlay();
26094
+ }
26095
+ else {
26096
+ this.openOverlay();
26097
+ }
26098
+ }
26099
+ async handleFileSelected(file) {
26100
+ this.errorKey = null;
26101
+ let content;
26102
+ try {
26103
+ content = await readFileAsText(file);
26104
+ const parsed = JSON.parse(content);
26105
+ const geometry = getGeometryFromGeoJSON(parsed);
26106
+ if (!geometry) {
26107
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26108
+ return;
26109
+ }
26110
+ const bbox = getGeometryBoundingBox(geometry);
26111
+ this.bbox = bbox;
26112
+ this.fileName = file.name;
26113
+ this.bboxChange.emit(bbox);
26114
+ this.cd.markForCheck();
26115
+ }
26116
+ catch {
26117
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26118
+ return;
26119
+ }
26120
+ }
26121
+ handleFileError(error) {
26122
+ if (error === 'file-too-large') {
26123
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26124
+ maxSize: this.maxFileSizeMb,
26125
+ });
26126
+ }
26127
+ else {
26128
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26129
+ }
26130
+ }
26131
+ setError(errorKey, params) {
26132
+ this.errorKey = errorKey;
26133
+ this.errorChange.emit({ key: errorKey, params });
26134
+ this.cd.markForCheck();
26135
+ }
26136
+ removeSelection(event) {
26137
+ this.bbox = null;
26138
+ this.fileName = '';
26139
+ this.errorKey = null;
26140
+ this.fileInput?.clear();
26141
+ this.bboxChange.emit(null);
26142
+ propagateToDocumentOnly(event);
26143
+ }
26144
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26145
+ 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", initialBbox: "initialBbox" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26146
+ provideIcons({
26147
+ iconoirCheckCircle,
26148
+ iconoirImport,
26149
+ iconoirSquareDashed,
26150
+ iconoirTrash,
26151
+ matClose,
26152
+ matExpandLess,
26153
+ matExpandMore,
26154
+ }),
26155
+ ], 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]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\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 {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\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 }); }
26156
+ }
26157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26158
+ type: Component,
26159
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26160
+ ButtonComponent,
26161
+ NgIcon,
26162
+ OverlayModule,
26163
+ TranslatePipe,
26164
+ DragAndDropFileInputComponent,
26165
+ ], providers: [
26166
+ provideIcons({
26167
+ iconoirCheckCircle,
26168
+ iconoirImport,
26169
+ iconoirSquareDashed,
26170
+ iconoirTrash,
26171
+ matClose,
26172
+ matExpandLess,
26173
+ matExpandMore,
26174
+ }),
26175
+ ], 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]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\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 {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\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" }]
26176
+ }], propDecorators: { title: [{
26177
+ type: Input
26178
+ }], maxFileSizeMb: [{
26179
+ type: Input
26180
+ }], initialBbox: [{
26181
+ type: Input
26182
+ }], bboxChange: [{
26183
+ type: Output
26184
+ }], errorChange: [{
26185
+ type: Output
26186
+ }], overlayOrigin: [{
26187
+ type: ViewChild,
26188
+ args: ['overlayOrigin']
26189
+ }], overlay: [{
26190
+ type: ViewChild,
26191
+ args: [CdkConnectedOverlay]
26192
+ }], fileInput: [{
26193
+ type: ViewChild,
26194
+ args: [DragAndDropFileInputComponent]
26195
+ }] } });
26196
+
25788
26197
  class CellPopinComponent {
25789
26198
  constructor() {
25790
26199
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25861,7 +26270,7 @@ class CellPopinComponent {
25861
26270
  this.isOpen = false;
25862
26271
  }
25863
26272
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25864
- 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"] }] }); }
26273
+ 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"] }] }); }
25865
26274
  }
25866
26275
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25867
26276
  type: Component,
@@ -27483,6 +27892,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
27483
27892
  }]
27484
27893
  }] });
27485
27894
 
27895
+ const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider', {
27896
+ providedIn: 'root',
27897
+ factory: () => ['geonames', { maxRows: 5 }],
27898
+ });
27899
+ class GeocodingService {
27900
+ constructor() {
27901
+ this.provider = inject(GEOCODING_PROVIDER);
27902
+ }
27903
+ query(text) {
27904
+ let queryObservable;
27905
+ switch (this.provider[0]) {
27906
+ case 'geoadmin':
27907
+ queryObservable = from(queryGeoadmin(text, this.provider[1]));
27908
+ break;
27909
+ case 'geonames':
27910
+ queryObservable = from(queryGeonames(text, this.provider[1]));
27911
+ break;
27912
+ case 'data-gouv-fr':
27913
+ queryObservable = from(queryDataGouvFr(text, this.provider[1]));
27914
+ break;
27915
+ default:
27916
+ return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
27917
+ }
27918
+ return queryObservable.pipe(catchError((error) => throwError(error)));
27919
+ }
27920
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
27921
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
27922
+ }
27923
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
27924
+ type: Injectable,
27925
+ args: [{
27926
+ providedIn: 'root',
27927
+ }]
27928
+ }] });
27929
+
27930
+ class LocationSearchComponent {
27931
+ constructor() {
27932
+ this.geocodingService = inject(GeocodingService);
27933
+ this.placeholder = '';
27934
+ this.resultSelected = new EventEmitter();
27935
+ this.displayWithFn = (item) => item.label;
27936
+ this.searchAction = (text) => this.geocodingService.query(text);
27937
+ }
27938
+ handleItemSelected(item) {
27939
+ this.resultSelected.emit(item);
27940
+ }
27941
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
27942
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: LocationSearchComponent, isStandalone: true, selector: "gn-ui-location-search", inputs: { placeholder: "placeholder", displayWithTemplate: "displayWithTemplate" }, outputs: { resultSelected: "resultSelected" }, ngImport: i0, template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
27943
+ }
27944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, decorators: [{
27945
+ type: Component,
27946
+ args: [{ selector: 'gn-ui-location-search', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [AutocompleteComponent], template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n" }]
27947
+ }], propDecorators: { placeholder: [{
27948
+ type: Input
27949
+ }], displayWithTemplate: [{
27950
+ type: Input
27951
+ }], resultSelected: [{
27952
+ type: Output
27953
+ }] } });
27954
+
27486
27955
  // expects the replacement key ${uuid}
27487
27956
  const RECORD_DATASET_URL_TOKEN = new InjectionToken('record-dataset-url-token');
27488
27957
  const RECORD_SERVICE_URL_TOKEN = new InjectionToken('record-service-url-token');
@@ -27951,6 +28420,45 @@ class DateRangeSearchField extends SimpleSearchField {
27951
28420
  return 'dateRange';
27952
28421
  }
27953
28422
  }
28423
+ // treats a resource's creation/revision dates as an interval (its min and max), so a record matches
28424
+ // when that interval intersects the filter range, not just when a single date falls inside it
28425
+ class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
28426
+ constructor(injector, order = 'desc') {
28427
+ super('resourceCreationRevisionDate', injector, order);
28428
+ const dateSourceFields = [
28429
+ 'creationDateForResource',
28430
+ 'revisionDateForResource',
28431
+ ];
28432
+ const collectSortedDates = dateSourceFields
28433
+ .map((field) => `if (doc.containsKey('${field}')) {
28434
+ for (def date : doc['${field}']) { dates.add(date.millis); }
28435
+ }`)
28436
+ .join('\n');
28437
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMin', `def dates = [];
28438
+ ${collectSortedDates}
28439
+ if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`, 'date');
28440
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMax', `def dates = [];
28441
+ ${collectSortedDates}
28442
+ if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`, 'date');
28443
+ }
28444
+ }
28445
+ class BoundingBoxSearchField extends SimpleSearchField {
28446
+ getAvailableValues() {
28447
+ return of([]);
28448
+ }
28449
+ getFiltersForValues(values) {
28450
+ return of({
28451
+ [this.esFieldName]: values.map(Number),
28452
+ });
28453
+ }
28454
+ getValuesForFilter(filters) {
28455
+ const filter = filters[this.esFieldName];
28456
+ return of(isBoundingBox(filter) ? filter : []);
28457
+ }
28458
+ getType() {
28459
+ return 'spatialExtent';
28460
+ }
28461
+ }
27954
28462
  marker('search.filters.availableServices.view');
27955
28463
  marker('search.filters.availableServices.download');
27956
28464
  class AvailableServicesField extends SimpleSearchField {
@@ -28095,6 +28603,8 @@ marker('search.filters.producerOrg');
28095
28603
  marker('search.filters.publisherOrg');
28096
28604
  marker('search.filters.user');
28097
28605
  marker('search.filters.changeDate');
28606
+ marker('search.filters.resourceCreationRevisionDate');
28607
+ marker('search.filters.spatialExtent');
28098
28608
  class FieldsService {
28099
28609
  constructor() {
28100
28610
  this.injector = inject(Injector);
@@ -28117,7 +28627,9 @@ class FieldsService {
28117
28627
  publisherOrg: new MultilingualSearchField('distributorOrgForResourceObject', this.injector, 'asc', 'key'),
28118
28628
  user: new UserSearchField(this.injector),
28119
28629
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28630
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(this.injector, 'desc'),
28120
28631
  availableServices: new AvailableServicesField(this.injector),
28632
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28121
28633
  };
28122
28634
  }
28123
28635
  get supportedFields() {
@@ -29380,7 +29892,7 @@ class ContactPillComponent {
29380
29892
  this.overlayOpen = false;
29381
29893
  }
29382
29894
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29383
- 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 }); }
29895
+ 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 }); }
29384
29896
  }
29385
29897
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29386
29898
  type: Component,
@@ -30106,7 +30618,7 @@ class ServiceCapabilitiesComponent {
30106
30618
  if (this.apiLinks.length > 0 &&
30107
30619
  this.apiLinks[0].accessServiceProtocol !== 'ogcFeatures') {
30108
30620
  this.loading = true;
30109
- this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol);
30621
+ this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol, true);
30110
30622
  this.loading = false;
30111
30623
  this.cdr.detectChanges();
30112
30624
  this.filteredLayers = this.availableLayers;
@@ -31302,19 +31814,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31302
31814
  type: Output
31303
31815
  }] } });
31304
31816
 
31817
+ class NotificationsService {
31818
+ constructor() {
31819
+ this.notifications$ = new BehaviorSubject([]);
31820
+ }
31821
+ showNotification(content, timeoutMs, error) {
31822
+ error && console.error(error);
31823
+ const id = Math.floor(Math.random() * 1000000);
31824
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31825
+ if (typeof timeoutMs !== 'undefined') {
31826
+ setTimeout(() => {
31827
+ this.removeNotificationById(id);
31828
+ }, timeoutMs);
31829
+ }
31830
+ return id;
31831
+ }
31832
+ removeNotificationById(id) {
31833
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31834
+ }
31835
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31836
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31837
+ }
31838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31839
+ type: Injectable,
31840
+ args: [{
31841
+ providedIn: 'root',
31842
+ }]
31843
+ }] });
31844
+
31845
+ class NotificationsContainerComponent {
31846
+ constructor() {
31847
+ this.notificationsService = inject(NotificationsService);
31848
+ }
31849
+ trackById(index, notification) {
31850
+ return notification.id;
31851
+ }
31852
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31853
+ 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: [
31854
+ trigger('enterExit', [
31855
+ transition(':enter', [
31856
+ animate('150ms', keyframes([
31857
+ style({ transform: 'scale(1)', opacity: 0 }),
31858
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31859
+ style({ transform: 'scale(1)', opacity: 1 }),
31860
+ ])),
31861
+ ]),
31862
+ transition(':leave', [
31863
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31864
+ ]),
31865
+ ]),
31866
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31867
+ }
31868
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31869
+ type: Component,
31870
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31871
+ trigger('enterExit', [
31872
+ transition(':enter', [
31873
+ animate('150ms', keyframes([
31874
+ style({ transform: 'scale(1)', opacity: 0 }),
31875
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31876
+ style({ transform: 'scale(1)', opacity: 1 }),
31877
+ ])),
31878
+ ]),
31879
+ transition(':leave', [
31880
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31881
+ ]),
31882
+ ]),
31883
+ ], 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" }]
31884
+ }] });
31885
+
31305
31886
  class FilterDropdownComponent {
31306
31887
  constructor() {
31307
31888
  this.searchFacade = inject(SearchFacade);
31308
31889
  this.searchService = inject(SearchService);
31309
31890
  this.fieldsService = inject(FieldsService);
31891
+ this.notificationsService = inject(NotificationsService);
31892
+ this.translateService = inject(TranslateService);
31893
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31310
31894
  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([])));
31311
- this.selectedDateRange$ = this.selected$.pipe(map$1((selectedDateRange) => selectedDateRange));
31895
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31896
+ this.selectedBoundingBox$ = this.selected$.pipe(map$1((selected) => Array.isArray(selected) && selected.length > 0
31897
+ ? selected
31898
+ : null));
31899
+ this.spatialExtentErrorNotificationId = null;
31312
31900
  }
31313
31901
  onSelectedValues(values) {
31314
31902
  this.fieldsService
31315
31903
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31316
31904
  .subscribe((filters) => this.searchService.updateFilters(filters));
31317
31905
  }
31906
+ onBboxChange(bbox) {
31907
+ this.fieldsService
31908
+ .buildFiltersFromFieldValues({
31909
+ [this.fieldName]: bbox,
31910
+ })
31911
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31912
+ this.clearSpatialExtentErrorNotification();
31913
+ }
31914
+ onSpatialExtentError(error) {
31915
+ this.clearSpatialExtentErrorNotification();
31916
+ this.spatialExtentErrorNotificationId =
31917
+ this.notificationsService.showNotification({
31918
+ type: 'error',
31919
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31920
+ text: this.translateService.instant(error.key, error.params),
31921
+ });
31922
+ }
31923
+ clearSpatialExtentErrorNotification() {
31924
+ if (this.spatialExtentErrorNotificationId === null)
31925
+ return;
31926
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31927
+ this.spatialExtentErrorNotificationId = null;
31928
+ }
31318
31929
  ngOnInit() {
31319
31930
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31320
31931
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31322,27 +31933,15 @@ class FilterDropdownComponent {
31322
31933
  value: v.value.toString(), // converting to string for the dropdown
31323
31934
  }))), catchError(() => of([])));
31324
31935
  }
31325
- onStartDateChange(start) {
31326
- if (!start)
31327
- return;
31328
- this.dateRange = { ...this.dateRange, start };
31329
- }
31330
- onEndDateChange(end) {
31331
- if (!end)
31332
- return;
31333
- this.dateRange = { ...this.dateRange, end };
31334
- if (this.dateRange.start && this.dateRange.end) {
31335
- this.fieldsService
31336
- .buildFiltersFromFieldValues({
31337
- [this.fieldName]: this.dateRange,
31338
- })
31339
- .subscribe((filters) => {
31340
- return this.searchService.updateFilters(filters);
31341
- });
31342
- }
31936
+ onDateRangeChange(dateRange) {
31937
+ this.fieldsService
31938
+ .buildFiltersFromFieldValues({
31939
+ [this.fieldName]: dateRange,
31940
+ })
31941
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31343
31942
  }
31344
31943
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31345
- 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 }); }
31944
+ 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 [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\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", "initialBbox"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31346
31945
  }
31347
31946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31348
31947
  type: Component,
@@ -31350,7 +31949,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31350
31949
  CommonModule,
31351
31950
  DateRangeDropdownComponent,
31352
31951
  DropdownMultiselectComponent,
31353
- ], 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" }]
31952
+ SpatialExtentDropdownComponent,
31953
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\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" }]
31354
31954
  }], propDecorators: { fieldName: [{
31355
31955
  type: Input
31356
31956
  }], title: [{
@@ -31411,7 +32011,7 @@ class FuzzySearchComponent {
31411
32011
  this.isSearchActive.emit(event);
31412
32012
  }
31413
32013
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31414
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
32014
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31415
32015
  }
31416
32016
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, decorators: [{
31417
32017
  type: Component,
@@ -31886,74 +32486,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31886
32486
  args: ['gnUiSearchStateContainer']
31887
32487
  }] } });
31888
32488
 
31889
- class NotificationsService {
31890
- constructor() {
31891
- this.notifications$ = new BehaviorSubject([]);
31892
- }
31893
- showNotification(content, timeoutMs, error) {
31894
- error && console.error(error);
31895
- const id = Math.floor(Math.random() * 1000000);
31896
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31897
- if (typeof timeoutMs === 'undefined')
31898
- return;
31899
- setTimeout(() => {
31900
- this.removeNotificationById(id);
31901
- }, timeoutMs);
31902
- }
31903
- removeNotificationById(id) {
31904
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31905
- }
31906
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31907
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31908
- }
31909
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31910
- type: Injectable,
31911
- args: [{
31912
- providedIn: 'root',
31913
- }]
31914
- }] });
31915
-
31916
- class NotificationsContainerComponent {
31917
- constructor() {
31918
- this.notificationsService = inject(NotificationsService);
31919
- }
31920
- trackById(index, notification) {
31921
- return notification.id;
31922
- }
31923
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31924
- 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: [
31925
- trigger('enterExit', [
31926
- transition(':enter', [
31927
- animate('150ms', keyframes([
31928
- style({ transform: 'scale(1)', opacity: 0 }),
31929
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31930
- style({ transform: 'scale(1)', opacity: 1 }),
31931
- ])),
31932
- ]),
31933
- transition(':leave', [
31934
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31935
- ]),
31936
- ]),
31937
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31938
- }
31939
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31940
- type: Component,
31941
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31942
- trigger('enterExit', [
31943
- transition(':enter', [
31944
- animate('150ms', keyframes([
31945
- style({ transform: 'scale(1)', opacity: 0 }),
31946
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31947
- style({ transform: 'scale(1)', opacity: 1 }),
31948
- ])),
31949
- ]),
31950
- transition(':leave', [
31951
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31952
- ]),
31953
- ]),
31954
- ], 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" }]
31955
- }] });
31956
-
31957
32489
  class ResultsTableContainerComponent {
31958
32490
  constructor() {
31959
32491
  this.searchFacade = inject(SearchFacade);
@@ -32038,12 +32570,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32038
32570
 
32039
32571
  marker('search.filters.summaryLabel.user');
32040
32572
  marker('search.filters.summaryLabel.changeDate');
32573
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate');
32574
+ const OPEN_BOUND = '…';
32041
32575
  class SearchFiltersSummaryItemComponent {
32042
32576
  constructor() {
32043
32577
  this.searchFacade = inject(SearchFacade);
32044
32578
  this.searchService = inject(SearchService);
32045
32579
  this.fieldsService = inject(FieldsService);
32046
- this.datePipe = inject(DatePipe);
32580
+ this.dateService = inject(DateService);
32047
32581
  this.translate = inject(TranslateService);
32048
32582
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32049
32583
  ? fieldValues[this.fieldName]
@@ -32070,9 +32604,10 @@ class SearchFiltersSummaryItemComponent {
32070
32604
  getReadableValues(fieldValues) {
32071
32605
  return fieldValues.map((value) => {
32072
32606
  if (this.fieldType === 'dateRange') {
32607
+ const { start, end } = value;
32073
32608
  return {
32074
32609
  value,
32075
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32610
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32076
32611
  };
32077
32612
  }
32078
32613
  else if (this.fieldName === 'user') {
@@ -32083,6 +32618,15 @@ class SearchFiltersSummaryItemComponent {
32083
32618
  }
32084
32619
  });
32085
32620
  }
32621
+ formatBound(date) {
32622
+ return date
32623
+ ? this.dateService.formatDate(date, {
32624
+ day: '2-digit',
32625
+ month: '2-digit',
32626
+ year: 'numeric',
32627
+ })
32628
+ : OPEN_BOUND;
32629
+ }
32086
32630
  async removeFilterValue(fieldValue) {
32087
32631
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32088
32632
  const updatedFieldValues = currentFieldValues
@@ -32095,11 +32639,11 @@ class SearchFiltersSummaryItemComponent {
32095
32639
  .subscribe((filters) => this.searchService.updateFilters(filters));
32096
32640
  }
32097
32641
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32098
- 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" }] }); }
32642
+ 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" }] }); }
32099
32643
  }
32100
32644
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32101
32645
  type: Component,
32102
- 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" }]
32646
+ 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" }]
32103
32647
  }], propDecorators: { fieldName: [{
32104
32648
  type: Input
32105
32649
  }] } });
@@ -32343,11 +32887,11 @@ class AddLayerFromFileComponent {
32343
32887
  }, 5000);
32344
32888
  }
32345
32889
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32346
- 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" }] }); }
32890
+ 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" }] }); }
32347
32891
  }
32348
32892
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32349
32893
  type: Component,
32350
- 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" }]
32894
+ 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" }]
32351
32895
  }] });
32352
32896
 
32353
32897
  class LayersPanelComponent {
@@ -32408,95 +32952,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32408
32952
  ], template: "<div\n class=\"bg-white rounded shadow shadow-lg relative h-full w-[400px] overflow-hidden\"\n>\n <div class=\"p-3 border-b border-gray-300 flex items-center\">\n <ng-icon class=\"mr-2\" name=\"matLayersOutline\"></ng-icon>\n <span translate>map.layers.list</span>\n </div>\n <div class=\"flex flex-col px-4 divide-y divide-y-gray-50\">\n @for (layer of layers$ | async; track layer; let index = $index) {\n <div class=\"flex flex-row py-3\">\n <ng-icon class=\"-ml-2 mr-2 shrink-0\" name=\"matChevronRight\"></ng-icon>\n <span class=\"mr-2 grow\">{{ layer.label }}</span>\n <a\n href\n class=\"underline text-sm text-red-700 hover:text-red-900\"\n (click)=\"deleteLayer(index); $event.preventDefault()\"\n >\n delete\n </a>\n </div>\n }\n </div>\n <gn-ui-expandable-panel-button [titleTemplate]=\"addLayerTitle\">\n <mat-tab-group animationDuration=\"200ms\" class=\"h-full\">\n <mat-tab [label]=\"'map.add.layer.catalog' | translate\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-catalog></gn-ui-add-layer-from-catalog>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wms' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wms></gn-ui-add-layer-from-wms>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wfs' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wfs></gn-ui-add-layer-from-wfs>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.ogc.api' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-ogc-api\n [ogcUrl]=\"ogcUrl\"\n (layerAdded)=\"addLayer($event)\"\n ></gn-ui-add-layer-from-ogc-api>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.file' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-file></gn-ui-add-layer-from-file>\n </div>\n </mat-tab>\n </mat-tab-group>\n </gn-ui-expandable-panel-button>\n</div>\n\n<ng-template #addLayerTitle>\n <ng-icon class=\"mr-4\" name=\"matAddCircleOutlineOutline\"> </ng-icon>\n <span translate>map.add.layer</span>\n</ng-template>\n" }]
32409
32953
  }] });
32410
32954
 
32411
- class GeocodingComponent {
32412
- constructor() {
32413
- this.mapFacade = inject(MapFacade);
32414
- this.geocodingService = inject(GeocodingService);
32415
- this.searchText = '';
32416
- this.results = [];
32417
- this.searchTextChanged = new Subject();
32418
- this.destroy$ = new Subject();
32419
- this.errorMessage = null;
32420
- this.searchTextChanged
32421
- .pipe(debounceTime(300), switchMap$1((searchText) => {
32422
- return from(this.geocodingService.query(searchText)).pipe(catchError$1((error) => {
32423
- this.errorMessage =
32424
- 'An error occurred while searching. Please try again.';
32425
- console.error(error);
32426
- return [];
32427
- }));
32428
- }), takeUntil(this.destroy$))
32429
- .subscribe((results) => {
32430
- this.results = results;
32431
- });
32432
- }
32433
- ngOnDestroy() {
32434
- this.destroy$.next();
32435
- this.destroy$.complete();
32436
- }
32437
- onSearchChange(searchText) {
32438
- if (!searchText) {
32439
- this.clearSearch();
32440
- return;
32441
- }
32442
- else {
32443
- this.searchTextChanged.next(searchText);
32444
- }
32445
- }
32446
- clearSearch() {
32447
- this.searchText = '';
32448
- this.results = [];
32449
- this.errorMessage = null;
32450
- }
32451
- async zoomToLocation(result) {
32452
- const geometry = result.geom;
32453
- if (geometry.type === 'Point') {
32454
- await this.zoomToPoint(geometry.coordinates);
32455
- }
32456
- else if (geometry.type === 'Polygon') {
32457
- await this.zoomToPolygon(geometry.coordinates);
32458
- }
32459
- else {
32460
- console.error(`Unsupported geometry type: ${geometry.type}`);
32461
- }
32462
- }
32463
- async zoomToPoint(pointCoords) {
32464
- const context = await firstValueFrom(this.mapFacade.context$);
32465
- const view = {
32466
- center: pointCoords,
32467
- zoom: 10,
32468
- };
32469
- this.mapFacade.applyContext({
32470
- ...context,
32471
- view,
32472
- });
32473
- }
32474
- async zoomToPolygon(polygonCoords) {
32475
- const context = await firstValueFrom(this.mapFacade.context$);
32476
- const view = {
32477
- geometry: {
32478
- type: 'Polygon',
32479
- coordinates: polygonCoords,
32480
- },
32481
- };
32482
- this.mapFacade.applyContext({
32483
- ...context,
32484
- view,
32485
- });
32486
- }
32487
- onEnterPress() {
32488
- if (this.results && this.results.length > 0) {
32489
- this.zoomToLocation(this.results[0]);
32490
- }
32491
- }
32492
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32493
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GeocodingComponent, isStandalone: true, selector: "gn-ui-geocoding", ngImport: i0, template: "<gn-ui-search-input\n [(value)]=\"searchText\"\n (valueChange)=\"onSearchChange($event)\"\n (keyup.enter)=\"onEnterPress()\"\n [placeholder]=\"'map.geocoding.placeholder' | translate\"\n>\n</gn-ui-search-input>\n@if (results && results.length) {\n <ul\n class=\"bg-gray-50 border border-gray-200 w-full mt-2 shadow-sm rounded-lg\"\n >\n @for (result of results; track result) {\n <li\n (click)=\"zoomToLocation(result)\"\n class=\"flex items-center pl-8 pr-4 py-2 border-b border-gray-200 relative cursor-pointer hover:bg-blue-100 hover:text-gray-800 transition duration-300 ease-in-out\"\n >\n <svg\n class=\"stroke-current text-blue-500 absolute w-5 h-5 left-3 top-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <span class=\"font-sans font-semibold ml-4\">{{ result.label }}</span>\n </li>\n }\n </ul>\n}\n", styles: [""], dependencies: [{ kind: "component", type: SearchInputComponent, selector: "gn-ui-search-input", inputs: ["value", "placeholder"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32494
- }
32495
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, decorators: [{
32496
- type: Component,
32497
- args: [{ selector: 'gn-ui-geocoding', standalone: true, imports: [SearchInputComponent, TranslatePipe], template: "<gn-ui-search-input\n [(value)]=\"searchText\"\n (valueChange)=\"onSearchChange($event)\"\n (keyup.enter)=\"onEnterPress()\"\n [placeholder]=\"'map.geocoding.placeholder' | translate\"\n>\n</gn-ui-search-input>\n@if (results && results.length) {\n <ul\n class=\"bg-gray-50 border border-gray-200 w-full mt-2 shadow-sm rounded-lg\"\n >\n @for (result of results; track result) {\n <li\n (click)=\"zoomToLocation(result)\"\n class=\"flex items-center pl-8 pr-4 py-2 border-b border-gray-200 relative cursor-pointer hover:bg-blue-100 hover:text-gray-800 transition duration-300 ease-in-out\"\n >\n <svg\n class=\"stroke-current text-blue-500 absolute w-5 h-5 left-3 top-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <span class=\"font-sans font-semibold ml-4\">{{ result.label }}</span>\n </li>\n }\n </ul>\n}\n" }]
32498
- }], ctorParameters: () => [] });
32499
-
32500
32955
  /*
32501
32956
  Metadata actions
32502
32957
  */
@@ -32512,8 +32967,7 @@ const closeMetadata = createAction('[Metadata view] close');
32512
32967
  Related actions
32513
32968
  */
32514
32969
  const setRelated = createAction('[Metadata view] Set related records', props());
32515
- const setSources = createAction('[Metadata view] Set sources', props());
32516
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32970
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32517
32971
  /*
32518
32972
  ChartConfig actions
32519
32973
  */
@@ -32545,9 +32999,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32545
32999
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32546
33000
  setChartConfig: setChartConfig,
32547
33001
  setIncompleteMetadata: setIncompleteMetadata,
32548
- setRelated: setRelated,
32549
- setSourceOf: setSourceOf,
32550
- setSources: setSources
33002
+ setLinkedRecords: setLinkedRecords,
33003
+ setRelated: setRelated
32551
33004
  });
32552
33005
 
32553
33006
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32590,12 +33043,9 @@ on(loadFullMetadata, (state) => ({
32590
33043
  on(setRelated, (state, { related }) => ({
32591
33044
  ...state,
32592
33045
  related,
32593
- })), on(setSources, (state, { sources }) => ({
32594
- ...state,
32595
- sources,
32596
- })), on(setSourceOf, (state, { sourceOf }) => ({
33046
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32597
33047
  ...state,
32598
- sourceOf,
33048
+ linkedRecords,
32599
33049
  })),
32600
33050
  /*
32601
33051
  ChartConfig reducers
@@ -32657,8 +33107,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32657
33107
  Related selectors
32658
33108
  */
32659
33109
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32660
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32661
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33110
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32662
33111
  /*
32663
33112
  Metadata selectors
32664
33113
  */
@@ -32759,6 +33208,453 @@ function parseHeaders(httpHeaders) {
32759
33208
  return result;
32760
33209
  }
32761
33210
 
33211
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33212
+ function fieldToSql(name) {
33213
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
33214
+ }
33215
+ function filterToSql(filter) {
33216
+ const operator = filter[0];
33217
+ const args = filter.slice(1);
33218
+ function valueToSql(value) {
33219
+ return typeof value === 'number' ? value : `'${value}'`;
33220
+ }
33221
+ switch (operator) {
33222
+ case '<':
33223
+ case '<=':
33224
+ case '>':
33225
+ case '>=':
33226
+ case '=':
33227
+ case '!=':
33228
+ case 'like':
33229
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33230
+ case 'in': {
33231
+ const values = args.slice(1);
33232
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
33233
+ }
33234
+ case 'and':
33235
+ case 'or': {
33236
+ const children = args
33237
+ .map(filterToSql)
33238
+ .join(` ${operator.toUpperCase()} `);
33239
+ return `(${children})`;
33240
+ }
33241
+ case 'not':
33242
+ return `NOT (${filterToSql(args[0])})`;
33243
+ }
33244
+ throw new Error(`Could not generate SQL query, operator not recognized: ${operator}`);
33245
+ }
33246
+ function aggregationToSql(aggregation) {
33247
+ const operation = aggregation[0];
33248
+ const field = aggregation[1];
33249
+ switch (operation) {
33250
+ case 'average':
33251
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
33252
+ case 'sum':
33253
+ case 'max':
33254
+ case 'min':
33255
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
33256
+ case 'count':
33257
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
33258
+ }
33259
+ }
33260
+ /**
33261
+ * Leave arguments at null if not used
33262
+ * @param tableName
33263
+ * @param selected
33264
+ * @param filter
33265
+ * @param sort
33266
+ * @param startIndex
33267
+ * @param count
33268
+ * @param groupBy
33269
+ * @param aggregations
33270
+ */
33271
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33272
+ let sqlSelect = 'SELECT *';
33273
+ const sqlFrom = ` FROM ${tableName}`;
33274
+ let sqlOrderBy = '';
33275
+ let sqlWhere = '';
33276
+ let sqlLimit = '';
33277
+ let sqlGroupBy = '';
33278
+ if (selected !== null) {
33279
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33280
+ }
33281
+ if (geometryColumn !== null) {
33282
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33283
+ }
33284
+ if (filter !== null) {
33285
+ sqlWhere = ` WHERE ${filterToSql(filter)}`;
33286
+ }
33287
+ if (sort?.length) {
33288
+ sqlOrderBy = ` ORDER BY ${sort
33289
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33290
+ .join(', ')}`;
33291
+ }
33292
+ if (startIndex !== null && count !== null) {
33293
+ sqlLimit = ` LIMIT ${count} OFFSET ${startIndex}`;
33294
+ }
33295
+ if (groupBy !== null && aggregations !== null) {
33296
+ sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33297
+ const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33298
+ const sqlGroupByFields = groupedByDistinct
33299
+ .map((group) => fieldToSql(group[1]))
33300
+ .join(', ');
33301
+ const sqlGroupBySelect = groupedByDistinct
33302
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33303
+ .join(', ');
33304
+ if (sqlGroupByFields && sqlGroupBySelect) {
33305
+ sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33306
+ sqlSelect += `, ${sqlGroupBySelect}`;
33307
+ }
33308
+ }
33309
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33310
+ }
33311
+
33312
+ function arrowTableToDataItems(table) {
33313
+ const fields = table.schema.fields;
33314
+ return table.toArray().map((row) => {
33315
+ const rowJson = row.toJSON();
33316
+ const feature = {
33317
+ type: 'Feature',
33318
+ geometry: null,
33319
+ properties: {},
33320
+ };
33321
+ const keys = Object.keys(rowJson);
33322
+ for (let i = 0; i < keys.length; i++) {
33323
+ const key = keys[i];
33324
+ const dataType = fields[i].type;
33325
+ let value = rowJson[key];
33326
+ // this might happen if we get an array inside a field
33327
+ if (value instanceof Vector) {
33328
+ value = Array.from(value);
33329
+ }
33330
+ // cast bigints to ints
33331
+ if (typeof value === 'bigint') {
33332
+ value = Number(value);
33333
+ }
33334
+ // rename columns with empty name
33335
+ if (!key) {
33336
+ feature.properties['unknown'] = value;
33337
+ continue;
33338
+ }
33339
+ // assign properties that look like an id to the geojson `id` field
33340
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33341
+ (typeof value == 'string' || typeof value === 'number')) {
33342
+ feature.id = value;
33343
+ }
33344
+ // if a date is in timestamp (number) format, convert it to native
33345
+ if (typeof value === 'number' &&
33346
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33347
+ value = new Date(value);
33348
+ }
33349
+ // if a binary field (most likely a geometry column): skip
33350
+ if (DataType.isBinary(dataType)) {
33351
+ continue;
33352
+ }
33353
+ // geometry column
33354
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33355
+ feature.geometry = JSON.parse(value);
33356
+ continue;
33357
+ }
33358
+ feature.properties[key] = value;
33359
+ }
33360
+ return feature;
33361
+ });
33362
+ }
33363
+
33364
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33365
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33366
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33367
+ const typesMapping = {
33368
+ INTEGER: 'number',
33369
+ SMALLINT: 'number',
33370
+ TINYINT: 'number',
33371
+ BIGINT: 'number',
33372
+ HUGEINT: 'number',
33373
+ UINTEGER: 'number',
33374
+ USMALLINT: 'number',
33375
+ UTINYINT: 'number',
33376
+ UBIGINT: 'number',
33377
+ UHUGEINT: 'number',
33378
+ DOUBLE: 'number',
33379
+ BIGNUM: 'number',
33380
+ DECIMAL: 'number',
33381
+ NUMERIC: 'number',
33382
+ FLOAT: 'number',
33383
+ REAL: 'number',
33384
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33385
+ TIMESTAMP: 'date',
33386
+ DATE: 'date',
33387
+ CHAR: 'string',
33388
+ VARCHAR: 'string',
33389
+ TEXT: 'string',
33390
+ UUID: 'string',
33391
+ BLOB: 'string',
33392
+ BIT: 'string',
33393
+ INTERVAL: 'string',
33394
+ LIST: 'string',
33395
+ BOOLEAN: 'boolean',
33396
+ };
33397
+ class Engine {
33398
+ async makeInit() {
33399
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33400
+ let worker_url = bundle.mainWorker;
33401
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33402
+ if (worker_url.startsWith('https://')) {
33403
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33404
+ type: 'text/javascript',
33405
+ }));
33406
+ }
33407
+ const worker = new Worker(worker_url);
33408
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33409
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33410
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33411
+ // setup duckdb options
33412
+ const conn = await this.db.connect();
33413
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33414
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33415
+ await conn.query(`SET TimeZone = '${timezone}';`);
33416
+ await conn.query(`SET threads = 1;`);
33417
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33418
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33419
+ conn.close();
33420
+ return this;
33421
+ }
33422
+ isReady() {
33423
+ if (!this.init_) {
33424
+ this.init_ = this.makeInit();
33425
+ }
33426
+ return this.init_;
33427
+ }
33428
+ /**
33429
+ * Returns information about a dataset once it's loaded:
33430
+ * - a list of properties description
33431
+ * - the name of the dataset geometry column (null if no geometry present)
33432
+ * @param datasetId name of the table under which the dataset will be stored
33433
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33434
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33435
+ */
33436
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33437
+ const conn = await this.db.connect();
33438
+ let results;
33439
+ // either we want to recreate the table, or we keep it if it already exists
33440
+ const safeLoadQuery = forceReload
33441
+ ? `DROP TABLE IF EXISTS ${datasetId};
33442
+ ${loadQuery}`
33443
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33444
+ // create the table
33445
+ try {
33446
+ results = await conn.query(safeLoadQuery);
33447
+ }
33448
+ catch (e) {
33449
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33450
+ }
33451
+ // read rows count
33452
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33453
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33454
+ // read columns
33455
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33456
+ let geometryColumn = null;
33457
+ const properties = results
33458
+ .toArray()
33459
+ .map((row) => {
33460
+ const rowObj = row.toJSON();
33461
+ if (rowObj['data_type'] === 'GEOMETRY') {
33462
+ // the geometry is not part of the properties
33463
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33464
+ // all get discarded
33465
+ geometryColumn = rowObj['column_name'];
33466
+ return null;
33467
+ }
33468
+ return {
33469
+ name: rowObj['column_name'],
33470
+ label: rowObj['column_name'],
33471
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33472
+ };
33473
+ })
33474
+ .filter((prop) => prop !== null);
33475
+ conn.close();
33476
+ return {
33477
+ properties,
33478
+ geometryColumn,
33479
+ rowsCount: Number(recordsCount),
33480
+ };
33481
+ }
33482
+ // register a Uint8 buffer using a handle in the duckdb instance
33483
+ async registerData(name, buffer) {
33484
+ return this.db.registerFileBuffer(name, buffer);
33485
+ }
33486
+ /**
33487
+ * @param query duckdb-specific query for fetching items
33488
+ */
33489
+ async queryItems(query) {
33490
+ const conn = await this.db.connect();
33491
+ const results = await conn.query(query);
33492
+ conn.close();
33493
+ return arrowTableToDataItems(results);
33494
+ }
33495
+ close() {
33496
+ this.db?.terminate();
33497
+ }
33498
+ }
33499
+ let engine = null;
33500
+ async function getEngine() {
33501
+ if (!engine) {
33502
+ engine = new Engine();
33503
+ }
33504
+ return engine.isReady();
33505
+ }
33506
+
33507
+ class BaseReader {
33508
+ constructor(url) {
33509
+ this.url = url;
33510
+ this.selected = null;
33511
+ this.groupedBy = null;
33512
+ this.aggregations = null;
33513
+ this.filter = null;
33514
+ this.sort = null;
33515
+ this.startIndex = null;
33516
+ this.count = null;
33517
+ this.loadPromise_ = Promise.resolve();
33518
+ this.cacheEnabled = false;
33519
+ }
33520
+ enableCache(enabled) {
33521
+ this.cacheEnabled = enabled;
33522
+ }
33523
+ load() {
33524
+ throw new Error('not implemented');
33525
+ }
33526
+ get isLoaded() {
33527
+ return this.loadPromise_;
33528
+ }
33529
+ get properties() {
33530
+ throw new Error('not implemented');
33531
+ }
33532
+ get info() {
33533
+ throw new Error('not implemented');
33534
+ }
33535
+ read() {
33536
+ throw new Error('not implemented');
33537
+ }
33538
+ selectAll() {
33539
+ this.groupedBy = null;
33540
+ this.aggregations = null;
33541
+ this.selected = null;
33542
+ this.filter = null;
33543
+ this.startIndex = null;
33544
+ this.count = null;
33545
+ return this;
33546
+ }
33547
+ select(...selectedFields) {
33548
+ this.selected = selectedFields;
33549
+ this.aggregations = null; // clear aggregations & groups when selecting fields
33550
+ this.groupedBy = null;
33551
+ return this;
33552
+ }
33553
+ groupBy(...groupBy) {
33554
+ this.groupedBy = groupBy;
33555
+ this.selected = null; // clear normal field selection when aggregating
33556
+ return this;
33557
+ }
33558
+ aggregate(...aggregations) {
33559
+ this.aggregations = aggregations;
33560
+ return this;
33561
+ }
33562
+ where(filter) {
33563
+ this.filter = filter;
33564
+ return this;
33565
+ }
33566
+ orderBy(...fieldSorts) {
33567
+ this.sort = fieldSorts;
33568
+ return this;
33569
+ }
33570
+ limit(startIndex, count) {
33571
+ this.startIndex = startIndex;
33572
+ this.count = count;
33573
+ return this;
33574
+ }
33575
+ }
33576
+
33577
+ /**
33578
+ * This reader handles file formats supported natively by DuckDB
33579
+ */
33580
+ class BaseFileReader extends BaseReader {
33581
+ // a table id should not exceed 63 chars
33582
+ generateDatasetId() {
33583
+ // generate a hash out of the url
33584
+ let hash = 0;
33585
+ for (const char of this.url) {
33586
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33587
+ hash = hash >>> 0; // make it unsigned
33588
+ }
33589
+ return `datafetcher_${hash.toString(16)}`;
33590
+ }
33591
+ async getLoadQuery() {
33592
+ throw new Error('not implemented');
33593
+ }
33594
+ async load() {
33595
+ this.datasetId = this.generateDatasetId();
33596
+ this.loadPromise_ = getEngine()
33597
+ .then((engine) => {
33598
+ this.engine = engine;
33599
+ return this.getLoadQuery();
33600
+ })
33601
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33602
+ .then((datasetInfo) => {
33603
+ this.properties_ = datasetInfo.properties;
33604
+ this.geometryColumn = datasetInfo.geometryColumn;
33605
+ this.rowsCount = datasetInfo.rowsCount;
33606
+ // returns void for the loaded promise
33607
+ });
33608
+ }
33609
+ get properties() {
33610
+ return this.isLoaded.then(() => this.properties_);
33611
+ }
33612
+ get info() {
33613
+ return this.isLoaded.then(() => ({
33614
+ itemsCount: this.rowsCount,
33615
+ hasGeometry: !!this.geometryColumn,
33616
+ }));
33617
+ }
33618
+ async read() {
33619
+ await this.isLoaded;
33620
+ // if only certain fields are selected, omit the geometry
33621
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33622
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33623
+ return this.engine.queryItems(query);
33624
+ }
33625
+ }
33626
+
33627
+ class CsvReader extends BaseFileReader {
33628
+ async getLoadQuery() {
33629
+ // first we get a list of columns hich have a detected type of VARCHAR
33630
+ // 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
33631
+ return `
33632
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33633
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33634
+ force_not_null = getvariable('strColumns'),
33635
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33636
+ );`;
33637
+ }
33638
+ }
33639
+
33640
+ class JsonReader extends BaseFileReader {
33641
+ async getLoadQuery() {
33642
+ return `
33643
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33644
+ maximum_object_size = 536870912 -- 500MB
33645
+ );`;
33646
+ }
33647
+ }
33648
+
33649
+ class GeojsonReader extends BaseFileReader {
33650
+ async getLoadQuery() {
33651
+ return `
33652
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33653
+ allowed_drivers = ['GeoJSON']
33654
+ );`;
33655
+ }
33656
+ }
33657
+
32762
33658
  async function inferDatasetType(url, typeHint) {
32763
33659
  const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32764
33660
  const fileExtension = fileExtensionMatches && fileExtensionMatches.length
@@ -32823,127 +33719,6 @@ function fetchDataAsArrayBuffer(url, cacheActive) {
32823
33719
  return new Uint8Array(array).buffer;
32824
33720
  });
32825
33721
  }
32826
- function tryParseDate(input) {
32827
- if (typeof input !== 'string')
32828
- return null;
32829
- function tryIso(value) {
32830
- const parsed = parseISO(value);
32831
- return isNaN(parsed.getDate()) ? null : parsed;
32832
- }
32833
- function tryFormat(value, format) {
32834
- const parsed = parse$4(value, format, new Date());
32835
- return isNaN(parsed.getDate()) ? null : parsed;
32836
- }
32837
- return (tryIso(input) ||
32838
- tryFormat(input, 'dd/MM/yyyy') ||
32839
- tryFormat(input, 'dd.MM.yyyy') ||
32840
- tryFormat(input, 'MM/dd/yyyy') ||
32841
- null);
32842
- }
32843
- function tryParseNumber(input) {
32844
- if (isNaN(input))
32845
- return null;
32846
- const parsed = parseFloat(input);
32847
- return isNaN(parsed) ? null : parsed;
32848
- }
32849
- function jsonToGeojsonFeature(object) {
32850
- const { id, properties } = Object.keys(object)
32851
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32852
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32853
- ? {
32854
- ...prev,
32855
- id: object[curr],
32856
- }
32857
- : {
32858
- ...prev,
32859
- properties: { ...prev.properties, [curr]: object[curr] },
32860
- }, { id: undefined, properties: {} });
32861
- return {
32862
- type: 'Feature',
32863
- geometry: null,
32864
- properties,
32865
- ...(id !== undefined && { id }),
32866
- };
32867
- }
32868
- function mutateProperties(items, mutators) {
32869
- const mutatorKeys = Object.keys(mutators);
32870
- for (let i = 0, ii = items.length; i < ii; i++) {
32871
- const item = items[i];
32872
- for (const mutatorField of mutatorKeys) {
32873
- if (!(mutatorField in item.properties))
32874
- continue;
32875
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32876
- }
32877
- }
32878
- return items;
32879
- }
32880
- const SAMPLE_SIZE = 20;
32881
- /**
32882
- * This will infer field types from a list of data items and cast the values accordingly
32883
- * @param items
32884
- * @param inferTypes
32885
- */
32886
- function processItemProperties(items, inferTypes = false) {
32887
- const foundFields = {};
32888
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32889
- const item = items[i];
32890
- const fields = Object.keys(item.properties);
32891
- for (const field of fields) {
32892
- if (!(field in foundFields)) {
32893
- foundFields[field] = {
32894
- label: field,
32895
- name: field,
32896
- type: null,
32897
- };
32898
- }
32899
- const value = item.properties[field];
32900
- const info = foundFields[field];
32901
- if (value === undefined || value === '' || value === null)
32902
- continue;
32903
- if (!inferTypes) {
32904
- if (info.type === null && typeof value === 'number') {
32905
- info.type = 'number';
32906
- }
32907
- else if (info.type === 'number' && typeof value !== 'number') {
32908
- info.type = 'string';
32909
- }
32910
- continue;
32911
- }
32912
- const parsedNumber = tryParseNumber(value);
32913
- if (info.type === null && parsedNumber !== null) {
32914
- info.type = 'number';
32915
- continue;
32916
- }
32917
- else if (info.type === 'number' && parsedNumber === null) {
32918
- info.type = 'string';
32919
- continue;
32920
- }
32921
- const parsedDate = tryParseDate(value);
32922
- if (info.type === null && parsedDate !== null) {
32923
- info.type = 'date';
32924
- }
32925
- else if (info.type === 'date' && parsedDate === null) {
32926
- info.type = 'string';
32927
- }
32928
- }
32929
- }
32930
- const properties = [];
32931
- const mutators = {};
32932
- for (const field in foundFields) {
32933
- const info = foundFields[field];
32934
- if (info.type === 'number') {
32935
- mutators[field] = tryParseNumber;
32936
- }
32937
- else if (info.type === 'date') {
32938
- mutators[field] = tryParseDate;
32939
- }
32940
- properties.push({ ...info, type: info.type || 'string' });
32941
- }
32942
- if (inferTypes) {
32943
- mutateProperties(items, mutators);
32944
- }
32945
- return { items, properties };
32946
- }
32947
33722
  /**
32948
33723
  * This creates a Proxy that allows reading and writing to the data item properties
32949
33724
  * as if it was a simple array of JSON objects
@@ -32965,333 +33740,71 @@ function getJsonDataItemsProxy(items) {
32965
33740
  });
32966
33741
  }
32967
33742
 
32968
- function filterToSql(filter) {
32969
- const operator = filter[0];
32970
- const args = filter.slice(1);
32971
- function valueToSql(value) {
32972
- return typeof value === 'number' ? value : `'${value}'`;
32973
- }
32974
- switch (operator) {
32975
- case '<':
32976
- case '<=':
32977
- case '>':
32978
- case '>=':
32979
- case '=':
32980
- case '!=':
32981
- case 'like':
32982
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32983
- case 'in': {
32984
- const values = args.slice(1);
32985
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
32986
- }
32987
- case 'and':
32988
- case 'or': {
32989
- const children = args
32990
- .map(filterToSql)
32991
- .join(` ${operator.toUpperCase()} `);
32992
- return `(${children})`;
32993
- }
32994
- case 'not':
32995
- return `NOT (${filterToSql(args[0])})`;
32996
- }
32997
- throw new Error(`Could not generate SQL query, operator not recognized: ${operator}`);
32998
- }
32999
- function aggregationToSql(aggregation) {
33000
- const operation = aggregation[0];
33001
- const field = aggregation[1];
33002
- switch (operation) {
33003
- case 'average':
33004
- return `AVG([${field}]) as [average(${field})]`;
33005
- case 'sum':
33006
- case 'max':
33007
- case 'min':
33008
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33009
- case 'count':
33010
- return 'COUNT(*) as [count()]';
33011
- }
33012
- }
33013
- /**
33014
- * Leave arguments at null if not used
33015
- * @param selected
33016
- * @param filter
33017
- * @param sort
33018
- * @param startIndex
33019
- * @param count
33020
- * @param groupBy
33021
- * @param aggregations
33022
- */
33023
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33024
- let sqlSelect = 'SELECT *';
33025
- const sqlFrom = ' FROM ?';
33026
- let sqlOrderBy = '';
33027
- let sqlWhere = '';
33028
- let sqlLimit = '';
33029
- let sqlGroupBy = '';
33030
- if (selected !== null) {
33031
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33032
- }
33033
- if (filter !== null) {
33034
- sqlWhere = ` WHERE ${filterToSql(filter)}`;
33035
- }
33036
- if (sort?.length) {
33037
- sqlOrderBy = ` ORDER BY ${sort
33038
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33039
- .join(', ')}`;
33040
- }
33041
- if (startIndex !== null && count !== null) {
33042
- sqlLimit = ` LIMIT ${count} OFFSET ${startIndex}`;
33043
- }
33044
- if (groupBy !== null && aggregations !== null) {
33045
- sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33046
- const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33047
- const sqlGroupByFields = groupedByDistinct
33048
- .map((group) => `[${group[1]}]`)
33049
- .join(', ');
33050
- const sqlGroupBySelect = groupedByDistinct
33051
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33052
- .join(', ');
33053
- if (sqlGroupByFields && sqlGroupBySelect) {
33054
- sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33055
- sqlSelect += `, ${sqlGroupBySelect}`;
33743
+ class ExcelReader extends BaseFileReader {
33744
+ async getLoadQuery() {
33745
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33746
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33747
+ const bufferHandle = `B${this.datasetId}`;
33748
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33749
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33750
+ const isXls = Array.from(magicNumber)
33751
+ .map((n) => n.toString(16).toUpperCase())
33752
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33753
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33754
+ if (isXls) {
33755
+ buffer = await import('xlsx').then(({ read, utils }) => {
33756
+ const workbook = read(buffer);
33757
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33758
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33759
+ });
33056
33760
  }
33057
- }
33058
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33059
- }
33060
-
33061
- class BaseReader {
33062
- constructor(url) {
33063
- this.url = url;
33064
- this.selected = null;
33065
- this.groupedBy = null;
33066
- this.aggregations = null;
33067
- this.filter = null;
33068
- this.sort = null;
33069
- this.startIndex = null;
33070
- this.count = null;
33071
- }
33072
- load() {
33073
- throw new Error('not implemented');
33074
- }
33075
- get properties() {
33076
- throw new Error('not implemented');
33077
- }
33078
- get info() {
33079
- throw new Error('not implemented');
33080
- }
33081
- read() {
33082
- throw new Error('not implemented');
33083
- }
33084
- selectAll() {
33085
- this.groupedBy = null;
33086
- this.aggregations = null;
33087
- this.selected = null;
33088
- this.filter = null;
33089
- this.startIndex = null;
33090
- this.count = null;
33091
- return this;
33092
- }
33093
- select(...selectedFields) {
33094
- this.selected = selectedFields;
33095
- this.aggregations = null; // clear aggregations & groups when selecting fields
33096
- this.groupedBy = null;
33097
- return this;
33098
- }
33099
- groupBy(...groupBy) {
33100
- this.groupedBy = groupBy;
33101
- this.selected = null; // clear normal field selection when aggregating
33102
- return this;
33103
- }
33104
- aggregate(...aggregations) {
33105
- this.aggregations = aggregations;
33106
- return this;
33107
- }
33108
- where(filter) {
33109
- this.filter = filter;
33110
- return this;
33111
- }
33112
- orderBy(...fieldSorts) {
33113
- this.sort = fieldSorts;
33114
- return this;
33115
- }
33116
- limit(startIndex, count) {
33117
- this.startIndex = startIndex;
33118
- this.count = count;
33119
- return this;
33120
- }
33121
- }
33122
-
33123
- class BaseCacheReader extends BaseReader {
33124
- constructor(url, cacheActive = true) {
33125
- super(url);
33126
- this.url = url;
33127
- this.cacheActive = cacheActive;
33128
- }
33129
- setCacheActive(value) {
33130
- this.cacheActive = value;
33131
- }
33132
- }
33133
-
33134
- class BaseFileReader extends BaseCacheReader {
33135
- getData() {
33136
- throw new Error('not implemented');
33137
- }
33138
- load() {
33139
- this.parseResult_ = this.getData();
33140
- }
33141
- get properties() {
33142
- return this.parseResult_.then((result) => result.properties);
33143
- }
33144
- get info() {
33145
- return this.parseResult_.then((result) => ({
33146
- itemsCount: result.items.length,
33147
- }));
33148
- }
33149
- async read() {
33150
- const items = (await this.parseResult_).items;
33151
- // no query defined: return the full results as is
33152
- if (this.groupedBy == null &&
33153
- this.aggregations == null &&
33154
- this.selected == null &&
33155
- this.sort == null &&
33156
- this.filter == null &&
33157
- this.startIndex == null &&
33158
- this.count == null) {
33159
- return items;
33160
- }
33161
- const jsonItems = getJsonDataItemsProxy(items);
33162
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33163
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33164
- return result.map(jsonToGeojsonFeature);
33165
- }
33166
- }
33167
-
33168
- function parseCsv(text) {
33169
- // first parse the header to guess the delimiter
33170
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33171
- let delimiter;
33172
- try {
33173
- const header = text.split('\n')[0];
33174
- const result = Papa.parse(header, {
33175
- header: false,
33176
- });
33177
- delimiter = result.meta.delimiter;
33178
- }
33179
- catch (e) {
33180
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33181
- }
33182
- const parsed = Papa.parse(text, {
33183
- header: true,
33184
- skipEmptyLines: true,
33185
- delimiter,
33186
- });
33187
- if (parsed.errors.length) {
33188
- throw new Error('CSV parsing failed for the following reasons:\n' +
33189
- parsed.errors
33190
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33191
- .join('\n'));
33192
- }
33193
- const items = parsed.data.map(jsonToGeojsonFeature);
33194
- return processItemProperties(items, true);
33195
- }
33196
- class CsvReader extends BaseFileReader {
33197
- getData() {
33198
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33761
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33762
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33763
+ return `
33764
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33199
33765
  }
33200
33766
  }
33201
33767
 
33202
- /**
33203
- * This parser only supports arrays of simple flat objects with properties
33204
- * @param text
33205
- */
33206
- function parseJson(text) {
33207
- const parsed = JSON.parse(text);
33208
- if (!Array.isArray(parsed)) {
33209
- throw new Error('Could not parse JSON, expected an array at root level');
33210
- }
33211
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33212
- }
33213
- class JsonReader extends BaseFileReader {
33214
- getData() {
33215
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33768
+ class GmlReader extends BaseFileReader {
33769
+ async getLoadQuery() {
33770
+ return `
33771
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33772
+ allowed_drivers = ['GML']
33773
+ );`;
33216
33774
  }
33217
33775
  }
33218
33776
 
33219
- /**
33220
- * This parser supports both Geojson Feature collections or arrays
33221
- * of Features
33222
- * @param text
33223
- */
33777
+ const formatGeojson = new GeoJSON$1();
33224
33778
  function parseGeojson(text) {
33225
33779
  const parsed = JSON.parse(text);
33226
33780
  const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33227
33781
  if (!Array.isArray(features)) {
33228
33782
  throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33229
33783
  }
33230
- return processItemProperties(features);
33231
- }
33232
- class GeojsonReader extends BaseFileReader {
33233
- getData() {
33234
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33235
- }
33236
- }
33237
-
33238
- /**
33239
- * This will read the first sheet of the excel workbook and expect the first
33240
- * line to contain the properties names
33241
- * @param buffer
33242
- */
33243
- function parseExcel(buffer) {
33244
- return import('xlsx').then(({ read, utils }) => {
33245
- const workbook = read(buffer);
33246
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33247
- let json = utils.sheet_to_json(sheet);
33248
- if (!json.length) {
33249
- json = [];
33250
- }
33251
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33252
- });
33253
- }
33254
- class ExcelReader extends BaseFileReader {
33255
- getData() {
33256
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33257
- }
33784
+ return features;
33258
33785
  }
33259
-
33260
- function parseGml(text, namespace, version) {
33261
- const splittedNamespace = namespace.split(':');
33262
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33786
+ function parseGml(text, featureType, version) {
33787
+ const parts = featureType.split(':');
33788
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33263
33789
  const match = regex.exec(text);
33264
33790
  if (match && match.length >= 2) {
33265
- const wf = new WFS({
33791
+ const wfs = new WFS({
33266
33792
  featureNS: match[1],
33267
- featureType: splittedNamespace[1],
33793
+ featureType: parts[1],
33268
33794
  version: version,
33269
33795
  });
33270
33796
  let features;
33271
33797
  try {
33272
- features = wf.readFeatures(text);
33798
+ features = wfs.readFeatures(text);
33273
33799
  }
33274
33800
  catch (e) {
33275
- throw Error("Couldn't parse WFS with GML features");
33801
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33276
33802
  }
33277
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33278
- return processItemProperties(geojsonItem.features, true);
33803
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33804
+ return geojsonItem.features;
33279
33805
  }
33280
33806
  throw Error("Couldn't retrieve namespace url");
33281
33807
  }
33282
- class GmlReader extends BaseFileReader {
33283
- constructor(url, namespace, version, cacheActive = true) {
33284
- super(url);
33285
- this.url = url;
33286
- this.namespace = namespace;
33287
- this.version = version;
33288
- this.cacheActive = cacheActive;
33289
- }
33290
- getData() {
33291
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33292
- }
33293
- }
33294
-
33295
33808
  async function getWfsEndpoint(wfsUrl) {
33296
33809
  try {
33297
33810
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33319,21 +33832,65 @@ async function getWfsEndpoint(wfsUrl) {
33319
33832
  }
33320
33833
  }
33321
33834
  }
33322
- class WfsReader extends BaseCacheReader {
33323
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33324
- super(url, cacheActive);
33325
- this.endpoint = wfsEndpoint;
33326
- this.featureTypeName = featureTypeName;
33327
- this.version = this.endpoint.getVersion();
33835
+ class WfsReader extends BaseReader {
33836
+ constructor(url, featureTypeName) {
33837
+ super(url);
33838
+ this.endpoint = getWfsEndpoint(url);
33839
+ this.featureType = this.endpoint
33840
+ .then((endpoint) => {
33841
+ const featureTypes = endpoint.getFeatureTypes();
33842
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33843
+ ? featureTypes[0].name
33844
+ : featureTypeName);
33845
+ })
33846
+ .then((featureType) => {
33847
+ if (!featureType) {
33848
+ throw new Error('wfs.featuretype.notfound');
33849
+ }
33850
+ return featureType;
33851
+ });
33852
+ }
33853
+ get backupReader() {
33854
+ if (this.backupReader_) {
33855
+ return this.backupReader_;
33856
+ }
33857
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33858
+ let reader;
33859
+ if (endpoint.supportsJson(featureType.name)) {
33860
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33861
+ asJson: true,
33862
+ outputCrs: 'EPSG:4326',
33863
+ }));
33864
+ }
33865
+ else {
33866
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33867
+ (featureType.defaultCrs === 'EPSG:4326' ||
33868
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33869
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33870
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33871
+ outputCrs: 'EPSG:4326',
33872
+ }));
33873
+ }
33874
+ }
33875
+ reader.enableCache(this.cacheEnabled);
33876
+ reader.load();
33877
+ return reader;
33878
+ });
33879
+ return this.backupReader_;
33328
33880
  }
33329
33881
  get properties() {
33330
- return this.endpoint
33331
- .getFeatureTypeFull(this.featureTypeName)
33332
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33882
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33333
33883
  const originalType = featureType.properties[prop];
33334
- const type = originalType === 'float' || originalType === 'integer'
33335
- ? 'number'
33336
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33884
+ let type;
33885
+ if (originalType === 'float' || originalType === 'integer') {
33886
+ type = 'number';
33887
+ }
33888
+ else if (originalType === 'boolean') {
33889
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33890
+ }
33891
+ else {
33892
+ type = originalType;
33893
+ }
33337
33894
  return {
33338
33895
  name: prop,
33339
33896
  label: prop,
@@ -33342,83 +33899,63 @@ class WfsReader extends BaseCacheReader {
33342
33899
  }));
33343
33900
  }
33344
33901
  get info() {
33345
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33902
+ return this.featureType.then((result) => ({
33346
33903
  itemsCount: result.objectCount,
33904
+ hasGeometry: !!result.geometryName,
33347
33905
  }));
33348
33906
  }
33349
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33350
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33351
- const featureTypes = wfsEndpoint.getFeatureTypes();
33352
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33353
- ? featureTypes[0].name
33354
- : featureTypeName);
33355
- if (!featureType) {
33356
- throw new Error('wfs.featuretype.notfound');
33357
- }
33358
- if (wfsEndpoint.supportsStartIndex()) {
33359
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33360
- }
33361
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33362
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33363
- asJson: true,
33364
- outputCrs: 'EPSG:4326',
33365
- }));
33366
- }
33367
- else {
33368
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33369
- (featureType.defaultCrs === 'EPSG:4326' ||
33370
- featureType.otherCrs?.includes('EPSG:4326'))) {
33371
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33372
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33373
- outputCrs: 'EPSG:4326',
33374
- }), featureType.name, wfsEndpoint.getVersion());
33375
- }
33376
- throw new Error('wfs.geojsongml.notsupported');
33377
- }
33907
+ load() {
33908
+ // Nothing to load for Wfs
33378
33909
  }
33379
- async getData(aggregation, groupedBy) {
33380
- if (aggregation || groupedBy) {
33381
- return { items: await this.getQueryData() };
33910
+ async read() {
33911
+ const endpoint = await this.endpoint;
33912
+ const featureType = await this.featureType;
33913
+ // if we can't use the WFS protocol we fall back to the backup reader
33914
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33915
+ const backupReader = await this.backupReader;
33916
+ backupReader.selectAll();
33917
+ if (this.selected) {
33918
+ backupReader.select(...this.selected);
33919
+ }
33920
+ if (this.aggregations) {
33921
+ backupReader.aggregate(...this.aggregations);
33922
+ }
33923
+ if (this.groupedBy) {
33924
+ backupReader.groupBy(...this.groupedBy);
33925
+ }
33926
+ if (this.sort) {
33927
+ backupReader.orderBy(...this.sort);
33928
+ }
33929
+ if (this.startIndex !== null && this.count !== null) {
33930
+ backupReader.limit(this.startIndex, this.count);
33931
+ }
33932
+ return backupReader.read();
33382
33933
  }
33383
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33934
+ const asJson = endpoint.supportsJson(featureType.name);
33384
33935
  const attributes = this.selected ?? undefined;
33385
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33936
+ let sortBy = null;
33937
+ if (this.sort) {
33938
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33939
+ sortBy = Array.isArray(this.sort[0])
33940
+ ? this.sort.map(mapSort)
33941
+ : mapSort(this.sort);
33942
+ }
33943
+ const url = endpoint.getFeatureUrl(featureType.name, {
33386
33944
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33387
33945
  ...(this.count !== null && { maxFeatures: this.count }),
33388
33946
  asJson,
33389
33947
  outputCrs: 'EPSG:4326',
33390
33948
  attributes,
33391
- // sortBy: this.sort // TODO: no sort in ogc-client?
33949
+ sortBy,
33392
33950
  });
33393
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33394
- const finalUrl = new URL(url);
33395
- const sorts = this.sort
33396
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33397
- .join(',');
33398
- // Direct update on string url to prevent encoding of +A and +D
33399
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33400
- }
33401
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33951
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33402
33952
  ? parseGeojson(text)
33403
- : parseGml(text, this.featureTypeName, this.version));
33404
- }
33405
- async getQueryData() {
33406
- const items = (await this.getData()).items;
33407
- const jsonItems = getJsonDataItemsProxy(items);
33408
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33409
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33410
- return result.map(jsonToGeojsonFeature);
33411
- }
33412
- load() {
33413
- // Nothing to load for Wfs
33414
- }
33415
- async read() {
33416
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33953
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33417
33954
  }
33418
33955
  }
33419
33956
 
33420
- async function openDataset(url, typeHint, options, cacheActive) {
33421
- const fileType = await inferDatasetType(url, typeHint);
33957
+ async function openDataset(url, options) {
33958
+ const fileType = await inferDatasetType(url, options?.typeHint);
33422
33959
  let reader;
33423
33960
  try {
33424
33961
  switch (fileType) {
@@ -33435,18 +33972,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33435
33972
  reader = new ExcelReader(url);
33436
33973
  break;
33437
33974
  case 'gml':
33438
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33975
+ reader = new GmlReader(url);
33439
33976
  break;
33440
33977
  case 'wfs':
33441
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33978
+ reader = new WfsReader(url, options?.wfsFeatureType);
33442
33979
  break;
33443
33980
  }
33444
- reader.setCacheActive(cacheActive);
33981
+ reader.enableCache(options?.enableCache ?? true);
33445
33982
  reader.load();
33446
33983
  return reader;
33447
33984
  }
33448
33985
  catch (e) {
33449
- //WfsReader may already raise a FetchError
33986
+ // WfsReader may already raise a FetchError
33450
33987
  if (e instanceof FetchError)
33451
33988
  throw e;
33452
33989
  else
@@ -33457,13 +33994,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33457
33994
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33458
33995
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33459
33996
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33460
- * File type is determined liked so:
33997
+ * File type is determined like so:
33461
33998
  * 1. if a type hint is given, use it
33462
33999
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33463
34000
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33464
34001
  */
33465
- async function readDataset(url, typeHint, options, cacheActive = true) {
33466
- const reader = await openDataset(url, typeHint, options, cacheActive);
34002
+ async function readDataset(url, options) {
34003
+ const reader = await openDataset(url, options);
33467
34004
  try {
33468
34005
  return await reader.read();
33469
34006
  }
@@ -33694,9 +34231,11 @@ class DataService {
33694
34231
  getDataset(link, cacheActive) {
33695
34232
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33696
34233
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33697
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34234
+ return from(openDataset(wfsUrlEndpoint, {
34235
+ typeHint: 'wfs',
33698
34236
  wfsFeatureType: link.name,
33699
- }, cacheActive));
34237
+ enableCache: cacheActive,
34238
+ }));
33700
34239
  }
33701
34240
  else if (link.type === 'download') {
33702
34241
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33704,12 +34243,15 @@ class DataService {
33704
34243
  const supportedType = SupportedTypes.indexOf(format) > -1
33705
34244
  ? format
33706
34245
  : undefined;
33707
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34246
+ return from(openDataset(linkProxifiedUrl, {
34247
+ typeHint: supportedType,
34248
+ enableCache: cacheActive,
34249
+ })).pipe();
33708
34250
  }
33709
34251
  else if (link.type === 'service' &&
33710
34252
  link.accessServiceProtocol === 'esriRest') {
33711
34253
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33712
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34254
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33713
34255
  }
33714
34256
  else if (link.type === 'service' &&
33715
34257
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33723,7 +34265,10 @@ class DataService {
33723
34265
  }
33724
34266
  const urlWithoutLimit = new URL(geojsonUrl);
33725
34267
  urlWithoutLimit.searchParams.delete('limit');
33726
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34268
+ return openDataset(urlWithoutLimit.toString(), {
34269
+ typeHint: 'geojson',
34270
+ enableCache: cacheActive,
34271
+ });
33727
34272
  }));
33728
34273
  }
33729
34274
  return throwError(() => 'protocol not supported');
@@ -33975,20 +34520,31 @@ class DataTableComponent {
33975
34520
  this.eltRef = inject(ElementRef);
33976
34521
  this.cdr = inject(ChangeDetectorRef);
33977
34522
  this.translateService = inject(TranslateService);
33978
- this._featureAttributes = [];
34523
+ this.columnsFromFeatureCatalog = null;
34524
+ this.columnsFromDataset = [];
33979
34525
  this.selected = new EventEmitter();
33980
- this.properties$ = new BehaviorSubject(null);
33981
34526
  this.loading$ = new BehaviorSubject(false);
33982
34527
  this.error = null;
33983
34528
  }
33984
34529
  set featureAttributes(value) {
33985
- this._featureAttributes = value;
33986
- this.properties$.next(value.map((attr) => attr.value));
34530
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34531
+ name: attrs.value,
34532
+ label: attrs.label,
34533
+ }));
33987
34534
  }
33988
34535
  set dataset(value) {
33989
34536
  this.dataset_ = value;
33990
34537
  this.dataset_.load();
33991
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34538
+ this.dataset_.info.then((info) => {
34539
+ this.count = info.itemsCount;
34540
+ this.cdr.detectChanges();
34541
+ });
34542
+ }
34543
+ get columns() {
34544
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34545
+ }
34546
+ get columnNames() {
34547
+ return this.columns.map((c) => c.name);
33992
34548
  }
33993
34549
  ngOnInit() {
33994
34550
  this.dataSource = new DataTableDataSource();
@@ -34023,11 +34579,12 @@ class DataTableComponent {
34023
34579
  }
34024
34580
  async readData() {
34025
34581
  this.loading$.next(true);
34026
- // wait for properties to be read
34027
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34028
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34029
- this.dataset_.select(...propsWithoutGeom);
34030
34582
  try {
34583
+ // wait for properties to be read
34584
+ if (!this.columnsFromFeatureCatalog) {
34585
+ this.columnsFromDataset = await this.dataset_.properties;
34586
+ }
34587
+ this.dataset_.select(...this.columnNames);
34031
34588
  await this.dataSource.showData(this.dataset_.read());
34032
34589
  this.error = null;
34033
34590
  }
@@ -34057,7 +34614,7 @@ class DataTableComponent {
34057
34614
  }
34058
34615
  }
34059
34616
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34060
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34617
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34061
34618
  }
34062
34619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34063
34620
  type: Component,
@@ -34069,10 +34626,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34069
34626
  CommonModule,
34070
34627
  LoadingMaskComponent,
34071
34628
  PopupAlertComponent,
34072
- LetDirective,
34073
34629
  TranslatePipe,
34074
34630
  TranslateDirective,
34075
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34631
+ ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34076
34632
  }], propDecorators: { featureAttributes: [{
34077
34633
  type: Input
34078
34634
  }], dataset: [{
@@ -34175,7 +34731,7 @@ class ChartViewComponent {
34175
34731
  }), shareReplay$1(1));
34176
34732
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34177
34733
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34178
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34734
+ .filter((prop) => prop.type === 'number')
34179
34735
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34180
34736
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34181
34737
  const newProp = choices[0]?.value || '';
@@ -34202,6 +34758,7 @@ class ChartViewComponent {
34202
34758
  this.aggregation$,
34203
34759
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34204
34760
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34761
+ this.loading = true;
34205
34762
  return dataset
34206
34763
  .groupBy(['distinct', xProp])
34207
34764
  .aggregate(fieldAgg)
@@ -34443,6 +35000,7 @@ class GeoTableViewComponent {
34443
35000
  }));
34444
35001
  }
34445
35002
  async initMapContext() {
35003
+ this.dataset.load();
34446
35004
  this.dataset.selectAll();
34447
35005
  return {
34448
35006
  layers: [
@@ -34616,8 +35174,7 @@ class MdViewFacade {
34616
35174
  }));
34617
35175
  this.error$ = this.store.pipe(select(getMetadataError));
34618
35176
  this.related$ = this.store.pipe(select(getRelated));
34619
- this.sources$ = this.store.pipe(select(getSources));
34620
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35177
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34621
35178
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34622
35179
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34623
35180
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34736,12 +35293,9 @@ class MdViewEffects {
34736
35293
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34737
35294
  return setRelated({ related });
34738
35295
  }), catchError(() => of(setRelated({ related: null })))));
34739
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34740
- return setSources({ sources });
34741
- }), catchError(() => of(setSources({ sources: null })))));
34742
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34743
- return setSourceOf({ sourceOf });
34744
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
35296
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35297
+ return setLinkedRecords({ linkedRecords });
35298
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34745
35299
  /*
34746
35300
  UserFeedback effects
34747
35301
  */
@@ -37442,7 +37996,7 @@ class GenericKeywordsComponent {
37442
37996
  this.deletedKeyword.emit(keyword);
37443
37997
  }
37444
37998
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37445
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37999
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37446
38000
  }
37447
38001
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, decorators: [{
37448
38002
  type: Component,
@@ -38034,7 +38588,7 @@ class FormFieldContactsForResourceComponent {
38034
38588
  provideNgIconsConfig({
38035
38589
  size: '1.5rem',
38036
38590
  }),
38037
- ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38591
+ ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38038
38592
  }
38039
38593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsForResourceComponent, decorators: [{
38040
38594
  type: Component,
@@ -38134,7 +38688,7 @@ class FormFieldContactsComponent {
38134
38688
  this.subscription.unsubscribe();
38135
38689
  }
38136
38690
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38137
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38691
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38138
38692
  }
38139
38693
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, decorators: [{
38140
38694
  type: Component,
@@ -38487,7 +39041,7 @@ class OnlineServiceResourceInputComponent {
38487
39041
  provideNgIconsConfig({
38488
39042
  size: '1.5em',
38489
39043
  }),
38490
- ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39044
+ ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38491
39045
  }
38492
39046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OnlineServiceResourceInputComponent, decorators: [{
38493
39047
  type: Component,
@@ -40020,7 +40574,7 @@ function flattenQueryParams(params) {
40020
40574
  const start = flattened[key].start;
40021
40575
  const end = flattened[key].end;
40022
40576
  flattened[key] = [
40023
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40577
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40024
40578
  ];
40025
40579
  }
40026
40580
  }
@@ -40377,5 +40931,5 @@ const CHART_TYPE_VALUES = [
40377
40931
  * Generated bundle index. Do not edit.
40378
40932
  */
40379
40933
 
40380
- export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40934
+ 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, GEOCODING_PROVIDER, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingService, 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, LocationSearchComponent, 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, ResourceCreationRevisionDateSearchField, 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 };
40381
40935
  //# sourceMappingURL=geonetwork-ui.mjs.map