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

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 (95) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1506 -938
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +295 -127
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +130 -37
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/map/src/index.ts +0 -1
  18. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  19. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  20. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  21. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  22. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  23. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  24. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  25. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  26. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  27. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  28. package/src/libs/feature/search/src/index.ts +2 -0
  29. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  30. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  31. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +5 -1
  32. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  33. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  34. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  35. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  36. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  37. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  38. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  40. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  41. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  42. package/src/libs/ui/inputs/src/index.ts +2 -0
  43. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  44. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  45. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  46. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  48. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  49. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  50. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  52. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  53. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  54. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  55. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  56. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  57. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  58. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
  59. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  60. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  61. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  62. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  63. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  64. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  65. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  66. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  67. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  68. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  69. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  70. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  71. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  72. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  73. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  74. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  75. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  76. package/src/libs/util/shared/src/index.ts +1 -0
  77. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  78. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  79. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  80. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  81. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  82. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  83. package/tailwind.base.css +5 -0
  84. package/translations/de.json +18 -4
  85. package/translations/en.json +17 -3
  86. package/translations/es.json +15 -1
  87. package/translations/fr.json +18 -4
  88. package/translations/it.json +16 -2
  89. package/translations/nl.json +15 -1
  90. package/translations/pt.json +15 -1
  91. package/translations/sk.json +16 -2
  92. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  93. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  94. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  95. /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.96d0c6ce2";
20442
+ var version = "2.11.0-dev.9bd41a961";
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']) {
@@ -20728,10 +20806,28 @@ class ElasticsearchService {
20728
20806
  };
20729
20807
  }
20730
20808
  buildPayloadSort(sortBy) {
20731
- if (sortBy === null)
20809
+ if (!sortBy || sortBy.length === 0)
20732
20810
  return undefined;
20733
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy];
20734
- return fields.map((field) => ({ [field[1]]: field[0] }));
20811
+ const fields = Array.isArray(sortBy[0])
20812
+ ? sortBy
20813
+ : [sortBy];
20814
+ return fields.map((field) => {
20815
+ // Sort by nested array of dates only works with the explicit syntax
20816
+ if (field[1].endsWith('.date')) {
20817
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'));
20818
+ return {
20819
+ [field[1]]: {
20820
+ order: field[0],
20821
+ mode: field[0] === 'desc' ? 'max' : 'min',
20822
+ missing: '_last',
20823
+ nested: {
20824
+ path: nestedPath,
20825
+ },
20826
+ },
20827
+ };
20828
+ }
20829
+ return { [field[1]]: field[0] };
20830
+ });
20735
20831
  }
20736
20832
  injectLangInQueryStringFields(queryFieldsPriority) {
20737
20833
  const queryLang = this.getQueryLang();
@@ -20767,7 +20863,49 @@ class ElasticsearchService {
20767
20863
  isCurrentSearchLang() {
20768
20864
  return this.metadataLang === 'current';
20769
20865
  }
20770
- 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)) {
20771
20909
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20772
20910
  const makeQuery = (filter) => {
20773
20911
  if (typeof filter === 'string') {
@@ -20786,7 +20924,9 @@ class ElasticsearchService {
20786
20924
  ? filters
20787
20925
  : Object.keys(filters)
20788
20926
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20927
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20789
20928
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20929
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20790
20930
  .filter((fieldname) => filters[fieldname] &&
20791
20931
  JSON.stringify(filters[fieldname]) !== '{}')
20792
20932
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20794,31 +20934,26 @@ class ElasticsearchService {
20794
20934
  if (filters['gn-ui-crossFieldFilter']) {
20795
20935
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`;
20796
20936
  }
20797
- const queryRange = Object.entries(filters)
20798
- .filter(([, value]) => isDateRange(value))
20799
- .map(([searchField, dateRange]) => {
20800
- return {
20801
- searchField,
20802
- dateRange,
20803
- };
20804
- })[0];
20937
+ const queryRanges = Object.entries(filters).filter(([, value]) => isDateRange(value));
20805
20938
  const queryParts = [
20806
20939
  queryString && {
20807
20940
  query_string: {
20808
20941
  query: queryString,
20809
20942
  },
20810
20943
  },
20811
- queryRange &&
20812
- queryRange.dateRange && {
20813
- range: {
20814
- [queryRange.searchField]: {
20815
- ...(queryRange.dateRange.start && {
20816
- gte: formatDate(queryRange.dateRange.start),
20817
- }),
20818
- ...(queryRange.dateRange.end && {
20819
- lte: formatDate(queryRange.dateRange.end),
20820
- }),
20821
- 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',
20822
20957
  },
20823
20958
  },
20824
20959
  },
@@ -20848,7 +20983,9 @@ class ElasticsearchService {
20848
20983
  },
20849
20984
  });
20850
20985
  }
20851
- 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);
20852
20989
  if (queryFilters) {
20853
20990
  filter.push(...queryFilters);
20854
20991
  }
@@ -20859,7 +20996,10 @@ class ElasticsearchService {
20859
20996
  },
20860
20997
  });
20861
20998
  }
20862
- if (geometry) {
20999
+ const boostGeometry = spatialFilterExtent
21000
+ ? bboxToPolygon(spatialFilterExtent)
21001
+ : geometry;
21002
+ if (boostGeometry) {
20863
21003
  // boosts applied using the filter geometry:
20864
21004
  // * records completely within the geometry receive a boost of 5
20865
21005
  // * records intersecting the geometry receive a boost of 2
@@ -20868,7 +21008,7 @@ class ElasticsearchService {
20868
21008
  should.push({
20869
21009
  geo_shape: {
20870
21010
  geom: {
20871
- shape: geometry,
21011
+ shape: boostGeometry,
20872
21012
  relation: 'within',
20873
21013
  },
20874
21014
  boost: 5.0,
@@ -20876,7 +21016,7 @@ class ElasticsearchService {
20876
21016
  }, {
20877
21017
  geo_shape: {
20878
21018
  geom: {
20879
- shape: geometry,
21019
+ shape: boostGeometry,
20880
21020
  relation: 'intersects',
20881
21021
  },
20882
21022
  boost: 2.0,
@@ -20885,7 +21025,7 @@ class ElasticsearchService {
20885
21025
  // this will boost the results variably depending on their distance from the given geometry
20886
21026
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20887
21027
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20888
- const bbox = getGeometryBoundingBox(geometry);
21028
+ const bbox = getGeometryBoundingBox(boostGeometry);
20889
21029
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20890
21030
  const northToCenter = new LineString([
20891
21031
  [center[0], bbox[3]],
@@ -21211,7 +21351,7 @@ class Gn4Repository {
21211
21351
  }
21212
21352
  getRecord(uniqueIdentifier) {
21213
21353
  return this.gn4SearchApi
21214
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21354
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21215
21355
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21216
21356
  }
21217
21357
  getMultipleRecords(uniqueIdentifiers) {
@@ -21265,19 +21405,28 @@ class Gn4Repository {
21265
21405
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21266
21406
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21267
21407
  }
21268
- getSources(record) {
21269
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21270
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21271
- return this.getMultipleRecords(sourcesIdentifiers);
21272
- }
21273
- return of(null);
21274
- }
21275
- getSourceOf(record) {
21276
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21277
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21278
- return this.getMultipleRecords(sourceOfIdentifiers);
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([]);
21279
21422
  }
21280
- 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()));
21281
21430
  }
21282
21431
  aggregate(params) {
21283
21432
  // if aggregations are empty, return an empty object right away
@@ -22466,48 +22615,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22466
22615
  type: Injectable
22467
22616
  }] });
22468
22617
 
22469
- const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider');
22470
- class GeocodingService {
22471
- constructor() {
22472
- this.provider = inject(GEOCODING_PROVIDER);
22473
- }
22474
- query(text) {
22475
- let queryObservable;
22476
- switch (this.provider[0]) {
22477
- case 'geoadmin':
22478
- queryObservable = from(queryGeoadmin(text, this.provider[1]));
22479
- break;
22480
- case 'geonames':
22481
- queryObservable = from(queryGeonames(text, this.provider[1]));
22482
- break;
22483
- case 'data-gouv-fr':
22484
- queryObservable = from(queryDataGouvFr(text, this.provider[1]));
22485
- break;
22486
- default:
22487
- return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
22488
- }
22489
- return queryObservable.pipe(catchError((error) => throwError(error)));
22490
- }
22491
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22492
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
22493
- }
22494
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
22495
- type: Injectable,
22496
- args: [{
22497
- providedIn: 'root',
22498
- }]
22499
- }] });
22500
-
22501
22618
  class FeatureMapModule {
22502
22619
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
22503
22620
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, imports: [i1.StoreFeatureModule] }); }
22504
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [
22505
- MapFacade,
22506
- {
22507
- provide: GEOCODING_PROVIDER,
22508
- useValue: ['geonames', { maxRows: 5 }],
22509
- },
22510
- ], 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)] }); }
22511
22622
  }
22512
22623
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, decorators: [{
22513
22624
  type: NgModule,
@@ -22515,13 +22626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22515
22626
  imports: [
22516
22627
  StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer),
22517
22628
  ],
22518
- providers: [
22519
- MapFacade,
22520
- {
22521
- provide: GEOCODING_PROVIDER,
22522
- useValue: ['geonames', { maxRows: 5 }],
22523
- },
22524
- ],
22629
+ providers: [MapFacade],
22525
22630
  }]
22526
22631
  }] });
22527
22632
 
@@ -22538,9 +22643,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22538
22643
  });
22539
22644
 
22540
22645
  const DEFAULT_BASEMAP_LAYER = {
22541
- type: 'xyz',
22542
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22543
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22646
+ type: 'maplibre-style',
22647
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22648
+ clickable: false,
22649
+ hoverable: false,
22544
22650
  };
22545
22651
  const DEFAULT_VIEW = {
22546
22652
  center: [0, 15],
@@ -23255,6 +23361,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23255
23361
  'search_preset',
23256
23362
  'advanced_filters',
23257
23363
  'limit',
23364
+ 'spatial_extent_max_file_size',
23258
23365
  ], warnings, errors);
23259
23366
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23260
23367
  searchConfig =
@@ -23272,6 +23379,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23272
23379
  })),
23273
23380
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23274
23381
  LIMIT: parsedSearchSection.limit,
23382
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23275
23383
  };
23276
23384
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23277
23385
  metadataQualityConfig =
@@ -24329,7 +24437,7 @@ class AutocompleteComponent {
24329
24437
  this.enterBtnPosition = event.target['value'].length * 8 + 80;
24330
24438
  }
24331
24439
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24332
- 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: [
24333
24441
  provideIcons({
24334
24442
  iconoirSearch,
24335
24443
  matClose,
@@ -24338,7 +24446,7 @@ class AutocompleteComponent {
24338
24446
  provideNgIconsConfig({
24339
24447
  size: '1.75rem',
24340
24448
  }),
24341
- ], 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 }); }
24342
24450
  }
24343
24451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, decorators: [{
24344
24452
  type: Component,
@@ -24359,7 +24467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24359
24467
  provideNgIconsConfig({
24360
24468
  size: '1.75rem',
24361
24469
  }),
24362
- ], 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"] }]
24363
24471
  }], propDecorators: { placeholder: [{
24364
24472
  type: Input
24365
24473
  }], enterButton: [{
@@ -24397,6 +24505,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24397
24505
  }], inputRef: [{
24398
24506
  type: ViewChild,
24399
24507
  args: ['searchInput']
24508
+ }], displayWithTemplate: [{
24509
+ type: Input
24400
24510
  }], displayWithFn: [{
24401
24511
  type: Input
24402
24512
  }] } });
@@ -24529,12 +24639,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24529
24639
  type: Input
24530
24640
  }] } });
24531
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
+
24532
24660
  class DatePickerComponent {
24533
24661
  constructor() {
24534
- this.dateAdapter = inject(DateAdapter);
24535
- this.translate = inject(TranslateService);
24536
24662
  this.dateChange = new EventEmitter();
24537
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24538
24663
  }
24539
24664
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24540
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: [
@@ -24542,30 +24667,19 @@ class DatePickerComponent {
24542
24667
  provideNgIconsConfig({
24543
24668
  size: '1.5rem',
24544
24669
  }),
24545
- {
24546
- provide: MAT_DATE_LOCALE,
24547
- useFactory: (locale) => locale,
24548
- },
24549
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
24550
24672
  }
24551
24673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24552
24674
  type: Component,
24553
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24554
- MatNativeDateModule,
24555
- MatDatepickerModule,
24556
- ButtonComponent,
24557
- NgIconComponent,
24558
- ], providers: [
24675
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24559
24676
  provideIcons({ iconoirCalendar }),
24560
24677
  provideNgIconsConfig({
24561
24678
  size: '1.5rem',
24562
24679
  }),
24563
- {
24564
- provide: MAT_DATE_LOCALE,
24565
- useFactory: (locale) => locale,
24566
- },
24680
+ provideLocalizedDateAdapter(),
24567
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"] }]
24568
- }], ctorParameters: () => [], propDecorators: { date: [{
24682
+ }], propDecorators: { date: [{
24569
24683
  type: Input
24570
24684
  }], dateChange: [{
24571
24685
  type: Output
@@ -24573,58 +24687,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24573
24687
 
24574
24688
  class DateRangeDropdownComponent {
24575
24689
  constructor() {
24576
- this.overlayContainer = inject(OverlayContainer);
24690
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24691
+ this.dateAdapter = inject(DateAdapter);
24692
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24577
24693
  this.cdr = inject(ChangeDetectorRef);
24578
- this.startDateChange = new EventEmitter();
24579
- this.endDateChange = new EventEmitter();
24580
- 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());
24581
24720
  }
24582
- ngAfterViewChecked() {
24583
- this.checkPickerOverlay();
24721
+ get startDate() {
24722
+ return this.dateRange.start;
24584
24723
  }
24585
- checkPickerOverlay() {
24586
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24587
- setTimeout(() => {
24588
- this.isPickerDisplayed =
24589
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24590
- null;
24591
- this.cdr.detectChanges();
24592
- }, 200); // FIXME: find a better way to deal with animation delay
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);
24593
24796
  }
24594
24797
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24595
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", startDate: "startDate", endDate: "endDate" }, outputs: { startDateChange: "startDateChange", endDateChange: "endDateChange" }, providers: [
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: [
24596
24799
  provideIcons({
24597
- matExpandMore,
24800
+ iconoirCalendar,
24801
+ matClose,
24598
24802
  matExpandLess,
24803
+ matExpandMore,
24804
+ }),
24805
+ provideNgIconsConfig({
24806
+ size: '1.5rem',
24599
24807
  }),
24600
- ], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"], dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }] }); }
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 }); }
24601
24810
  }
24602
24811
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24603
24812
  type: Component,
24604
24813
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24605
- NgIconComponent,
24606
- MatNativeDateModule,
24607
- MatDatepickerModule,
24814
+ AutofocusDirective,
24608
24815
  ButtonComponent,
24816
+ MatDatepickerModule,
24817
+ NgIcon,
24818
+ NgTemplateOutlet,
24819
+ OverlayModule,
24820
+ TranslateDirective,
24821
+ TranslatePipe,
24609
24822
  ], providers: [
24610
24823
  provideIcons({
24611
- matExpandMore,
24824
+ iconoirCalendar,
24825
+ matClose,
24612
24826
  matExpandLess,
24827
+ matExpandMore,
24613
24828
  }),
24614
- ], template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"] }]
24615
- }], propDecorators: { title: [{
24616
- type: Input
24617
- }], startDate: [{
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: [{
24618
24835
  type: Input
24619
- }], endDate: [{
24836
+ }], dateRange: [{
24620
24837
  type: Input
24621
- }], startDateChange: [{
24622
- type: Output
24623
- }], endDateChange: [{
24838
+ }], dateRangeChange: [{
24624
24839
  type: Output
24625
- }], picker: [{
24840
+ }], overlayOrigin: [{
24626
24841
  type: ViewChild,
24627
- args: ['picker']
24842
+ args: ['overlayOrigin']
24628
24843
  }] } });
24629
24844
 
24630
24845
  class DateRangePickerComponent {
@@ -24638,20 +24853,17 @@ class DateRangePickerComponent {
24638
24853
  provideNgIconsConfig({
24639
24854
  size: '1.5rem',
24640
24855
  }),
24641
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
24642
24858
  }
24643
24859
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24644
24860
  type: Component,
24645
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24646
- MatNativeDateModule,
24647
- MatDatepickerModule,
24648
- ButtonComponent,
24649
- NgIconComponent,
24650
- ], providers: [
24861
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24651
24862
  provideIcons({ iconoirCalendar }),
24652
24863
  provideNgIconsConfig({
24653
24864
  size: '1.5rem',
24654
24865
  }),
24866
+ provideLocalizedDateAdapter(),
24655
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"] }]
24656
24868
  }], propDecorators: { startDate: [{
24657
24869
  type: Input
@@ -24669,28 +24881,68 @@ class DragAndDropFileInputComponent {
24669
24881
  constructor() {
24670
24882
  this.placeholder = placeholder;
24671
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;
24672
24890
  this.fileChange = new EventEmitter();
24891
+ this.errorChange = new EventEmitter();
24673
24892
  this.selectedFile = null;
24674
24893
  }
24675
24894
  get fileName() {
24676
24895
  return this.selectedFile && this.selectedFile.name;
24677
24896
  }
24897
+ get maxFileSizeBytes() {
24898
+ return typeof this.maxFileSizeMb === 'number'
24899
+ ? megabytesToBytes(this.maxFileSizeMb)
24900
+ : null;
24901
+ }
24678
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
+ }
24679
24908
  this.selectedFile = event.addedFiles[0];
24680
24909
  this.fileChange.emit(this.selectedFile);
24681
24910
  }
24911
+ openFileSelector() {
24912
+ this.dropzone.showFileSelector();
24913
+ }
24914
+ clear() {
24915
+ this.selectedFile = null;
24916
+ }
24682
24917
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24683
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept" }, outputs: { fileChange: "fileChange" }, ngImport: i0, template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$7.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }] }); }
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"] }] }); }
24684
24919
  }
24685
24920
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24686
24921
  type: Component,
24687
- args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule], template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"] }]
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"] }]
24688
24923
  }], propDecorators: { placeholder: [{
24689
24924
  type: Input
24690
24925
  }], accept: [{
24691
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
24692
24939
  }], fileChange: [{
24693
24940
  type: Output
24941
+ }], errorChange: [{
24942
+ type: Output
24943
+ }], dropzone: [{
24944
+ type: ViewChild,
24945
+ args: [NgxDropzoneComponent]
24694
24946
  }] } });
24695
24947
 
24696
24948
  class DropdownMultiselectComponent {
@@ -24739,7 +24991,7 @@ class DropdownMultiselectComponent {
24739
24991
  }
24740
24992
  setFocus() {
24741
24993
  setTimeout(() => {
24742
- this.searchFieldInput.nativeElement.focus();
24994
+ this.searchFieldInput?.nativeElement.focus();
24743
24995
  }, 0);
24744
24996
  }
24745
24997
  openOverlay() {
@@ -24845,7 +25097,10 @@ class DropdownMultiselectComponent {
24845
25097
  matExpandMore,
24846
25098
  matExpandLess,
24847
25099
  }),
24848
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
24849
25104
  }
24850
25105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24851
25106
  type: Component,
@@ -24855,7 +25110,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24855
25110
  matExpandMore,
24856
25111
  matExpandLess,
24857
25112
  }),
24858
- ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
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" }]
24859
25117
  }], propDecorators: { title: [{
24860
25118
  type: Input
24861
25119
  }], choices: [{
@@ -25027,7 +25285,7 @@ class DropdownSelectorComponent {
25027
25285
  provideNgIconsConfig({
25028
25286
  size: '1.5em',
25029
25287
  }),
25030
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
25031
25289
  }
25032
25290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25033
25291
  type: Component,
@@ -25767,6 +26025,175 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25767
26025
  type: Output
25768
26026
  }] } });
25769
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
+
25770
26197
  class CellPopinComponent {
25771
26198
  constructor() {
25772
26199
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25843,7 +26270,7 @@ class CellPopinComponent {
25843
26270
  this.isOpen = false;
25844
26271
  }
25845
26272
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25846
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
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"] }] }); }
25847
26274
  }
25848
26275
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25849
26276
  type: Component,
@@ -27465,6 +27892,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
27465
27892
  }]
27466
27893
  }] });
27467
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
+
27468
27955
  // expects the replacement key ${uuid}
27469
27956
  const RECORD_DATASET_URL_TOKEN = new InjectionToken('record-dataset-url-token');
27470
27957
  const RECORD_SERVICE_URL_TOKEN = new InjectionToken('record-service-url-token');
@@ -27933,6 +28420,45 @@ class DateRangeSearchField extends SimpleSearchField {
27933
28420
  return 'dateRange';
27934
28421
  }
27935
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
+ }
27936
28462
  marker('search.filters.availableServices.view');
27937
28463
  marker('search.filters.availableServices.download');
27938
28464
  class AvailableServicesField extends SimpleSearchField {
@@ -28077,6 +28603,8 @@ marker('search.filters.producerOrg');
28077
28603
  marker('search.filters.publisherOrg');
28078
28604
  marker('search.filters.user');
28079
28605
  marker('search.filters.changeDate');
28606
+ marker('search.filters.resourceCreationRevisionDate');
28607
+ marker('search.filters.spatialExtent');
28080
28608
  class FieldsService {
28081
28609
  constructor() {
28082
28610
  this.injector = inject(Injector);
@@ -28099,7 +28627,9 @@ class FieldsService {
28099
28627
  publisherOrg: new MultilingualSearchField('distributorOrgForResourceObject', this.injector, 'asc', 'key'),
28100
28628
  user: new UserSearchField(this.injector),
28101
28629
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28630
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(this.injector, 'desc'),
28102
28631
  availableServices: new AvailableServicesField(this.injector),
28632
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28103
28633
  };
28104
28634
  }
28105
28635
  get supportedFields() {
@@ -29362,7 +29892,7 @@ class ContactPillComponent {
29362
29892
  this.overlayOpen = false;
29363
29893
  }
29364
29894
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29365
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
29366
29896
  }
29367
29897
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29368
29898
  type: Component,
@@ -30088,7 +30618,7 @@ class ServiceCapabilitiesComponent {
30088
30618
  if (this.apiLinks.length > 0 &&
30089
30619
  this.apiLinks[0].accessServiceProtocol !== 'ogcFeatures') {
30090
30620
  this.loading = true;
30091
- 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);
30092
30622
  this.loading = false;
30093
30623
  this.cdr.detectChanges();
30094
30624
  this.filteredLayers = this.availableLayers;
@@ -31284,19 +31814,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31284
31814
  type: Output
31285
31815
  }] } });
31286
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
+
31287
31886
  class FilterDropdownComponent {
31288
31887
  constructor() {
31289
31888
  this.searchFacade = inject(SearchFacade);
31290
31889
  this.searchService = inject(SearchService);
31291
31890
  this.fieldsService = inject(FieldsService);
31891
+ this.notificationsService = inject(NotificationsService);
31892
+ this.translateService = inject(TranslateService);
31893
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31292
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([])));
31293
- 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;
31294
31900
  }
31295
31901
  onSelectedValues(values) {
31296
31902
  this.fieldsService
31297
31903
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31298
31904
  .subscribe((filters) => this.searchService.updateFilters(filters));
31299
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
+ }
31300
31929
  ngOnInit() {
31301
31930
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31302
31931
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31304,27 +31933,15 @@ class FilterDropdownComponent {
31304
31933
  value: v.value.toString(), // converting to string for the dropdown
31305
31934
  }))), catchError(() => of([])));
31306
31935
  }
31307
- onStartDateChange(start) {
31308
- if (!start)
31309
- return;
31310
- this.dateRange = { ...this.dateRange, start };
31311
- }
31312
- onEndDateChange(end) {
31313
- if (!end)
31314
- return;
31315
- this.dateRange = { ...this.dateRange, end };
31316
- if (this.dateRange.start && this.dateRange.end) {
31317
- this.fieldsService
31318
- .buildFiltersFromFieldValues({
31319
- [this.fieldName]: this.dateRange,
31320
- })
31321
- .subscribe((filters) => {
31322
- return this.searchService.updateFilters(filters);
31323
- });
31324
- }
31936
+ onDateRangeChange(dateRange) {
31937
+ this.fieldsService
31938
+ .buildFiltersFromFieldValues({
31939
+ [this.fieldName]: dateRange,
31940
+ })
31941
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31325
31942
  }
31326
31943
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31327
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
31328
31945
  }
31329
31946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31330
31947
  type: Component,
@@ -31332,7 +31949,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31332
31949
  CommonModule,
31333
31950
  DateRangeDropdownComponent,
31334
31951
  DropdownMultiselectComponent,
31335
- ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
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" }]
31336
31954
  }], propDecorators: { fieldName: [{
31337
31955
  type: Input
31338
31956
  }], title: [{
@@ -31393,7 +32011,7 @@ class FuzzySearchComponent {
31393
32011
  this.isSearchActive.emit(event);
31394
32012
  }
31395
32013
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31396
- 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 }); }
31397
32015
  }
31398
32016
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, decorators: [{
31399
32017
  type: Component,
@@ -31802,11 +32420,7 @@ const SortByEnum = {
31802
32420
  RELEVANCY: ['desc', '_score'],
31803
32421
  QUALITY_SCORE: ['desc', 'qualityScore'],
31804
32422
  CHANGE_DATE: ['desc', 'changeDate'],
31805
- RESOURCE_DATES: [
31806
- ['desc', 'revisionDateForResource'],
31807
- ['desc', 'publicationDateForResource'],
31808
- ['desc', 'creationDateForResource'],
31809
- ],
32423
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
31810
32424
  };
31811
32425
 
31812
32426
  class SortByComponent {
@@ -31820,7 +32434,7 @@ class SortByComponent {
31820
32434
  },
31821
32435
  {
31822
32436
  label: marker('results.sortBy.dateStamp'),
31823
- value: SortByEnum.RESOURCE_DATES,
32437
+ value: SortByEnum.RESOURCE_DATE,
31824
32438
  },
31825
32439
  {
31826
32440
  label: marker('results.sortBy.popularity'),
@@ -31872,74 +32486,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31872
32486
  args: ['gnUiSearchStateContainer']
31873
32487
  }] } });
31874
32488
 
31875
- class NotificationsService {
31876
- constructor() {
31877
- this.notifications$ = new BehaviorSubject([]);
31878
- }
31879
- showNotification(content, timeoutMs, error) {
31880
- error && console.error(error);
31881
- const id = Math.floor(Math.random() * 1000000);
31882
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31883
- if (typeof timeoutMs === 'undefined')
31884
- return;
31885
- setTimeout(() => {
31886
- this.removeNotificationById(id);
31887
- }, timeoutMs);
31888
- }
31889
- removeNotificationById(id) {
31890
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31891
- }
31892
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31893
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31894
- }
31895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31896
- type: Injectable,
31897
- args: [{
31898
- providedIn: 'root',
31899
- }]
31900
- }] });
31901
-
31902
- class NotificationsContainerComponent {
31903
- constructor() {
31904
- this.notificationsService = inject(NotificationsService);
31905
- }
31906
- trackById(index, notification) {
31907
- return notification.id;
31908
- }
31909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31910
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31911
- trigger('enterExit', [
31912
- transition(':enter', [
31913
- animate('150ms', keyframes([
31914
- style({ transform: 'scale(1)', opacity: 0 }),
31915
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31916
- style({ transform: 'scale(1)', opacity: 1 }),
31917
- ])),
31918
- ]),
31919
- transition(':leave', [
31920
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31921
- ]),
31922
- ]),
31923
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31924
- }
31925
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31926
- type: Component,
31927
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31928
- trigger('enterExit', [
31929
- transition(':enter', [
31930
- animate('150ms', keyframes([
31931
- style({ transform: 'scale(1)', opacity: 0 }),
31932
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31933
- style({ transform: 'scale(1)', opacity: 1 }),
31934
- ])),
31935
- ]),
31936
- transition(':leave', [
31937
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31938
- ]),
31939
- ]),
31940
- ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31941
- }] });
31942
-
31943
32489
  class ResultsTableContainerComponent {
31944
32490
  constructor() {
31945
32491
  this.searchFacade = inject(SearchFacade);
@@ -32024,12 +32570,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32024
32570
 
32025
32571
  marker('search.filters.summaryLabel.user');
32026
32572
  marker('search.filters.summaryLabel.changeDate');
32573
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate');
32574
+ const OPEN_BOUND = '…';
32027
32575
  class SearchFiltersSummaryItemComponent {
32028
32576
  constructor() {
32029
32577
  this.searchFacade = inject(SearchFacade);
32030
32578
  this.searchService = inject(SearchService);
32031
32579
  this.fieldsService = inject(FieldsService);
32032
- this.datePipe = inject(DatePipe);
32580
+ this.dateService = inject(DateService);
32033
32581
  this.translate = inject(TranslateService);
32034
32582
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32035
32583
  ? fieldValues[this.fieldName]
@@ -32056,9 +32604,10 @@ class SearchFiltersSummaryItemComponent {
32056
32604
  getReadableValues(fieldValues) {
32057
32605
  return fieldValues.map((value) => {
32058
32606
  if (this.fieldType === 'dateRange') {
32607
+ const { start, end } = value;
32059
32608
  return {
32060
32609
  value,
32061
- 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)}`,
32062
32611
  };
32063
32612
  }
32064
32613
  else if (this.fieldName === 'user') {
@@ -32069,6 +32618,15 @@ class SearchFiltersSummaryItemComponent {
32069
32618
  }
32070
32619
  });
32071
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
+ }
32072
32630
  async removeFilterValue(fieldValue) {
32073
32631
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32074
32632
  const updatedFieldValues = currentFieldValues
@@ -32081,11 +32639,11 @@ class SearchFiltersSummaryItemComponent {
32081
32639
  .subscribe((filters) => this.searchService.updateFilters(filters));
32082
32640
  }
32083
32641
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32084
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, providers: [DatePipe], ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
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" }] }); }
32085
32643
  }
32086
32644
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32087
32645
  type: Component,
32088
- args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], providers: [DatePipe], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
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" }]
32089
32647
  }], propDecorators: { fieldName: [{
32090
32648
  type: Input
32091
32649
  }] } });
@@ -32329,11 +32887,11 @@ class AddLayerFromFileComponent {
32329
32887
  }, 5000);
32330
32888
  }
32331
32889
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32332
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept"], outputs: ["fileChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
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" }] }); }
32333
32891
  }
32334
32892
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32335
32893
  type: Component,
32336
- args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
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" }]
32337
32895
  }] });
32338
32896
 
32339
32897
  class LayersPanelComponent {
@@ -32394,95 +32952,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32394
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" }]
32395
32953
  }] });
32396
32954
 
32397
- class GeocodingComponent {
32398
- constructor() {
32399
- this.mapFacade = inject(MapFacade);
32400
- this.geocodingService = inject(GeocodingService);
32401
- this.searchText = '';
32402
- this.results = [];
32403
- this.searchTextChanged = new Subject();
32404
- this.destroy$ = new Subject();
32405
- this.errorMessage = null;
32406
- this.searchTextChanged
32407
- .pipe(debounceTime(300), switchMap$1((searchText) => {
32408
- return from(this.geocodingService.query(searchText)).pipe(catchError$1((error) => {
32409
- this.errorMessage =
32410
- 'An error occurred while searching. Please try again.';
32411
- console.error(error);
32412
- return [];
32413
- }));
32414
- }), takeUntil(this.destroy$))
32415
- .subscribe((results) => {
32416
- this.results = results;
32417
- });
32418
- }
32419
- ngOnDestroy() {
32420
- this.destroy$.next();
32421
- this.destroy$.complete();
32422
- }
32423
- onSearchChange(searchText) {
32424
- if (!searchText) {
32425
- this.clearSearch();
32426
- return;
32427
- }
32428
- else {
32429
- this.searchTextChanged.next(searchText);
32430
- }
32431
- }
32432
- clearSearch() {
32433
- this.searchText = '';
32434
- this.results = [];
32435
- this.errorMessage = null;
32436
- }
32437
- async zoomToLocation(result) {
32438
- const geometry = result.geom;
32439
- if (geometry.type === 'Point') {
32440
- await this.zoomToPoint(geometry.coordinates);
32441
- }
32442
- else if (geometry.type === 'Polygon') {
32443
- await this.zoomToPolygon(geometry.coordinates);
32444
- }
32445
- else {
32446
- console.error(`Unsupported geometry type: ${geometry.type}`);
32447
- }
32448
- }
32449
- async zoomToPoint(pointCoords) {
32450
- const context = await firstValueFrom(this.mapFacade.context$);
32451
- const view = {
32452
- center: pointCoords,
32453
- zoom: 10,
32454
- };
32455
- this.mapFacade.applyContext({
32456
- ...context,
32457
- view,
32458
- });
32459
- }
32460
- async zoomToPolygon(polygonCoords) {
32461
- const context = await firstValueFrom(this.mapFacade.context$);
32462
- const view = {
32463
- geometry: {
32464
- type: 'Polygon',
32465
- coordinates: polygonCoords,
32466
- },
32467
- };
32468
- this.mapFacade.applyContext({
32469
- ...context,
32470
- view,
32471
- });
32472
- }
32473
- onEnterPress() {
32474
- if (this.results && this.results.length > 0) {
32475
- this.zoomToLocation(this.results[0]);
32476
- }
32477
- }
32478
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32479
- 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" }] }); }
32480
- }
32481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, decorators: [{
32482
- type: Component,
32483
- 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" }]
32484
- }], ctorParameters: () => [] });
32485
-
32486
32955
  /*
32487
32956
  Metadata actions
32488
32957
  */
@@ -32498,8 +32967,7 @@ const closeMetadata = createAction('[Metadata view] close');
32498
32967
  Related actions
32499
32968
  */
32500
32969
  const setRelated = createAction('[Metadata view] Set related records', props());
32501
- const setSources = createAction('[Metadata view] Set sources', props());
32502
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32970
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32503
32971
  /*
32504
32972
  ChartConfig actions
32505
32973
  */
@@ -32531,9 +32999,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32531
32999
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32532
33000
  setChartConfig: setChartConfig,
32533
33001
  setIncompleteMetadata: setIncompleteMetadata,
32534
- setRelated: setRelated,
32535
- setSourceOf: setSourceOf,
32536
- setSources: setSources
33002
+ setLinkedRecords: setLinkedRecords,
33003
+ setRelated: setRelated
32537
33004
  });
32538
33005
 
32539
33006
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32576,12 +33043,9 @@ on(loadFullMetadata, (state) => ({
32576
33043
  on(setRelated, (state, { related }) => ({
32577
33044
  ...state,
32578
33045
  related,
32579
- })), on(setSources, (state, { sources }) => ({
32580
- ...state,
32581
- sources,
32582
- })), on(setSourceOf, (state, { sourceOf }) => ({
33046
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32583
33047
  ...state,
32584
- sourceOf,
33048
+ linkedRecords,
32585
33049
  })),
32586
33050
  /*
32587
33051
  ChartConfig reducers
@@ -32643,8 +33107,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32643
33107
  Related selectors
32644
33108
  */
32645
33109
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32646
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32647
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33110
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32648
33111
  /*
32649
33112
  Metadata selectors
32650
33113
  */
@@ -32745,6 +33208,453 @@ function parseHeaders(httpHeaders) {
32745
33208
  return result;
32746
33209
  }
32747
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
+
32748
33658
  async function inferDatasetType(url, typeHint) {
32749
33659
  const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32750
33660
  const fileExtension = fileExtensionMatches && fileExtensionMatches.length
@@ -32809,127 +33719,6 @@ function fetchDataAsArrayBuffer(url, cacheActive) {
32809
33719
  return new Uint8Array(array).buffer;
32810
33720
  });
32811
33721
  }
32812
- function tryParseDate(input) {
32813
- if (typeof input !== 'string')
32814
- return null;
32815
- function tryIso(value) {
32816
- const parsed = parseISO(value);
32817
- return isNaN(parsed.getDate()) ? null : parsed;
32818
- }
32819
- function tryFormat(value, format) {
32820
- const parsed = parse$4(value, format, new Date());
32821
- return isNaN(parsed.getDate()) ? null : parsed;
32822
- }
32823
- return (tryIso(input) ||
32824
- tryFormat(input, 'dd/MM/yyyy') ||
32825
- tryFormat(input, 'dd.MM.yyyy') ||
32826
- tryFormat(input, 'MM/dd/yyyy') ||
32827
- null);
32828
- }
32829
- function tryParseNumber(input) {
32830
- if (isNaN(input))
32831
- return null;
32832
- const parsed = parseFloat(input);
32833
- return isNaN(parsed) ? null : parsed;
32834
- }
32835
- function jsonToGeojsonFeature(object) {
32836
- const { id, properties } = Object.keys(object)
32837
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32838
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32839
- ? {
32840
- ...prev,
32841
- id: object[curr],
32842
- }
32843
- : {
32844
- ...prev,
32845
- properties: { ...prev.properties, [curr]: object[curr] },
32846
- }, { id: undefined, properties: {} });
32847
- return {
32848
- type: 'Feature',
32849
- geometry: null,
32850
- properties,
32851
- ...(id !== undefined && { id }),
32852
- };
32853
- }
32854
- function mutateProperties(items, mutators) {
32855
- const mutatorKeys = Object.keys(mutators);
32856
- for (let i = 0, ii = items.length; i < ii; i++) {
32857
- const item = items[i];
32858
- for (const mutatorField of mutatorKeys) {
32859
- if (!(mutatorField in item.properties))
32860
- continue;
32861
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32862
- }
32863
- }
32864
- return items;
32865
- }
32866
- const SAMPLE_SIZE = 20;
32867
- /**
32868
- * This will infer field types from a list of data items and cast the values accordingly
32869
- * @param items
32870
- * @param inferTypes
32871
- */
32872
- function processItemProperties(items, inferTypes = false) {
32873
- const foundFields = {};
32874
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32875
- const item = items[i];
32876
- const fields = Object.keys(item.properties);
32877
- for (const field of fields) {
32878
- if (!(field in foundFields)) {
32879
- foundFields[field] = {
32880
- label: field,
32881
- name: field,
32882
- type: null,
32883
- };
32884
- }
32885
- const value = item.properties[field];
32886
- const info = foundFields[field];
32887
- if (value === undefined || value === '' || value === null)
32888
- continue;
32889
- if (!inferTypes) {
32890
- if (info.type === null && typeof value === 'number') {
32891
- info.type = 'number';
32892
- }
32893
- else if (info.type === 'number' && typeof value !== 'number') {
32894
- info.type = 'string';
32895
- }
32896
- continue;
32897
- }
32898
- const parsedNumber = tryParseNumber(value);
32899
- if (info.type === null && parsedNumber !== null) {
32900
- info.type = 'number';
32901
- continue;
32902
- }
32903
- else if (info.type === 'number' && parsedNumber === null) {
32904
- info.type = 'string';
32905
- continue;
32906
- }
32907
- const parsedDate = tryParseDate(value);
32908
- if (info.type === null && parsedDate !== null) {
32909
- info.type = 'date';
32910
- }
32911
- else if (info.type === 'date' && parsedDate === null) {
32912
- info.type = 'string';
32913
- }
32914
- }
32915
- }
32916
- const properties = [];
32917
- const mutators = {};
32918
- for (const field in foundFields) {
32919
- const info = foundFields[field];
32920
- if (info.type === 'number') {
32921
- mutators[field] = tryParseNumber;
32922
- }
32923
- else if (info.type === 'date') {
32924
- mutators[field] = tryParseDate;
32925
- }
32926
- properties.push({ ...info, type: info.type || 'string' });
32927
- }
32928
- if (inferTypes) {
32929
- mutateProperties(items, mutators);
32930
- }
32931
- return { items, properties };
32932
- }
32933
33722
  /**
32934
33723
  * This creates a Proxy that allows reading and writing to the data item properties
32935
33724
  * as if it was a simple array of JSON objects
@@ -32951,333 +33740,71 @@ function getJsonDataItemsProxy(items) {
32951
33740
  });
32952
33741
  }
32953
33742
 
32954
- function filterToSql(filter) {
32955
- const operator = filter[0];
32956
- const args = filter.slice(1);
32957
- function valueToSql(value) {
32958
- return typeof value === 'number' ? value : `'${value}'`;
32959
- }
32960
- switch (operator) {
32961
- case '<':
32962
- case '<=':
32963
- case '>':
32964
- case '>=':
32965
- case '=':
32966
- case '!=':
32967
- case 'like':
32968
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32969
- case 'in': {
32970
- const values = args.slice(1);
32971
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
32972
- }
32973
- case 'and':
32974
- case 'or': {
32975
- const children = args
32976
- .map(filterToSql)
32977
- .join(` ${operator.toUpperCase()} `);
32978
- return `(${children})`;
32979
- }
32980
- case 'not':
32981
- return `NOT (${filterToSql(args[0])})`;
32982
- }
32983
- throw new Error(`Could not generate SQL query, operator not recognized: ${operator}`);
32984
- }
32985
- function aggregationToSql(aggregation) {
32986
- const operation = aggregation[0];
32987
- const field = aggregation[1];
32988
- switch (operation) {
32989
- case 'average':
32990
- return `AVG([${field}]) as [average(${field})]`;
32991
- case 'sum':
32992
- case 'max':
32993
- case 'min':
32994
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
32995
- case 'count':
32996
- return 'COUNT(*) as [count()]';
32997
- }
32998
- }
32999
- /**
33000
- * Leave arguments at null if not used
33001
- * @param selected
33002
- * @param filter
33003
- * @param sort
33004
- * @param startIndex
33005
- * @param count
33006
- * @param groupBy
33007
- * @param aggregations
33008
- */
33009
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33010
- let sqlSelect = 'SELECT *';
33011
- const sqlFrom = ' FROM ?';
33012
- let sqlOrderBy = '';
33013
- let sqlWhere = '';
33014
- let sqlLimit = '';
33015
- let sqlGroupBy = '';
33016
- if (selected !== null) {
33017
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33018
- }
33019
- if (filter !== null) {
33020
- sqlWhere = ` WHERE ${filterToSql(filter)}`;
33021
- }
33022
- if (sort?.length) {
33023
- sqlOrderBy = ` ORDER BY ${sort
33024
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33025
- .join(', ')}`;
33026
- }
33027
- if (startIndex !== null && count !== null) {
33028
- sqlLimit = ` LIMIT ${count} OFFSET ${startIndex}`;
33029
- }
33030
- if (groupBy !== null && aggregations !== null) {
33031
- sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33032
- const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33033
- const sqlGroupByFields = groupedByDistinct
33034
- .map((group) => `[${group[1]}]`)
33035
- .join(', ');
33036
- const sqlGroupBySelect = groupedByDistinct
33037
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33038
- .join(', ');
33039
- if (sqlGroupByFields && sqlGroupBySelect) {
33040
- sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33041
- 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
+ });
33042
33760
  }
33043
- }
33044
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33045
- }
33046
-
33047
- class BaseReader {
33048
- constructor(url) {
33049
- this.url = url;
33050
- this.selected = null;
33051
- this.groupedBy = null;
33052
- this.aggregations = null;
33053
- this.filter = null;
33054
- this.sort = null;
33055
- this.startIndex = null;
33056
- this.count = null;
33057
- }
33058
- load() {
33059
- throw new Error('not implemented');
33060
- }
33061
- get properties() {
33062
- throw new Error('not implemented');
33063
- }
33064
- get info() {
33065
- throw new Error('not implemented');
33066
- }
33067
- read() {
33068
- throw new Error('not implemented');
33069
- }
33070
- selectAll() {
33071
- this.groupedBy = null;
33072
- this.aggregations = null;
33073
- this.selected = null;
33074
- this.filter = null;
33075
- this.startIndex = null;
33076
- this.count = null;
33077
- return this;
33078
- }
33079
- select(...selectedFields) {
33080
- this.selected = selectedFields;
33081
- this.aggregations = null; // clear aggregations & groups when selecting fields
33082
- this.groupedBy = null;
33083
- return this;
33084
- }
33085
- groupBy(...groupBy) {
33086
- this.groupedBy = groupBy;
33087
- this.selected = null; // clear normal field selection when aggregating
33088
- return this;
33089
- }
33090
- aggregate(...aggregations) {
33091
- this.aggregations = aggregations;
33092
- return this;
33093
- }
33094
- where(filter) {
33095
- this.filter = filter;
33096
- return this;
33097
- }
33098
- orderBy(...fieldSorts) {
33099
- this.sort = fieldSorts;
33100
- return this;
33101
- }
33102
- limit(startIndex, count) {
33103
- this.startIndex = startIndex;
33104
- this.count = count;
33105
- return this;
33106
- }
33107
- }
33108
-
33109
- class BaseCacheReader extends BaseReader {
33110
- constructor(url, cacheActive = true) {
33111
- super(url);
33112
- this.url = url;
33113
- this.cacheActive = cacheActive;
33114
- }
33115
- setCacheActive(value) {
33116
- this.cacheActive = value;
33117
- }
33118
- }
33119
-
33120
- class BaseFileReader extends BaseCacheReader {
33121
- getData() {
33122
- throw new Error('not implemented');
33123
- }
33124
- load() {
33125
- this.parseResult_ = this.getData();
33126
- }
33127
- get properties() {
33128
- return this.parseResult_.then((result) => result.properties);
33129
- }
33130
- get info() {
33131
- return this.parseResult_.then((result) => ({
33132
- itemsCount: result.items.length,
33133
- }));
33134
- }
33135
- async read() {
33136
- const items = (await this.parseResult_).items;
33137
- // no query defined: return the full results as is
33138
- if (this.groupedBy == null &&
33139
- this.aggregations == null &&
33140
- this.selected == null &&
33141
- this.sort == null &&
33142
- this.filter == null &&
33143
- this.startIndex == null &&
33144
- this.count == null) {
33145
- return items;
33146
- }
33147
- const jsonItems = getJsonDataItemsProxy(items);
33148
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33149
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33150
- return result.map(jsonToGeojsonFeature);
33151
- }
33152
- }
33153
-
33154
- function parseCsv(text) {
33155
- // first parse the header to guess the delimiter
33156
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33157
- let delimiter;
33158
- try {
33159
- const header = text.split('\n')[0];
33160
- const result = Papa.parse(header, {
33161
- header: false,
33162
- });
33163
- delimiter = result.meta.delimiter;
33164
- }
33165
- catch (e) {
33166
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33167
- }
33168
- const parsed = Papa.parse(text, {
33169
- header: true,
33170
- skipEmptyLines: true,
33171
- delimiter,
33172
- });
33173
- if (parsed.errors.length) {
33174
- throw new Error('CSV parsing failed for the following reasons:\n' +
33175
- parsed.errors
33176
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33177
- .join('\n'));
33178
- }
33179
- const items = parsed.data.map(jsonToGeojsonFeature);
33180
- return processItemProperties(items, true);
33181
- }
33182
- class CsvReader extends BaseFileReader {
33183
- getData() {
33184
- 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);`;
33185
33765
  }
33186
33766
  }
33187
33767
 
33188
- /**
33189
- * This parser only supports arrays of simple flat objects with properties
33190
- * @param text
33191
- */
33192
- function parseJson(text) {
33193
- const parsed = JSON.parse(text);
33194
- if (!Array.isArray(parsed)) {
33195
- throw new Error('Could not parse JSON, expected an array at root level');
33196
- }
33197
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33198
- }
33199
- class JsonReader extends BaseFileReader {
33200
- getData() {
33201
- 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
+ );`;
33202
33774
  }
33203
33775
  }
33204
33776
 
33205
- /**
33206
- * This parser supports both Geojson Feature collections or arrays
33207
- * of Features
33208
- * @param text
33209
- */
33777
+ const formatGeojson = new GeoJSON$1();
33210
33778
  function parseGeojson(text) {
33211
33779
  const parsed = JSON.parse(text);
33212
33780
  const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33213
33781
  if (!Array.isArray(features)) {
33214
33782
  throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33215
33783
  }
33216
- return processItemProperties(features);
33217
- }
33218
- class GeojsonReader extends BaseFileReader {
33219
- getData() {
33220
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33221
- }
33222
- }
33223
-
33224
- /**
33225
- * This will read the first sheet of the excel workbook and expect the first
33226
- * line to contain the properties names
33227
- * @param buffer
33228
- */
33229
- function parseExcel(buffer) {
33230
- return import('xlsx').then(({ read, utils }) => {
33231
- const workbook = read(buffer);
33232
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33233
- let json = utils.sheet_to_json(sheet);
33234
- if (!json.length) {
33235
- json = [];
33236
- }
33237
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33238
- });
33239
- }
33240
- class ExcelReader extends BaseFileReader {
33241
- getData() {
33242
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33243
- }
33784
+ return features;
33244
33785
  }
33245
-
33246
- function parseGml(text, namespace, version) {
33247
- const splittedNamespace = namespace.split(':');
33248
- 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]}=["']([^'"]*)["']`);
33249
33789
  const match = regex.exec(text);
33250
33790
  if (match && match.length >= 2) {
33251
- const wf = new WFS({
33791
+ const wfs = new WFS({
33252
33792
  featureNS: match[1],
33253
- featureType: splittedNamespace[1],
33793
+ featureType: parts[1],
33254
33794
  version: version,
33255
33795
  });
33256
33796
  let features;
33257
33797
  try {
33258
- features = wf.readFeatures(text);
33798
+ features = wfs.readFeatures(text);
33259
33799
  }
33260
33800
  catch (e) {
33261
- throw Error("Couldn't parse WFS with GML features");
33801
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33262
33802
  }
33263
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33264
- return processItemProperties(geojsonItem.features, true);
33803
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33804
+ return geojsonItem.features;
33265
33805
  }
33266
33806
  throw Error("Couldn't retrieve namespace url");
33267
33807
  }
33268
- class GmlReader extends BaseFileReader {
33269
- constructor(url, namespace, version, cacheActive = true) {
33270
- super(url);
33271
- this.url = url;
33272
- this.namespace = namespace;
33273
- this.version = version;
33274
- this.cacheActive = cacheActive;
33275
- }
33276
- getData() {
33277
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33278
- }
33279
- }
33280
-
33281
33808
  async function getWfsEndpoint(wfsUrl) {
33282
33809
  try {
33283
33810
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33305,21 +33832,65 @@ async function getWfsEndpoint(wfsUrl) {
33305
33832
  }
33306
33833
  }
33307
33834
  }
33308
- class WfsReader extends BaseCacheReader {
33309
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33310
- super(url, cacheActive);
33311
- this.endpoint = wfsEndpoint;
33312
- this.featureTypeName = featureTypeName;
33313
- this.version = this.endpoint.getVersion();
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_;
33314
33880
  }
33315
33881
  get properties() {
33316
- return this.endpoint
33317
- .getFeatureTypeFull(this.featureTypeName)
33318
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33882
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33319
33883
  const originalType = featureType.properties[prop];
33320
- const type = originalType === 'float' || originalType === 'integer'
33321
- ? 'number'
33322
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
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
+ }
33323
33894
  return {
33324
33895
  name: prop,
33325
33896
  label: prop,
@@ -33328,83 +33899,63 @@ class WfsReader extends BaseCacheReader {
33328
33899
  }));
33329
33900
  }
33330
33901
  get info() {
33331
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33902
+ return this.featureType.then((result) => ({
33332
33903
  itemsCount: result.objectCount,
33904
+ hasGeometry: !!result.geometryName,
33333
33905
  }));
33334
33906
  }
33335
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33336
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33337
- const featureTypes = wfsEndpoint.getFeatureTypes();
33338
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33339
- ? featureTypes[0].name
33340
- : featureTypeName);
33341
- if (!featureType) {
33342
- throw new Error('wfs.featuretype.notfound');
33343
- }
33344
- if (wfsEndpoint.supportsStartIndex()) {
33345
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33346
- }
33347
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33348
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33349
- asJson: true,
33350
- outputCrs: 'EPSG:4326',
33351
- }));
33352
- }
33353
- else {
33354
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33355
- (featureType.defaultCrs === 'EPSG:4326' ||
33356
- featureType.otherCrs?.includes('EPSG:4326'))) {
33357
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33358
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33359
- outputCrs: 'EPSG:4326',
33360
- }), featureType.name, wfsEndpoint.getVersion());
33361
- }
33362
- throw new Error('wfs.geojsongml.notsupported');
33363
- }
33907
+ load() {
33908
+ // Nothing to load for Wfs
33364
33909
  }
33365
- async getData(aggregation, groupedBy) {
33366
- if (aggregation || groupedBy) {
33367
- 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();
33368
33933
  }
33369
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33934
+ const asJson = endpoint.supportsJson(featureType.name);
33370
33935
  const attributes = this.selected ?? undefined;
33371
- 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, {
33372
33944
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33373
33945
  ...(this.count !== null && { maxFeatures: this.count }),
33374
33946
  asJson,
33375
33947
  outputCrs: 'EPSG:4326',
33376
33948
  attributes,
33377
- // sortBy: this.sort // TODO: no sort in ogc-client?
33949
+ sortBy,
33378
33950
  });
33379
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33380
- const finalUrl = new URL(url);
33381
- const sorts = this.sort
33382
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33383
- .join(',');
33384
- // Direct update on string url to prevent encoding of +A and +D
33385
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33386
- }
33387
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33951
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33388
33952
  ? parseGeojson(text)
33389
- : parseGml(text, this.featureTypeName, this.version));
33390
- }
33391
- async getQueryData() {
33392
- const items = (await this.getData()).items;
33393
- const jsonItems = getJsonDataItemsProxy(items);
33394
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33395
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33396
- return result.map(jsonToGeojsonFeature);
33397
- }
33398
- load() {
33399
- // Nothing to load for Wfs
33400
- }
33401
- async read() {
33402
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33953
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33403
33954
  }
33404
33955
  }
33405
33956
 
33406
- async function openDataset(url, typeHint, options, cacheActive) {
33407
- const fileType = await inferDatasetType(url, typeHint);
33957
+ async function openDataset(url, options) {
33958
+ const fileType = await inferDatasetType(url, options?.typeHint);
33408
33959
  let reader;
33409
33960
  try {
33410
33961
  switch (fileType) {
@@ -33421,18 +33972,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33421
33972
  reader = new ExcelReader(url);
33422
33973
  break;
33423
33974
  case 'gml':
33424
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33975
+ reader = new GmlReader(url);
33425
33976
  break;
33426
33977
  case 'wfs':
33427
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33978
+ reader = new WfsReader(url, options?.wfsFeatureType);
33428
33979
  break;
33429
33980
  }
33430
- reader.setCacheActive(cacheActive);
33981
+ reader.enableCache(options?.enableCache ?? true);
33431
33982
  reader.load();
33432
33983
  return reader;
33433
33984
  }
33434
33985
  catch (e) {
33435
- //WfsReader may already raise a FetchError
33986
+ // WfsReader may already raise a FetchError
33436
33987
  if (e instanceof FetchError)
33437
33988
  throw e;
33438
33989
  else
@@ -33443,13 +33994,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33443
33994
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33444
33995
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33445
33996
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33446
- * File type is determined liked so:
33997
+ * File type is determined like so:
33447
33998
  * 1. if a type hint is given, use it
33448
33999
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33449
34000
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33450
34001
  */
33451
- async function readDataset(url, typeHint, options, cacheActive = true) {
33452
- const reader = await openDataset(url, typeHint, options, cacheActive);
34002
+ async function readDataset(url, options) {
34003
+ const reader = await openDataset(url, options);
33453
34004
  try {
33454
34005
  return await reader.read();
33455
34006
  }
@@ -33680,9 +34231,11 @@ class DataService {
33680
34231
  getDataset(link, cacheActive) {
33681
34232
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33682
34233
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33683
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34234
+ return from(openDataset(wfsUrlEndpoint, {
34235
+ typeHint: 'wfs',
33684
34236
  wfsFeatureType: link.name,
33685
- }, cacheActive));
34237
+ enableCache: cacheActive,
34238
+ }));
33686
34239
  }
33687
34240
  else if (link.type === 'download') {
33688
34241
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33690,12 +34243,15 @@ class DataService {
33690
34243
  const supportedType = SupportedTypes.indexOf(format) > -1
33691
34244
  ? format
33692
34245
  : undefined;
33693
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34246
+ return from(openDataset(linkProxifiedUrl, {
34247
+ typeHint: supportedType,
34248
+ enableCache: cacheActive,
34249
+ })).pipe();
33694
34250
  }
33695
34251
  else if (link.type === 'service' &&
33696
34252
  link.accessServiceProtocol === 'esriRest') {
33697
34253
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33698
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34254
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33699
34255
  }
33700
34256
  else if (link.type === 'service' &&
33701
34257
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33709,7 +34265,10 @@ class DataService {
33709
34265
  }
33710
34266
  const urlWithoutLimit = new URL(geojsonUrl);
33711
34267
  urlWithoutLimit.searchParams.delete('limit');
33712
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34268
+ return openDataset(urlWithoutLimit.toString(), {
34269
+ typeHint: 'geojson',
34270
+ enableCache: cacheActive,
34271
+ });
33713
34272
  }));
33714
34273
  }
33715
34274
  return throwError(() => 'protocol not supported');
@@ -33961,20 +34520,31 @@ class DataTableComponent {
33961
34520
  this.eltRef = inject(ElementRef);
33962
34521
  this.cdr = inject(ChangeDetectorRef);
33963
34522
  this.translateService = inject(TranslateService);
33964
- this._featureAttributes = [];
34523
+ this.columnsFromFeatureCatalog = null;
34524
+ this.columnsFromDataset = [];
33965
34525
  this.selected = new EventEmitter();
33966
- this.properties$ = new BehaviorSubject(null);
33967
34526
  this.loading$ = new BehaviorSubject(false);
33968
34527
  this.error = null;
33969
34528
  }
33970
34529
  set featureAttributes(value) {
33971
- this._featureAttributes = value;
33972
- this.properties$.next(value.map((attr) => attr.value));
34530
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34531
+ name: attrs.value,
34532
+ label: attrs.label,
34533
+ }));
33973
34534
  }
33974
34535
  set dataset(value) {
33975
34536
  this.dataset_ = value;
33976
34537
  this.dataset_.load();
33977
- 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);
33978
34548
  }
33979
34549
  ngOnInit() {
33980
34550
  this.dataSource = new DataTableDataSource();
@@ -34009,11 +34579,12 @@ class DataTableComponent {
34009
34579
  }
34010
34580
  async readData() {
34011
34581
  this.loading$.next(true);
34012
- // wait for properties to be read
34013
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34014
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34015
- this.dataset_.select(...propsWithoutGeom);
34016
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);
34017
34588
  await this.dataSource.showData(this.dataset_.read());
34018
34589
  this.error = null;
34019
34590
  }
@@ -34043,7 +34614,7 @@ class DataTableComponent {
34043
34614
  }
34044
34615
  }
34045
34616
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34046
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
34047
34618
  }
34048
34619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34049
34620
  type: Component,
@@ -34055,10 +34626,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34055
34626
  CommonModule,
34056
34627
  LoadingMaskComponent,
34057
34628
  PopupAlertComponent,
34058
- LetDirective,
34059
34629
  TranslatePipe,
34060
34630
  TranslateDirective,
34061
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
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"] }]
34062
34632
  }], propDecorators: { featureAttributes: [{
34063
34633
  type: Input
34064
34634
  }], dataset: [{
@@ -34161,7 +34731,7 @@ class ChartViewComponent {
34161
34731
  }), shareReplay$1(1));
34162
34732
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34163
34733
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34164
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34734
+ .filter((prop) => prop.type === 'number')
34165
34735
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34166
34736
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34167
34737
  const newProp = choices[0]?.value || '';
@@ -34188,6 +34758,7 @@ class ChartViewComponent {
34188
34758
  this.aggregation$,
34189
34759
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34190
34760
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34761
+ this.loading = true;
34191
34762
  return dataset
34192
34763
  .groupBy(['distinct', xProp])
34193
34764
  .aggregate(fieldAgg)
@@ -34429,6 +35000,7 @@ class GeoTableViewComponent {
34429
35000
  }));
34430
35001
  }
34431
35002
  async initMapContext() {
35003
+ this.dataset.load();
34432
35004
  this.dataset.selectAll();
34433
35005
  return {
34434
35006
  layers: [
@@ -34602,8 +35174,7 @@ class MdViewFacade {
34602
35174
  }));
34603
35175
  this.error$ = this.store.pipe(select(getMetadataError));
34604
35176
  this.related$ = this.store.pipe(select(getRelated));
34605
- this.sources$ = this.store.pipe(select(getSources));
34606
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35177
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34607
35178
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34608
35179
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34609
35180
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34722,12 +35293,9 @@ class MdViewEffects {
34722
35293
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34723
35294
  return setRelated({ related });
34724
35295
  }), catchError(() => of(setRelated({ related: null })))));
34725
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34726
- return setSources({ sources });
34727
- }), catchError(() => of(setSources({ sources: null })))));
34728
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34729
- return setSourceOf({ sourceOf });
34730
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
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 })))));
34731
35299
  /*
34732
35300
  UserFeedback effects
34733
35301
  */
@@ -37428,7 +37996,7 @@ class GenericKeywordsComponent {
37428
37996
  this.deletedKeyword.emit(keyword);
37429
37997
  }
37430
37998
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37431
- 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 }); }
37432
38000
  }
37433
38001
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, decorators: [{
37434
38002
  type: Component,
@@ -38020,7 +38588,7 @@ class FormFieldContactsForResourceComponent {
38020
38588
  provideNgIconsConfig({
38021
38589
  size: '1.5rem',
38022
38590
  }),
38023
- ], 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 }); }
38024
38592
  }
38025
38593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsForResourceComponent, decorators: [{
38026
38594
  type: Component,
@@ -38120,7 +38688,7 @@ class FormFieldContactsComponent {
38120
38688
  this.subscription.unsubscribe();
38121
38689
  }
38122
38690
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38123
- 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 }); }
38124
38692
  }
38125
38693
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, decorators: [{
38126
38694
  type: Component,
@@ -38473,7 +39041,7 @@ class OnlineServiceResourceInputComponent {
38473
39041
  provideNgIconsConfig({
38474
39042
  size: '1.5em',
38475
39043
  }),
38476
- ], 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 }); }
38477
39045
  }
38478
39046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OnlineServiceResourceInputComponent, decorators: [{
38479
39047
  type: Component,
@@ -39946,7 +40514,7 @@ class RouterService {
39946
40514
  return ROUTER_ROUTE_ORGANIZATION;
39947
40515
  }
39948
40516
  getDefaultSort() {
39949
- return SortByEnum.RESOURCE_DATES;
40517
+ return SortByEnum.RESOURCE_DATE;
39950
40518
  }
39951
40519
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39952
40520
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, providedIn: 'root' }); }
@@ -40006,7 +40574,7 @@ function flattenQueryParams(params) {
40006
40574
  const start = flattened[key].start;
40007
40575
  const end = flattened[key].end;
40008
40576
  flattened[key] = [
40009
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40577
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40010
40578
  ];
40011
40579
  }
40012
40580
  }
@@ -40363,5 +40931,5 @@ const CHART_TYPE_VALUES = [
40363
40931
  * Generated bundle index. Do not edit.
40364
40932
  */
40365
40933
 
40366
- export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
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 };
40367
40935
  //# sourceMappingURL=geonetwork-ui.mjs.map