geonetwork-ui 2.11.0-dev.8442dae6e → 2.11.0-dev.9ba9c4d3d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1333 -691
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +228 -81
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +70 -10
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +22 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +13 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  18. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  20. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  21. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  22. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  23. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  24. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  25. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  26. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  27. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  28. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  29. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  30. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  31. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  32. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  33. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  34. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  35. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  36. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  37. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  38. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  40. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  41. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  42. package/src/libs/ui/inputs/src/index.ts +2 -0
  43. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  44. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  45. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  46. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  48. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  49. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  50. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  52. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  53. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  54. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  55. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  56. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  57. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  58. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  59. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  60. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  61. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  62. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  63. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  64. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  65. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  66. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  67. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  68. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  69. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  70. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  71. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  72. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  73. package/src/libs/util/shared/src/index.ts +1 -0
  74. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  75. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  76. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  77. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  78. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  79. package/tailwind.base.css +5 -0
  80. package/translations/de.json +26 -3
  81. package/translations/en.json +25 -2
  82. package/translations/es.json +23 -0
  83. package/translations/fr.json +26 -3
  84. package/translations/it.json +24 -1
  85. package/translations/nl.json +23 -0
  86. package/translations/pt.json +23 -0
  87. package/translations/sk.json +24 -1
  88. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -6,9 +6,9 @@ import GeoJSON from 'ol/format/GeoJSON.js';
6
6
  import { parse as parse$1 } from 'ol/xml.js';
7
7
  import { format } from 'date-fns/format';
8
8
  import { Namespace, Literal, lit, parse as parse$2, sym, BlankNode, graph } from 'rdflib';
9
- import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, combineLatest, from, exhaustMap, throwError, forkJoin, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
9
+ import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, forkJoin, combineLatest, from, exhaustMap, throwError, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
10
10
  import * as i0 from '@angular/core';
11
- import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, HostListener, Input, Directive, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, afterNextRender, viewChildren } from '@angular/core';
11
+ import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, afterNextRender, booleanAttribute, Input, Directive, HostListener, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
13
13
  import * as i4 from '@ngx-translate/core';
14
14
  import { TranslateLoader, TranslateCompiler, TranslateDefaultParser, TranslateParser, TranslateService, provideTranslateService, TranslateDirective, TranslatePipe, TranslateModule } from '@ngx-translate/core';
@@ -18,7 +18,7 @@ import { map as map$1, shareReplay as shareReplay$1, catchError, tap as tap$1, f
18
18
  import { lt, valid, coerce, satisfies, ltr } from 'semver';
19
19
  import chroma from 'chroma-js';
20
20
  import * as i1$1 from '@angular/common';
21
- import { Location, CommonModule, NgClass, NgTemplateOutlet, DatePipe, AsyncPipe } from '@angular/common';
21
+ import { Location, CommonModule, NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common';
22
22
  import { formatDistance } from 'date-fns/formatDistance';
23
23
  import { Scroll, NavigationEnd, Router, RouteReuseStrategy } from '@angular/router';
24
24
  import { WmtsEndpoint, WmsEndpoint, WfsEndpoint, OgcApiEndpoint, sharedFetch, useCache, StacEndpoint, TmsEndpoint } from '@camptocamp/ogc-client';
@@ -41,10 +41,10 @@ import * as TOML from '@ltd/j-toml';
41
41
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
42
  import CircleStyle from 'ol/style/Circle.js';
43
43
  import EmblaCarousel from 'embla-carousel';
44
- import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirImport, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirCheckCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
44
+ import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirTrash, iconoirSquareDashed, iconoirImport, iconoirCheckCircle, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
45
45
  import { MatButtonModule } from '@angular/material/button';
46
- import * as i1$8 from '@angular/cdk/overlay';
47
- import { OverlayContainer, ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
46
+ import * as i1$7 from '@angular/cdk/overlay';
47
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
48
  import * as i1$3 from '@angular/forms';
49
49
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
50
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +52,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
52
  import tippy from 'tippy.js';
53
53
  import * as i2 from '@angular/material/progress-spinner';
54
54
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
55
- import { matClose, matContentCopy, matExpandLess, matExpandMore, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
55
+ import { matClose, matContentCopy, matExpandMore, matExpandLess, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
56
56
  import * as i1$4 from '@angular/material/checkbox';
57
57
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
58
  import * as i1$5 from '@angular/material/tooltip';
59
59
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
60
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
61
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
62
+ import { enUS } from 'date-fns/locale/en-US';
61
63
  import * as i1$6 from '@angular/material/datepicker';
62
64
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
65
+ import * as i1$8 from 'ngx-dropzone';
66
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
67
  import * as i1$9 from '@angular/material/button-toggle';
66
68
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
69
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -79,10 +81,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
81
  import Duration from 'duration-relativetimeformat';
80
82
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
83
  import { trigger, transition, animate, keyframes, style } from '@angular/animations';
82
- import * as Papa from 'papaparse';
83
- import { parse as parse$4 } from 'date-fns/parse';
84
- import { parseISO } from 'date-fns/parseISO';
85
- import { WFS, GeoJSON as GeoJSON$1 } from 'ol/format.js';
84
+ import * as duckdb from '@duckdb/duckdb-wasm';
85
+ import { Vector, DataType } from 'apache-arrow';
86
+ import WFS from 'ol/format/WFS';
87
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
88
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
89
  import * as i2$4 from '@angular/material/sort';
88
90
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +92,6 @@ import * as i1$b from '@angular/material/table';
90
92
  import { MatTableModule } from '@angular/material/table';
91
93
  import * as i3 from '@angular/material/paginator';
92
94
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
95
  import { Meta } from '@angular/platform-browser';
95
96
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
97
  import * as i3$1 from '@angular/material/radio';
@@ -788,6 +789,15 @@ marker('record.metadata.api.accessServiceProtocol.maplibre-style');
788
789
  marker('record.metadata.api.accessServiceProtocol.postgis');
789
790
  marker('record.metadata.api.accessServiceProtocol.stac');
790
791
  marker('record.metadata.api.accessServiceProtocol.other');
792
+ marker('domain.record.associationType.crossReference');
793
+ marker('domain.record.associationType.largerWorkCitation');
794
+ marker('domain.record.associationType.partOfSeamlessDatabase');
795
+ marker('domain.record.associationType.stereoMate');
796
+ marker('domain.record.associationType.isComposedOf');
797
+ marker('domain.record.associationType.collectiveTitle');
798
+ marker('domain.record.associationType.series');
799
+ marker('domain.record.associationType.dependency');
800
+ marker('domain.record.associationType.revisionOf');
791
801
  // DS_AssociationTypeCode values of the ISO19115-3 codelist, any unrecognized value
792
802
  // being mapped to 'crossReference'
793
803
  const associationTypeValues = [
@@ -18898,14 +18908,29 @@ class Gn4FieldMapper {
18898
18908
  featureTypes: selectField(source, 'featureTypes'),
18899
18909
  }, output),
18900
18910
  related: (output, source) => {
18901
- 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');
18902
18913
  const featureCatalogIdentifier = selectField(fcatSource, 'uuid');
18903
- const sourceOfLinks = getAsArray(selectField(selectField(source, 'related'), 'hassources'));
18914
+ const sourceOfLinks = getAsArray(selectField(related, 'hassources'));
18904
18915
  const sourceOfIdentifiers = sourceOfLinks
18905
18916
  .filter((link) => link['origin'] === 'catalog')
18906
18917
  .map((link) => {
18907
18918
  return selectField(selectField(link, '_source'), 'uuid');
18908
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));
18909
18934
  const extraValues = {};
18910
18935
  if (featureCatalogIdentifier) {
18911
18936
  extraValues.featureCatalogIdentifier = featureCatalogIdentifier;
@@ -18913,6 +18938,12 @@ class Gn4FieldMapper {
18913
18938
  if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
18914
18939
  extraValues.sourceOfIdentifiers = sourceOfIdentifiers;
18915
18940
  }
18941
+ if (associatedIdentifiers && associatedIdentifiers.length > 0) {
18942
+ extraValues.associatedIdentifiers = associatedIdentifiers;
18943
+ }
18944
+ if (siblings && siblings.length > 0) {
18945
+ extraValues.siblings = siblings;
18946
+ }
18916
18947
  return Object.keys(extraValues).length > 0
18917
18948
  ? this.addExtra(extraValues, output)
18918
18949
  : output;
@@ -19438,10 +19469,10 @@ class DateService {
19438
19469
  const dateObj = this.getDateObject(date);
19439
19470
  return { locale, dateObj };
19440
19471
  }
19441
- async getDateLocale() {
19472
+ async getDateFnsLocale() {
19442
19473
  const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE;
19443
19474
  const locales = await this.dateLocales;
19444
- return locales[lang];
19475
+ return locales[lang] ?? locales[DEFAULT_LANGUAGE];
19445
19476
  }
19446
19477
  formatDate(date, options) {
19447
19478
  const { locale, dateObj } = this.getLocaleAndDate(date);
@@ -19454,7 +19485,7 @@ class DateService {
19454
19485
  async formatRelativeDateTime(date) {
19455
19486
  const dateObj = this.getDateObject(date);
19456
19487
  const now = new Date();
19457
- const locale = await this.getDateLocale();
19488
+ const locale = await this.getDateFnsLocale();
19458
19489
  return formatDistance(dateObj, now, {
19459
19490
  addSuffix: true,
19460
19491
  locale: locale,
@@ -19489,6 +19520,18 @@ function propagateToDocumentOnly(event) {
19489
19520
  }, 0);
19490
19521
  }
19491
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
+
19492
19535
  function formatUserInfo(userInfo, displayCount = false) {
19493
19536
  const infos = (typeof userInfo === 'string' ? userInfo : '').split('|');
19494
19537
  const count = displayCount ? ` ${infos[3].split(' ')[1]}` : '';
@@ -19548,6 +19591,11 @@ function getGeometryFromGeoJSON(data) {
19548
19591
  }
19549
19592
  return null;
19550
19593
  }
19594
+ function isBoundingBox(value) {
19595
+ return (Array.isArray(value) &&
19596
+ value.length === 4 &&
19597
+ value.every((item) => typeof item === 'number'));
19598
+ }
19551
19599
  function getGeometryBoundingBox(geometry) {
19552
19600
  // use the bounding box if specified in the GeoJSON object
19553
19601
  if (geometry.bbox) {
@@ -20333,6 +20381,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20333
20381
  }]
20334
20382
  }] });
20335
20383
 
20384
+ class AutofocusDirective {
20385
+ constructor() {
20386
+ this.el = inject(ElementRef);
20387
+ this.injector = inject(Injector);
20388
+ }
20389
+ set autofocus(active) {
20390
+ if (!active)
20391
+ return;
20392
+ afterNextRender(() => this.el.nativeElement.focus(), {
20393
+ injector: this.injector,
20394
+ });
20395
+ }
20396
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20397
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.3.19", type: AutofocusDirective, isStandalone: true, selector: "[gnUiAutofocus]", inputs: { autofocus: ["gnUiAutofocus", "autofocus", booleanAttribute] }, ngImport: i0 }); }
20398
+ }
20399
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20400
+ type: Directive,
20401
+ args: [{
20402
+ selector: '[gnUiAutofocus]',
20403
+ standalone: true,
20404
+ }]
20405
+ }], propDecorators: { autofocus: [{
20406
+ type: Input,
20407
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20408
+ }] } });
20409
+
20336
20410
  class ImageFallbackDirective {
20337
20411
  constructor() {
20338
20412
  this.el = inject(ElementRef);
@@ -20360,7 +20434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20360
20434
  }] } });
20361
20435
 
20362
20436
  var name = "geonetwork-ui";
20363
- var version = "2.11.0-dev.8442dae6e";
20437
+ var version = "2.11.0-dev.9ba9c4d3d";
20364
20438
  var engines = {
20365
20439
  node: ">=24"
20366
20440
  };
@@ -20381,6 +20455,7 @@ var peerDependencies = {
20381
20455
  "@angular/core": "19.x || 20.x || 21.x",
20382
20456
  "@angular/forms": "19.x || 20.x || 21.x",
20383
20457
  "@angular/material": "19.x || 20.x || 21.x",
20458
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20384
20459
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20385
20460
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20386
20461
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20390,20 +20465,21 @@ var peerDependencies = {
20390
20465
  "@ngrx/store": "19.x || 20.x || 21.x",
20391
20466
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20392
20467
  "@ngrx/operators": "19.x || 20.x || 21.x",
20393
- "@ngx-translate/core": "16.x",
20394
- "@ngx-translate/http-loader": "16.x",
20395
- "flag-icons": "^7.3.2",
20468
+ "@ngx-translate/core": "17.x",
20469
+ "@ngx-translate/http-loader": "17.x",
20470
+ "flag-icons": "~7.5.0",
20396
20471
  rxjs: "7.x",
20397
20472
  "zone.js": "*",
20398
20473
  tailwindcss: "3.x"
20399
20474
  };
20400
20475
  var dependencies = {
20401
20476
  "@biesbjerg/ngx-translate-extract-marker": "~1.0.0",
20402
- "@camptocamp/ogc-client": "1.3.1-dev.bb345f0",
20403
- "@geospatial-sdk/core": "0.0.5-dev.61",
20404
- "@geospatial-sdk/geocoding": "0.0.5-dev.61",
20405
- "@geospatial-sdk/legend": "0.0.5-dev.61",
20406
- "@geospatial-sdk/openlayers": "0.0.5-dev.61",
20477
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20478
+ "@duckdb/duckdb-wasm": "~1.32.0",
20479
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20480
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20481
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20482
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20407
20483
  "@ltd/j-toml": "~1.38.0",
20408
20484
  "@messageformat/core": "~3.4.0",
20409
20485
  "@ng-icons/core": "~32.5.0",
@@ -20411,7 +20487,7 @@ var dependencies = {
20411
20487
  "@ng-icons/material-icons": "~32.5.0",
20412
20488
  "@ng-icons/tabler-icons": "~32.4.0",
20413
20489
  "@rgrove/parse-xml": "4.2.0",
20414
- alasql: "~4.17.0",
20490
+ "apache-arrow": "~17.0.0",
20415
20491
  "chart.js": "~4.5.1",
20416
20492
  "chroma-js": "~3.2.0",
20417
20493
  "date-fns": "4.1.0",
@@ -20424,7 +20500,6 @@ var dependencies = {
20424
20500
  "ngx-dropzone": "~3.1.0",
20425
20501
  "ngx-translate-messageformat-compiler": "~7.2.0",
20426
20502
  ol: "~10.8.0",
20427
- papaparse: "~5.5.3",
20428
20503
  proj4: "~2.20.4",
20429
20504
  rdflib: "~2.3.5",
20430
20505
  semver: "~7.7.4",
@@ -20669,6 +20744,7 @@ class ElasticsearchService {
20669
20744
  values: uuids,
20670
20745
  },
20671
20746
  },
20747
+ size: uuids.length,
20672
20748
  };
20673
20749
  }
20674
20750
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20719,10 +20795,28 @@ class ElasticsearchService {
20719
20795
  };
20720
20796
  }
20721
20797
  buildPayloadSort(sortBy) {
20722
- if (sortBy === null)
20798
+ if (!sortBy || sortBy.length === 0)
20723
20799
  return undefined;
20724
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy];
20725
- return fields.map((field) => ({ [field[1]]: field[0] }));
20800
+ const fields = Array.isArray(sortBy[0])
20801
+ ? sortBy
20802
+ : [sortBy];
20803
+ return fields.map((field) => {
20804
+ // Sort by nested array of dates only works with the explicit syntax
20805
+ if (field[1].endsWith('.date')) {
20806
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'));
20807
+ return {
20808
+ [field[1]]: {
20809
+ order: field[0],
20810
+ mode: field[0] === 'desc' ? 'max' : 'min',
20811
+ missing: '_last',
20812
+ nested: {
20813
+ path: nestedPath,
20814
+ },
20815
+ },
20816
+ };
20817
+ }
20818
+ return { [field[1]]: field[0] };
20819
+ });
20726
20820
  }
20727
20821
  injectLangInQueryStringFields(queryFieldsPriority) {
20728
20822
  const queryLang = this.getQueryLang();
@@ -20758,7 +20852,13 @@ class ElasticsearchService {
20758
20852
  isCurrentSearchLang() {
20759
20853
  return this.metadataLang === 'current';
20760
20854
  }
20761
- filtersToQuery(filters) {
20855
+ findSpatialFilterExtent(filters) {
20856
+ if (typeof filters === 'string') {
20857
+ return undefined;
20858
+ }
20859
+ return Object.values(filters).find(isBoundingBox);
20860
+ }
20861
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20762
20862
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20763
20863
  const makeQuery = (filter) => {
20764
20864
  if (typeof filter === 'string') {
@@ -20777,7 +20877,9 @@ class ElasticsearchService {
20777
20877
  ? filters
20778
20878
  : Object.keys(filters)
20779
20879
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20880
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20780
20881
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20882
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20781
20883
  .filter((fieldname) => filters[fieldname] &&
20782
20884
  JSON.stringify(filters[fieldname]) !== '{}')
20783
20885
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20813,6 +20915,21 @@ class ElasticsearchService {
20813
20915
  },
20814
20916
  },
20815
20917
  },
20918
+ spatialFilterExtent && {
20919
+ geo_shape: {
20920
+ geom: {
20921
+ shape: {
20922
+ type: 'envelope',
20923
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20924
+ coordinates: [
20925
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20926
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20927
+ ],
20928
+ },
20929
+ relation: 'intersects',
20930
+ },
20931
+ },
20932
+ },
20816
20933
  ].filter(Boolean);
20817
20934
  return queryParts.length > 0 ? queryParts : undefined;
20818
20935
  }
@@ -20839,7 +20956,9 @@ class ElasticsearchService {
20839
20956
  },
20840
20957
  });
20841
20958
  }
20842
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20959
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20960
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20961
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20843
20962
  if (queryFilters) {
20844
20963
  filter.push(...queryFilters);
20845
20964
  }
@@ -20850,7 +20969,10 @@ class ElasticsearchService {
20850
20969
  },
20851
20970
  });
20852
20971
  }
20853
- if (geometry) {
20972
+ const boostGeometry = spatialFilterExtent
20973
+ ? bboxToPolygon(spatialFilterExtent)
20974
+ : geometry;
20975
+ if (boostGeometry) {
20854
20976
  // boosts applied using the filter geometry:
20855
20977
  // * records completely within the geometry receive a boost of 5
20856
20978
  // * records intersecting the geometry receive a boost of 2
@@ -20859,7 +20981,7 @@ class ElasticsearchService {
20859
20981
  should.push({
20860
20982
  geo_shape: {
20861
20983
  geom: {
20862
- shape: geometry,
20984
+ shape: boostGeometry,
20863
20985
  relation: 'within',
20864
20986
  },
20865
20987
  boost: 5.0,
@@ -20867,7 +20989,7 @@ class ElasticsearchService {
20867
20989
  }, {
20868
20990
  geo_shape: {
20869
20991
  geom: {
20870
- shape: geometry,
20992
+ shape: boostGeometry,
20871
20993
  relation: 'intersects',
20872
20994
  },
20873
20995
  boost: 2.0,
@@ -20876,7 +20998,7 @@ class ElasticsearchService {
20876
20998
  // this will boost the results variably depending on their distance from the given geometry
20877
20999
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20878
21000
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20879
- const bbox = getGeometryBoundingBox(geometry);
21001
+ const bbox = getGeometryBoundingBox(boostGeometry);
20880
21002
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20881
21003
  const northToCenter = new LineString([
20882
21004
  [center[0], bbox[3]],
@@ -21202,7 +21324,7 @@ class Gn4Repository {
21202
21324
  }
21203
21325
  getRecord(uniqueIdentifier) {
21204
21326
  return this.gn4SearchApi
21205
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21327
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21206
21328
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21207
21329
  }
21208
21330
  getMultipleRecords(uniqueIdentifiers) {
@@ -21256,19 +21378,28 @@ class Gn4Repository {
21256
21378
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21257
21379
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21258
21380
  }
21259
- getSources(record) {
21260
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21261
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21262
- return this.getMultipleRecords(sourcesIdentifiers);
21263
- }
21264
- return of(null);
21265
- }
21266
- getSourceOf(record) {
21267
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21268
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21269
- return this.getMultipleRecords(sourceOfIdentifiers);
21381
+ getLinkedRecords(record) {
21382
+ const siblings = (record.extras?.['siblings'] ?? []);
21383
+ const relations = [
21384
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21385
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21386
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21387
+ [
21388
+ 'associated',
21389
+ (record.extras?.['associatedIdentifiers'] ?? []),
21390
+ ],
21391
+ ];
21392
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21393
+ if (requested.length === 0) {
21394
+ return of([]);
21270
21395
  }
21271
- return of(null);
21396
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21397
+ record,
21398
+ relation,
21399
+ associationType: relation === 'sibling'
21400
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21401
+ : undefined,
21402
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21272
21403
  }
21273
21404
  aggregate(params) {
21274
21405
  // if aggregations are empty, return an empty object right away
@@ -22529,9 +22660,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22529
22660
  });
22530
22661
 
22531
22662
  const DEFAULT_BASEMAP_LAYER = {
22532
- type: 'xyz',
22533
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22534
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22663
+ type: 'maplibre-style',
22664
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22665
+ clickable: false,
22666
+ hoverable: false,
22535
22667
  };
22536
22668
  const DEFAULT_VIEW = {
22537
22669
  center: [0, 15],
@@ -23246,6 +23378,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23246
23378
  'search_preset',
23247
23379
  'advanced_filters',
23248
23380
  'limit',
23381
+ 'spatial_extent_max_file_size',
23249
23382
  ], warnings, errors);
23250
23383
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23251
23384
  searchConfig =
@@ -23263,6 +23396,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23263
23396
  })),
23264
23397
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23265
23398
  LIMIT: parsedSearchSection.limit,
23399
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23266
23400
  };
23267
23401
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23268
23402
  metadataQualityConfig =
@@ -24520,12 +24654,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24520
24654
  type: Input
24521
24655
  }] } });
24522
24656
 
24657
+ function provideLocalizedDateAdapter() {
24658
+ return [
24659
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24660
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24661
+ {
24662
+ provide: DateAdapter,
24663
+ useFactory: () => {
24664
+ const dateService = inject(DateService);
24665
+ const adapter = new DateFnsAdapter();
24666
+ dateService
24667
+ .getDateFnsLocale()
24668
+ .then((locale) => adapter.setLocale(locale));
24669
+ return adapter;
24670
+ },
24671
+ },
24672
+ ];
24673
+ }
24674
+
24523
24675
  class DatePickerComponent {
24524
24676
  constructor() {
24525
- this.dateAdapter = inject(DateAdapter);
24526
- this.translate = inject(TranslateService);
24527
24677
  this.dateChange = new EventEmitter();
24528
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24529
24678
  }
24530
24679
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24531
24680
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DatePickerComponent, isStandalone: true, selector: "gn-ui-date-picker", inputs: { date: "date" }, outputs: { dateChange: "dateChange" }, providers: [
@@ -24533,30 +24682,19 @@ class DatePickerComponent {
24533
24682
  provideNgIconsConfig({
24534
24683
  size: '1.5rem',
24535
24684
  }),
24536
- {
24537
- provide: MAT_DATE_LOCALE,
24538
- useFactory: (locale) => locale,
24539
- },
24540
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24685
+ provideLocalizedDateAdapter(),
24686
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24541
24687
  }
24542
24688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24543
24689
  type: Component,
24544
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24545
- MatNativeDateModule,
24546
- MatDatepickerModule,
24547
- ButtonComponent,
24548
- NgIconComponent,
24549
- ], providers: [
24690
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24550
24691
  provideIcons({ iconoirCalendar }),
24551
24692
  provideNgIconsConfig({
24552
24693
  size: '1.5rem',
24553
24694
  }),
24554
- {
24555
- provide: MAT_DATE_LOCALE,
24556
- useFactory: (locale) => locale,
24557
- },
24695
+ provideLocalizedDateAdapter(),
24558
24696
  ], template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24559
- }], ctorParameters: () => [], propDecorators: { date: [{
24697
+ }], propDecorators: { date: [{
24560
24698
  type: Input
24561
24699
  }], dateChange: [{
24562
24700
  type: Output
@@ -24564,58 +24702,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24564
24702
 
24565
24703
  class DateRangeDropdownComponent {
24566
24704
  constructor() {
24567
- this.overlayContainer = inject(OverlayContainer);
24705
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24706
+ this.dateAdapter = inject(DateAdapter);
24707
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24568
24708
  this.cdr = inject(ChangeDetectorRef);
24569
- this.startDateChange = new EventEmitter();
24570
- this.endDateChange = new EventEmitter();
24571
- this.isPickerDisplayed = false;
24709
+ this.dateRange = {};
24710
+ this.dateRangeChange = new EventEmitter();
24711
+ this.overlayPositions = [
24712
+ {
24713
+ originX: 'start',
24714
+ originY: 'bottom',
24715
+ overlayX: 'start',
24716
+ overlayY: 'top',
24717
+ offsetY: 8,
24718
+ },
24719
+ {
24720
+ originX: 'start',
24721
+ originY: 'top',
24722
+ overlayX: 'start',
24723
+ overlayY: 'bottom',
24724
+ offsetY: -8,
24725
+ },
24726
+ ];
24727
+ this.scrollStrategy = this.scrollStrategies.reposition();
24728
+ this.overlayOpen = false;
24729
+ this.expandedBound = 'start';
24730
+ this.invalidBounds = { start: false, end: false };
24731
+ // redraw when the lazily loaded UI locale reaches the adapter
24732
+ this.dateAdapter.localeChanges
24733
+ .pipe(takeUntilDestroyed())
24734
+ .subscribe(() => this.cdr.markForCheck());
24572
24735
  }
24573
- ngAfterViewChecked() {
24574
- this.checkPickerOverlay();
24736
+ get startDate() {
24737
+ return this.dateRange.start;
24575
24738
  }
24576
- checkPickerOverlay() {
24577
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24578
- setTimeout(() => {
24579
- this.isPickerDisplayed =
24580
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24581
- null;
24582
- this.cdr.detectChanges();
24583
- }, 200); // FIXME: find a better way to deal with animation delay
24739
+ get endDate() {
24740
+ return this.dateRange.end;
24741
+ }
24742
+ get selectedDatesCount() {
24743
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24744
+ }
24745
+ openOverlay() {
24746
+ this.expandedBound = 'start';
24747
+ this.overlayOpen = true;
24748
+ }
24749
+ closeOverlay() {
24750
+ this.overlayOpen = false;
24751
+ }
24752
+ toggleBound(bound) {
24753
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24754
+ }
24755
+ formatDate(date) {
24756
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24757
+ }
24758
+ onDateInput(bound, event) {
24759
+ const typedText = event.target.value.trim();
24760
+ const date = typedText
24761
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24762
+ : null;
24763
+ if (!this.isAcceptable(bound, typedText, date)) {
24764
+ // keep the text as typed and flag it; the filter stays on its last value
24765
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24766
+ return;
24767
+ }
24768
+ if (bound === 'start')
24769
+ this.setStartDate(date);
24770
+ else
24771
+ this.setEndDate(date);
24772
+ }
24773
+ normalizeDateInput(bound, event) {
24774
+ if (this.invalidBounds[bound])
24775
+ return;
24776
+ const date = bound === 'start' ? this.startDate : this.endDate;
24777
+ const input = event.target;
24778
+ input.value = date ? this.formatDate(date) : '';
24779
+ }
24780
+ isAcceptable(bound, typedText, date) {
24781
+ if (!typedText)
24782
+ return true;
24783
+ if (!date || !this.dateAdapter.isValid(date))
24784
+ return false;
24785
+ return bound === 'start'
24786
+ ? !this.endDate || date <= this.endDate
24787
+ : !this.startDate || date >= this.startDate;
24788
+ }
24789
+ setStartDate(date) {
24790
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24791
+ this.applyDateRange({ ...this.dateRange, start: date });
24792
+ this.expandedBound = 'end';
24793
+ }
24794
+ setEndDate(date) {
24795
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24796
+ this.applyDateRange({ ...this.dateRange, end: date });
24797
+ this.expandedBound = null;
24798
+ }
24799
+ clearDates(event) {
24800
+ this.invalidBounds = { start: false, end: false };
24801
+ this.expandedBound = 'start';
24802
+ this.applyDateRange({});
24803
+ propagateToDocumentOnly(event);
24804
+ }
24805
+ applyDateRange(dateRange) {
24806
+ this.dateRange = {
24807
+ ...(dateRange.start && { start: dateRange.start }),
24808
+ ...(dateRange.end && { end: dateRange.end }),
24809
+ };
24810
+ this.dateRangeChange.emit(this.dateRange);
24584
24811
  }
24585
24812
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24586
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", startDate: "startDate", endDate: "endDate" }, outputs: { startDateChange: "startDateChange", endDateChange: "endDateChange" }, providers: [
24813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", dateRange: "dateRange" }, outputs: { dateRangeChange: "dateRangeChange" }, providers: [
24587
24814
  provideIcons({
24588
- matExpandMore,
24815
+ iconoirCalendar,
24816
+ matClose,
24589
24817
  matExpandLess,
24818
+ matExpandMore,
24590
24819
  }),
24591
- ], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"], dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }] }); }
24820
+ provideNgIconsConfig({
24821
+ size: '1.5rem',
24822
+ }),
24823
+ provideLocalizedDateAdapter(),
24824
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: AutofocusDirective, selector: "[gnUiAutofocus]", inputs: ["gnUiAutofocus"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24592
24825
  }
24593
24826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24594
24827
  type: Component,
24595
24828
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24596
- NgIconComponent,
24597
- MatNativeDateModule,
24598
- MatDatepickerModule,
24829
+ AutofocusDirective,
24599
24830
  ButtonComponent,
24831
+ MatDatepickerModule,
24832
+ NgIcon,
24833
+ NgTemplateOutlet,
24834
+ OverlayModule,
24835
+ TranslateDirective,
24836
+ TranslatePipe,
24600
24837
  ], providers: [
24601
24838
  provideIcons({
24602
- matExpandMore,
24839
+ iconoirCalendar,
24840
+ matClose,
24603
24841
  matExpandLess,
24842
+ matExpandMore,
24604
24843
  }),
24605
- ], 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"] }]
24606
- }], propDecorators: { title: [{
24607
- type: Input
24608
- }], startDate: [{
24844
+ provideNgIconsConfig({
24845
+ size: '1.5rem',
24846
+ }),
24847
+ provideLocalizedDateAdapter(),
24848
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"] }]
24849
+ }], ctorParameters: () => [], propDecorators: { title: [{
24609
24850
  type: Input
24610
- }], endDate: [{
24851
+ }], dateRange: [{
24611
24852
  type: Input
24612
- }], startDateChange: [{
24853
+ }], dateRangeChange: [{
24613
24854
  type: Output
24614
- }], endDateChange: [{
24615
- type: Output
24616
- }], picker: [{
24855
+ }], overlayOrigin: [{
24617
24856
  type: ViewChild,
24618
- args: ['picker']
24857
+ args: ['overlayOrigin']
24619
24858
  }] } });
24620
24859
 
24621
24860
  class DateRangePickerComponent {
@@ -24629,20 +24868,17 @@ class DateRangePickerComponent {
24629
24868
  provideNgIconsConfig({
24630
24869
  size: '1.5rem',
24631
24870
  }),
24632
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24871
+ provideLocalizedDateAdapter(),
24872
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24633
24873
  }
24634
24874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24635
24875
  type: Component,
24636
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24637
- MatNativeDateModule,
24638
- MatDatepickerModule,
24639
- ButtonComponent,
24640
- NgIconComponent,
24641
- ], providers: [
24876
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24642
24877
  provideIcons({ iconoirCalendar }),
24643
24878
  provideNgIconsConfig({
24644
24879
  size: '1.5rem',
24645
24880
  }),
24881
+ provideLocalizedDateAdapter(),
24646
24882
  ], template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24647
24883
  }], propDecorators: { startDate: [{
24648
24884
  type: Input
@@ -24660,28 +24896,68 @@ class DragAndDropFileInputComponent {
24660
24896
  constructor() {
24661
24897
  this.placeholder = placeholder;
24662
24898
  this.accept = '*';
24899
+ this.maxFileSizeMb = null;
24900
+ this.icon = null;
24901
+ this.dropzoneBackgroundColor = null;
24902
+ this.textClass = '';
24903
+ this.extraClass = '';
24904
+ this.showFileName = true;
24663
24905
  this.fileChange = new EventEmitter();
24906
+ this.errorChange = new EventEmitter();
24664
24907
  this.selectedFile = null;
24665
24908
  }
24666
24909
  get fileName() {
24667
24910
  return this.selectedFile && this.selectedFile.name;
24668
24911
  }
24912
+ get maxFileSizeBytes() {
24913
+ return typeof this.maxFileSizeMb === 'number'
24914
+ ? megabytesToBytes(this.maxFileSizeMb)
24915
+ : null;
24916
+ }
24669
24917
  selectFile(event) {
24918
+ if (event.rejectedFiles?.length) {
24919
+ const reason = event.rejectedFiles[0].reason;
24920
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24921
+ return;
24922
+ }
24670
24923
  this.selectedFile = event.addedFiles[0];
24671
24924
  this.fileChange.emit(this.selectedFile);
24672
24925
  }
24926
+ openFileSelector() {
24927
+ this.dropzone.showFileSelector();
24928
+ }
24929
+ clear() {
24930
+ this.selectedFile = null;
24931
+ }
24673
24932
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24674
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept" }, outputs: { fileChange: "fileChange" }, ngImport: i0, template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$7.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }] }); }
24933
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept", maxFileSizeMb: "maxFileSizeMb", icon: "icon", dropzoneBackgroundColor: "dropzoneBackgroundColor", textClass: "textClass", extraClass: "extraClass", showFileName: "showFileName" }, outputs: { fileChange: "fileChange", errorChange: "errorChange" }, viewQueries: [{ propertyName: "dropzone", first: true, predicate: NgxDropzoneComponent, descendants: true }], ngImport: i0, template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$8.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
24675
24934
  }
24676
24935
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24677
24936
  type: Component,
24678
- args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule], template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"] }]
24937
+ args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule, NgIcon], template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"] }]
24679
24938
  }], propDecorators: { placeholder: [{
24680
24939
  type: Input
24681
24940
  }], accept: [{
24682
24941
  type: Input
24942
+ }], maxFileSizeMb: [{
24943
+ type: Input
24944
+ }], icon: [{
24945
+ type: Input
24946
+ }], dropzoneBackgroundColor: [{
24947
+ type: Input
24948
+ }], textClass: [{
24949
+ type: Input
24950
+ }], extraClass: [{
24951
+ type: Input
24952
+ }], showFileName: [{
24953
+ type: Input
24683
24954
  }], fileChange: [{
24684
24955
  type: Output
24956
+ }], errorChange: [{
24957
+ type: Output
24958
+ }], dropzone: [{
24959
+ type: ViewChild,
24960
+ args: [NgxDropzoneComponent]
24685
24961
  }] } });
24686
24962
 
24687
24963
  class DropdownMultiselectComponent {
@@ -24836,7 +25112,7 @@ class DropdownMultiselectComponent {
24836
25112
  matExpandMore,
24837
25113
  matExpandLess,
24838
25114
  }),
24839
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25115
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24840
25116
  }
24841
25117
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24842
25118
  type: Component,
@@ -24846,7 +25122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24846
25122
  matExpandMore,
24847
25123
  matExpandLess,
24848
25124
  }),
24849
- ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
25125
+ ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
24850
25126
  }], propDecorators: { title: [{
24851
25127
  type: Input
24852
25128
  }], choices: [{
@@ -25018,7 +25294,7 @@ class DropdownSelectorComponent {
25018
25294
  provideNgIconsConfig({
25019
25295
  size: '1.5em',
25020
25296
  }),
25021
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25297
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25022
25298
  }
25023
25299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25024
25300
  type: Component,
@@ -25758,6 +26034,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25758
26034
  type: Output
25759
26035
  }] } });
25760
26036
 
26037
+ marker('search.filters.spatialExtent.import');
26038
+ marker('search.filters.spatialExtent.helpText');
26039
+ marker('search.filters.spatialExtent.error.title');
26040
+ marker('search.filters.spatialExtent.bboxPrefix');
26041
+ marker('search.filters.spatialExtent.bboxDelete');
26042
+ class SpatialExtentDropdownComponent {
26043
+ constructor() {
26044
+ this.cd = inject(ChangeDetectorRef);
26045
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26046
+ this.maxFileSizeMb = null;
26047
+ this.bboxChange = new EventEmitter();
26048
+ this.errorChange = new EventEmitter();
26049
+ this.bbox = null;
26050
+ this.fileName = '';
26051
+ this.overlayPositions = [
26052
+ {
26053
+ originX: 'start',
26054
+ originY: 'bottom',
26055
+ overlayX: 'start',
26056
+ overlayY: 'top',
26057
+ offsetY: 8,
26058
+ },
26059
+ {
26060
+ originX: 'start',
26061
+ originY: 'top',
26062
+ overlayX: 'start',
26063
+ overlayY: 'bottom',
26064
+ offsetY: -8,
26065
+ },
26066
+ ];
26067
+ this.scrollStrategy = this.scrollStrategies.reposition();
26068
+ this.overlayOpen = false;
26069
+ this.overlayMinWidth = 'none';
26070
+ this.errorKey = null;
26071
+ }
26072
+ get hasSelection() {
26073
+ return !!this.bbox;
26074
+ }
26075
+ openOverlay() {
26076
+ this.overlayMinWidth =
26077
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26078
+ .width + 'px';
26079
+ this.overlayOpen = true;
26080
+ }
26081
+ closeOverlay() {
26082
+ this.overlayOpen = false;
26083
+ }
26084
+ toggleOverlay() {
26085
+ if (this.overlayOpen) {
26086
+ this.closeOverlay();
26087
+ }
26088
+ else {
26089
+ this.openOverlay();
26090
+ }
26091
+ }
26092
+ async handleFileSelected(file) {
26093
+ this.errorKey = null;
26094
+ let content;
26095
+ try {
26096
+ content = await readFileAsText(file);
26097
+ const parsed = JSON.parse(content);
26098
+ const geometry = getGeometryFromGeoJSON(parsed);
26099
+ if (!geometry) {
26100
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26101
+ return;
26102
+ }
26103
+ const bbox = getGeometryBoundingBox(geometry);
26104
+ this.bbox = bbox;
26105
+ this.fileName = file.name;
26106
+ this.bboxChange.emit(bbox);
26107
+ this.cd.markForCheck();
26108
+ }
26109
+ catch {
26110
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26111
+ return;
26112
+ }
26113
+ }
26114
+ handleFileError(error) {
26115
+ if (error === 'file-too-large') {
26116
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26117
+ maxSize: this.maxFileSizeMb,
26118
+ });
26119
+ }
26120
+ else {
26121
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26122
+ }
26123
+ }
26124
+ setError(errorKey, params) {
26125
+ this.errorKey = errorKey;
26126
+ this.errorChange.emit({ key: errorKey, params });
26127
+ this.cd.markForCheck();
26128
+ }
26129
+ removeSelection(event) {
26130
+ this.bbox = null;
26131
+ this.fileName = '';
26132
+ this.errorKey = null;
26133
+ this.fileInput?.clear();
26134
+ this.bboxChange.emit(null);
26135
+ propagateToDocumentOnly(event);
26136
+ }
26137
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26138
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SpatialExtentDropdownComponent, isStandalone: true, selector: "gn-ui-spatial-extent-dropdown", inputs: { title: "title", maxFileSizeMb: "maxFileSizeMb" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26139
+ provideIcons({
26140
+ iconoirCheckCircle,
26141
+ iconoirImport,
26142
+ iconoirSquareDashed,
26143
+ iconoirTrash,
26144
+ matClose,
26145
+ matExpandLess,
26146
+ matExpandMore,
26147
+ }),
26148
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "fileInput", first: true, predicate: DragAndDropFileInputComponent, descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26149
+ }
26150
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26151
+ type: Component,
26152
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26153
+ ButtonComponent,
26154
+ NgIcon,
26155
+ OverlayModule,
26156
+ TranslatePipe,
26157
+ DragAndDropFileInputComponent,
26158
+ ], providers: [
26159
+ provideIcons({
26160
+ iconoirCheckCircle,
26161
+ iconoirImport,
26162
+ iconoirSquareDashed,
26163
+ iconoirTrash,
26164
+ matClose,
26165
+ matExpandLess,
26166
+ matExpandMore,
26167
+ }),
26168
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n" }]
26169
+ }], propDecorators: { title: [{
26170
+ type: Input
26171
+ }], maxFileSizeMb: [{
26172
+ type: Input
26173
+ }], bboxChange: [{
26174
+ type: Output
26175
+ }], errorChange: [{
26176
+ type: Output
26177
+ }], overlayOrigin: [{
26178
+ type: ViewChild,
26179
+ args: ['overlayOrigin']
26180
+ }], overlay: [{
26181
+ type: ViewChild,
26182
+ args: [CdkConnectedOverlay]
26183
+ }], fileInput: [{
26184
+ type: ViewChild,
26185
+ args: [DragAndDropFileInputComponent]
26186
+ }] } });
26187
+
25761
26188
  class CellPopinComponent {
25762
26189
  constructor() {
25763
26190
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25834,7 +26261,7 @@ class CellPopinComponent {
25834
26261
  this.isOpen = false;
25835
26262
  }
25836
26263
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25837
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
26264
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
25838
26265
  }
25839
26266
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25840
26267
  type: Component,
@@ -27924,6 +28351,23 @@ class DateRangeSearchField extends SimpleSearchField {
27924
28351
  return 'dateRange';
27925
28352
  }
27926
28353
  }
28354
+ class BoundingBoxSearchField extends SimpleSearchField {
28355
+ getAvailableValues() {
28356
+ return of([]);
28357
+ }
28358
+ getFiltersForValues(values) {
28359
+ return of({
28360
+ [this.esFieldName]: values.map(Number),
28361
+ });
28362
+ }
28363
+ getValuesForFilter(filters) {
28364
+ const filter = filters[this.esFieldName];
28365
+ return of(isBoundingBox(filter) ? filter : []);
28366
+ }
28367
+ getType() {
28368
+ return 'spatialExtent';
28369
+ }
28370
+ }
27927
28371
  marker('search.filters.availableServices.view');
27928
28372
  marker('search.filters.availableServices.download');
27929
28373
  class AvailableServicesField extends SimpleSearchField {
@@ -28068,6 +28512,7 @@ marker('search.filters.producerOrg');
28068
28512
  marker('search.filters.publisherOrg');
28069
28513
  marker('search.filters.user');
28070
28514
  marker('search.filters.changeDate');
28515
+ marker('search.filters.spatialExtent');
28071
28516
  class FieldsService {
28072
28517
  constructor() {
28073
28518
  this.injector = inject(Injector);
@@ -28091,6 +28536,7 @@ class FieldsService {
28091
28536
  user: new UserSearchField(this.injector),
28092
28537
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28093
28538
  availableServices: new AvailableServicesField(this.injector),
28539
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28094
28540
  };
28095
28541
  }
28096
28542
  get supportedFields() {
@@ -29353,7 +29799,7 @@ class ContactPillComponent {
29353
29799
  this.overlayOpen = false;
29354
29800
  }
29355
29801
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29356
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29802
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29357
29803
  }
29358
29804
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29359
29805
  type: Component,
@@ -31275,19 +31721,111 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31275
31721
  type: Output
31276
31722
  }] } });
31277
31723
 
31724
+ class NotificationsService {
31725
+ constructor() {
31726
+ this.notifications$ = new BehaviorSubject([]);
31727
+ }
31728
+ showNotification(content, timeoutMs, error) {
31729
+ error && console.error(error);
31730
+ const id = Math.floor(Math.random() * 1000000);
31731
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31732
+ if (typeof timeoutMs !== 'undefined') {
31733
+ setTimeout(() => {
31734
+ this.removeNotificationById(id);
31735
+ }, timeoutMs);
31736
+ }
31737
+ return id;
31738
+ }
31739
+ removeNotificationById(id) {
31740
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31741
+ }
31742
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31743
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31744
+ }
31745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31746
+ type: Injectable,
31747
+ args: [{
31748
+ providedIn: 'root',
31749
+ }]
31750
+ }] });
31751
+
31752
+ class NotificationsContainerComponent {
31753
+ constructor() {
31754
+ this.notificationsService = inject(NotificationsService);
31755
+ }
31756
+ trackById(index, notification) {
31757
+ return notification.id;
31758
+ }
31759
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31760
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31761
+ trigger('enterExit', [
31762
+ transition(':enter', [
31763
+ animate('150ms', keyframes([
31764
+ style({ transform: 'scale(1)', opacity: 0 }),
31765
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31766
+ style({ transform: 'scale(1)', opacity: 1 }),
31767
+ ])),
31768
+ ]),
31769
+ transition(':leave', [
31770
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31771
+ ]),
31772
+ ]),
31773
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31774
+ }
31775
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31776
+ type: Component,
31777
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31778
+ trigger('enterExit', [
31779
+ transition(':enter', [
31780
+ animate('150ms', keyframes([
31781
+ style({ transform: 'scale(1)', opacity: 0 }),
31782
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31783
+ style({ transform: 'scale(1)', opacity: 1 }),
31784
+ ])),
31785
+ ]),
31786
+ transition(':leave', [
31787
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31788
+ ]),
31789
+ ]),
31790
+ ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31791
+ }] });
31792
+
31278
31793
  class FilterDropdownComponent {
31279
31794
  constructor() {
31280
31795
  this.searchFacade = inject(SearchFacade);
31281
31796
  this.searchService = inject(SearchService);
31282
31797
  this.fieldsService = inject(FieldsService);
31798
+ this.notificationsService = inject(NotificationsService);
31799
+ this.translateService = inject(TranslateService);
31800
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31283
31801
  this.selected$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$1((fieldValues) => fieldValues[this.fieldName]), filter$1((selected) => !!selected), startWith$1([]), catchError(() => of([])));
31284
- this.selectedDateRange$ = this.selected$.pipe(map$1((selectedDateRange) => selectedDateRange));
31802
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31803
+ this.spatialExtentErrorNotificationId = null;
31285
31804
  }
31286
31805
  onSelectedValues(values) {
31287
31806
  this.fieldsService
31288
31807
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31289
31808
  .subscribe((filters) => this.searchService.updateFilters(filters));
31290
31809
  }
31810
+ onBboxChange(bbox) {
31811
+ console.log(bbox);
31812
+ this.clearSpatialExtentErrorNotification();
31813
+ }
31814
+ onSpatialExtentError(error) {
31815
+ this.clearSpatialExtentErrorNotification();
31816
+ this.spatialExtentErrorNotificationId =
31817
+ this.notificationsService.showNotification({
31818
+ type: 'error',
31819
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31820
+ text: this.translateService.instant(error.key, error.params),
31821
+ });
31822
+ }
31823
+ clearSpatialExtentErrorNotification() {
31824
+ if (this.spatialExtentErrorNotificationId === null)
31825
+ return;
31826
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31827
+ this.spatialExtentErrorNotificationId = null;
31828
+ }
31291
31829
  ngOnInit() {
31292
31830
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31293
31831
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31295,27 +31833,15 @@ class FilterDropdownComponent {
31295
31833
  value: v.value.toString(), // converting to string for the dropdown
31296
31834
  }))), catchError(() => of([])));
31297
31835
  }
31298
- onStartDateChange(start) {
31299
- if (!start)
31300
- return;
31301
- this.dateRange = { ...this.dateRange, start };
31302
- }
31303
- onEndDateChange(end) {
31304
- if (!end)
31305
- return;
31306
- this.dateRange = { ...this.dateRange, end };
31307
- if (this.dateRange.start && this.dateRange.end) {
31308
- this.fieldsService
31309
- .buildFiltersFromFieldValues({
31310
- [this.fieldName]: this.dateRange,
31311
- })
31312
- .subscribe((filters) => {
31313
- return this.searchService.updateFilters(filters);
31314
- });
31315
- }
31836
+ onDateRangeChange(dateRange) {
31837
+ this.fieldsService
31838
+ .buildFiltersFromFieldValues({
31839
+ [this.fieldName]: dateRange,
31840
+ })
31841
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31316
31842
  }
31317
31843
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31318
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31844
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31319
31845
  }
31320
31846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31321
31847
  type: Component,
@@ -31323,7 +31849,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31323
31849
  CommonModule,
31324
31850
  DateRangeDropdownComponent,
31325
31851
  DropdownMultiselectComponent,
31326
- ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31852
+ SpatialExtentDropdownComponent,
31853
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31327
31854
  }], propDecorators: { fieldName: [{
31328
31855
  type: Input
31329
31856
  }], title: [{
@@ -31793,11 +32320,7 @@ const SortByEnum = {
31793
32320
  RELEVANCY: ['desc', '_score'],
31794
32321
  QUALITY_SCORE: ['desc', 'qualityScore'],
31795
32322
  CHANGE_DATE: ['desc', 'changeDate'],
31796
- RESOURCE_DATES: [
31797
- ['desc', 'revisionDateForResource'],
31798
- ['desc', 'publicationDateForResource'],
31799
- ['desc', 'creationDateForResource'],
31800
- ],
32323
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
31801
32324
  };
31802
32325
 
31803
32326
  class SortByComponent {
@@ -31811,7 +32334,7 @@ class SortByComponent {
31811
32334
  },
31812
32335
  {
31813
32336
  label: marker('results.sortBy.dateStamp'),
31814
- value: SortByEnum.RESOURCE_DATES,
32337
+ value: SortByEnum.RESOURCE_DATE,
31815
32338
  },
31816
32339
  {
31817
32340
  label: marker('results.sortBy.popularity'),
@@ -31863,74 +32386,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31863
32386
  args: ['gnUiSearchStateContainer']
31864
32387
  }] } });
31865
32388
 
31866
- class NotificationsService {
31867
- constructor() {
31868
- this.notifications$ = new BehaviorSubject([]);
31869
- }
31870
- showNotification(content, timeoutMs, error) {
31871
- error && console.error(error);
31872
- const id = Math.floor(Math.random() * 1000000);
31873
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31874
- if (typeof timeoutMs === 'undefined')
31875
- return;
31876
- setTimeout(() => {
31877
- this.removeNotificationById(id);
31878
- }, timeoutMs);
31879
- }
31880
- removeNotificationById(id) {
31881
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31882
- }
31883
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31884
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31885
- }
31886
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31887
- type: Injectable,
31888
- args: [{
31889
- providedIn: 'root',
31890
- }]
31891
- }] });
31892
-
31893
- class NotificationsContainerComponent {
31894
- constructor() {
31895
- this.notificationsService = inject(NotificationsService);
31896
- }
31897
- trackById(index, notification) {
31898
- return notification.id;
31899
- }
31900
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31901
- 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: [
31902
- trigger('enterExit', [
31903
- transition(':enter', [
31904
- animate('150ms', keyframes([
31905
- style({ transform: 'scale(1)', opacity: 0 }),
31906
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31907
- style({ transform: 'scale(1)', opacity: 1 }),
31908
- ])),
31909
- ]),
31910
- transition(':leave', [
31911
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31912
- ]),
31913
- ]),
31914
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31915
- }
31916
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31917
- type: Component,
31918
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31919
- trigger('enterExit', [
31920
- transition(':enter', [
31921
- animate('150ms', keyframes([
31922
- style({ transform: 'scale(1)', opacity: 0 }),
31923
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31924
- style({ transform: 'scale(1)', opacity: 1 }),
31925
- ])),
31926
- ]),
31927
- transition(':leave', [
31928
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31929
- ]),
31930
- ]),
31931
- ], 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" }]
31932
- }] });
31933
-
31934
32389
  class ResultsTableContainerComponent {
31935
32390
  constructor() {
31936
32391
  this.searchFacade = inject(SearchFacade);
@@ -32015,12 +32470,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32015
32470
 
32016
32471
  marker('search.filters.summaryLabel.user');
32017
32472
  marker('search.filters.summaryLabel.changeDate');
32473
+ const OPEN_BOUND = '…';
32018
32474
  class SearchFiltersSummaryItemComponent {
32019
32475
  constructor() {
32020
32476
  this.searchFacade = inject(SearchFacade);
32021
32477
  this.searchService = inject(SearchService);
32022
32478
  this.fieldsService = inject(FieldsService);
32023
- this.datePipe = inject(DatePipe);
32479
+ this.dateService = inject(DateService);
32024
32480
  this.translate = inject(TranslateService);
32025
32481
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32026
32482
  ? fieldValues[this.fieldName]
@@ -32047,9 +32503,10 @@ class SearchFiltersSummaryItemComponent {
32047
32503
  getReadableValues(fieldValues) {
32048
32504
  return fieldValues.map((value) => {
32049
32505
  if (this.fieldType === 'dateRange') {
32506
+ const { start, end } = value;
32050
32507
  return {
32051
32508
  value,
32052
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32509
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32053
32510
  };
32054
32511
  }
32055
32512
  else if (this.fieldName === 'user') {
@@ -32060,6 +32517,15 @@ class SearchFiltersSummaryItemComponent {
32060
32517
  }
32061
32518
  });
32062
32519
  }
32520
+ formatBound(date) {
32521
+ return date
32522
+ ? this.dateService.formatDate(date, {
32523
+ day: '2-digit',
32524
+ month: '2-digit',
32525
+ year: 'numeric',
32526
+ })
32527
+ : OPEN_BOUND;
32528
+ }
32063
32529
  async removeFilterValue(fieldValue) {
32064
32530
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32065
32531
  const updatedFieldValues = currentFieldValues
@@ -32072,11 +32538,11 @@ class SearchFiltersSummaryItemComponent {
32072
32538
  .subscribe((filters) => this.searchService.updateFilters(filters));
32073
32539
  }
32074
32540
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32075
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, providers: [DatePipe], ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32541
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32076
32542
  }
32077
32543
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32078
32544
  type: Component,
32079
- args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], providers: [DatePipe], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32545
+ args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32080
32546
  }], propDecorators: { fieldName: [{
32081
32547
  type: Input
32082
32548
  }] } });
@@ -32320,11 +32786,11 @@ class AddLayerFromFileComponent {
32320
32786
  }, 5000);
32321
32787
  }
32322
32788
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32323
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept"], outputs: ["fileChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32789
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32324
32790
  }
32325
32791
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32326
32792
  type: Component,
32327
- args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32793
+ args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32328
32794
  }] });
32329
32795
 
32330
32796
  class LayersPanelComponent {
@@ -32489,8 +32955,7 @@ const closeMetadata = createAction('[Metadata view] close');
32489
32955
  Related actions
32490
32956
  */
32491
32957
  const setRelated = createAction('[Metadata view] Set related records', props());
32492
- const setSources = createAction('[Metadata view] Set sources', props());
32493
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32958
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32494
32959
  /*
32495
32960
  ChartConfig actions
32496
32961
  */
@@ -32522,9 +32987,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32522
32987
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32523
32988
  setChartConfig: setChartConfig,
32524
32989
  setIncompleteMetadata: setIncompleteMetadata,
32525
- setRelated: setRelated,
32526
- setSourceOf: setSourceOf,
32527
- setSources: setSources
32990
+ setLinkedRecords: setLinkedRecords,
32991
+ setRelated: setRelated
32528
32992
  });
32529
32993
 
32530
32994
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32567,12 +33031,9 @@ on(loadFullMetadata, (state) => ({
32567
33031
  on(setRelated, (state, { related }) => ({
32568
33032
  ...state,
32569
33033
  related,
32570
- })), on(setSources, (state, { sources }) => ({
33034
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32571
33035
  ...state,
32572
- sources,
32573
- })), on(setSourceOf, (state, { sourceOf }) => ({
32574
- ...state,
32575
- sourceOf,
33036
+ linkedRecords,
32576
33037
  })),
32577
33038
  /*
32578
33039
  ChartConfig reducers
@@ -32634,8 +33095,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32634
33095
  Related selectors
32635
33096
  */
32636
33097
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32637
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32638
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33098
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32639
33099
  /*
32640
33100
  Metadata selectors
32641
33101
  */
@@ -32736,212 +33196,10 @@ function parseHeaders(httpHeaders) {
32736
33196
  return result;
32737
33197
  }
32738
33198
 
32739
- async function inferDatasetType(url, typeHint) {
32740
- const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32741
- const fileExtension = fileExtensionMatches && fileExtensionMatches.length
32742
- ? fileExtensionMatches[1].toLowerCase()
32743
- : null;
32744
- // 1. type hint
32745
- if (typeHint)
32746
- return Promise.resolve(typeHint);
32747
- // 2. content-type header
32748
- const headers = await fetchHeaders(url);
32749
- if ('supportedType' in headers)
32750
- return headers.supportedType;
32751
- // 3. file extension from url
32752
- else if (SupportedTypes.indexOf(fileExtension) > -1)
32753
- return fileExtension;
32754
- // no type inferred or hinted
32755
- if ('mimeType' in headers)
32756
- throw FetchError.unsupportedType(headers.mimeType);
32757
- else
32758
- throw FetchError.unknownType();
32759
- }
32760
- function fetchHeaders(url) {
32761
- return sharedFetch(url, 'HEAD')
32762
- .catch((error) => {
32763
- throw FetchError.corsOrNetwork(error.message);
32764
- })
32765
- .then((response) => {
32766
- if (!response.ok) {
32767
- throw FetchError.http(response.status);
32768
- }
32769
- return parseHeaders(response.headers);
32770
- });
32771
- }
32772
- function fetchDataAsText(url, cacheActive) {
32773
- const fetchFactory = () => sharedFetch(url)
32774
- .catch((error) => {
32775
- throw FetchError.corsOrNetwork(error.message);
32776
- })
32777
- .then(async (response) => {
32778
- if (!response.ok) {
32779
- const clonedResponse = response.clone();
32780
- throw FetchError.http(response.status, await clonedResponse.text());
32781
- }
32782
- const clonedResponse = response.clone();
32783
- return clonedResponse.text();
32784
- });
32785
- return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
32786
- }
32787
- function fetchDataAsArrayBuffer(url, cacheActive) {
32788
- const fetchFactory = () => sharedFetch(url)
32789
- .catch((error) => {
32790
- throw FetchError.corsOrNetwork(error.message);
32791
- })
32792
- .then(async (response) => {
32793
- if (!response.ok) {
32794
- throw FetchError.http(response.status, await response.text());
32795
- }
32796
- // convert to a numeric array so that we can store the response in cache
32797
- return Array.from(new Uint8Array(await response.arrayBuffer()));
32798
- });
32799
- return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
32800
- return new Uint8Array(array).buffer;
32801
- });
32802
- }
32803
- function tryParseDate(input) {
32804
- if (typeof input !== 'string')
32805
- return null;
32806
- function tryIso(value) {
32807
- const parsed = parseISO(value);
32808
- return isNaN(parsed.getDate()) ? null : parsed;
32809
- }
32810
- function tryFormat(value, format) {
32811
- const parsed = parse$4(value, format, new Date());
32812
- return isNaN(parsed.getDate()) ? null : parsed;
32813
- }
32814
- return (tryIso(input) ||
32815
- tryFormat(input, 'dd/MM/yyyy') ||
32816
- tryFormat(input, 'dd.MM.yyyy') ||
32817
- tryFormat(input, 'MM/dd/yyyy') ||
32818
- null);
32819
- }
32820
- function tryParseNumber(input) {
32821
- if (isNaN(input))
32822
- return null;
32823
- const parsed = parseFloat(input);
32824
- return isNaN(parsed) ? null : parsed;
32825
- }
32826
- function jsonToGeojsonFeature(object) {
32827
- const { id, properties } = Object.keys(object)
32828
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32829
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32830
- ? {
32831
- ...prev,
32832
- id: object[curr],
32833
- }
32834
- : {
32835
- ...prev,
32836
- properties: { ...prev.properties, [curr]: object[curr] },
32837
- }, { id: undefined, properties: {} });
32838
- return {
32839
- type: 'Feature',
32840
- geometry: null,
32841
- properties,
32842
- ...(id !== undefined && { id }),
32843
- };
32844
- }
32845
- function mutateProperties(items, mutators) {
32846
- const mutatorKeys = Object.keys(mutators);
32847
- for (let i = 0, ii = items.length; i < ii; i++) {
32848
- const item = items[i];
32849
- for (const mutatorField of mutatorKeys) {
32850
- if (!(mutatorField in item.properties))
32851
- continue;
32852
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32853
- }
32854
- }
32855
- return items;
32856
- }
32857
- const SAMPLE_SIZE = 20;
32858
- /**
32859
- * This will infer field types from a list of data items and cast the values accordingly
32860
- * @param items
32861
- * @param inferTypes
32862
- */
32863
- function processItemProperties(items, inferTypes = false) {
32864
- const foundFields = {};
32865
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32866
- const item = items[i];
32867
- const fields = Object.keys(item.properties);
32868
- for (const field of fields) {
32869
- if (!(field in foundFields)) {
32870
- foundFields[field] = {
32871
- label: field,
32872
- name: field,
32873
- type: null,
32874
- };
32875
- }
32876
- const value = item.properties[field];
32877
- const info = foundFields[field];
32878
- if (value === undefined || value === '' || value === null)
32879
- continue;
32880
- if (!inferTypes) {
32881
- if (info.type === null && typeof value === 'number') {
32882
- info.type = 'number';
32883
- }
32884
- else if (info.type === 'number' && typeof value !== 'number') {
32885
- info.type = 'string';
32886
- }
32887
- continue;
32888
- }
32889
- const parsedNumber = tryParseNumber(value);
32890
- if (info.type === null && parsedNumber !== null) {
32891
- info.type = 'number';
32892
- continue;
32893
- }
32894
- else if (info.type === 'number' && parsedNumber === null) {
32895
- info.type = 'string';
32896
- continue;
32897
- }
32898
- const parsedDate = tryParseDate(value);
32899
- if (info.type === null && parsedDate !== null) {
32900
- info.type = 'date';
32901
- }
32902
- else if (info.type === 'date' && parsedDate === null) {
32903
- info.type = 'string';
32904
- }
32905
- }
32906
- }
32907
- const properties = [];
32908
- const mutators = {};
32909
- for (const field in foundFields) {
32910
- const info = foundFields[field];
32911
- if (info.type === 'number') {
32912
- mutators[field] = tryParseNumber;
32913
- }
32914
- else if (info.type === 'date') {
32915
- mutators[field] = tryParseDate;
32916
- }
32917
- properties.push({ ...info, type: info.type || 'string' });
32918
- }
32919
- if (inferTypes) {
32920
- mutateProperties(items, mutators);
32921
- }
32922
- return { items, properties };
32923
- }
32924
- /**
32925
- * This creates a Proxy that allows reading and writing to the data item properties
32926
- * as if it was a simple array of JSON objects
32927
- * @param items
32928
- */
32929
- function getJsonDataItemsProxy(items) {
32930
- return new Proxy(items, {
32931
- get(target, p) {
32932
- if (typeof p === 'string' &&
32933
- !Number.isNaN(parseInt(p)) &&
32934
- target[p]?.properties) {
32935
- return target[p].properties;
32936
- }
32937
- return target[p];
32938
- },
32939
- set() {
32940
- throw new Error('This object is read-only');
32941
- },
32942
- });
33199
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33200
+ function fieldToSql(name) {
33201
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32943
33202
  }
32944
-
32945
33203
  function filterToSql(filter) {
32946
33204
  const operator = filter[0];
32947
33205
  const args = filter.slice(1);
@@ -32956,10 +33214,10 @@ function filterToSql(filter) {
32956
33214
  case '=':
32957
33215
  case '!=':
32958
33216
  case 'like':
32959
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33217
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32960
33218
  case 'in': {
32961
33219
  const values = args.slice(1);
32962
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33220
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32963
33221
  }
32964
33222
  case 'and':
32965
33223
  case 'or': {
@@ -32978,17 +33236,18 @@ function aggregationToSql(aggregation) {
32978
33236
  const field = aggregation[1];
32979
33237
  switch (operation) {
32980
33238
  case 'average':
32981
- return `AVG([${field}]) as [average(${field})]`;
33239
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
32982
33240
  case 'sum':
32983
33241
  case 'max':
32984
33242
  case 'min':
32985
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33243
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
32986
33244
  case 'count':
32987
- return 'COUNT(*) as [count()]';
33245
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
32988
33246
  }
32989
33247
  }
32990
33248
  /**
32991
33249
  * Leave arguments at null if not used
33250
+ * @param tableName
32992
33251
  * @param selected
32993
33252
  * @param filter
32994
33253
  * @param sort
@@ -32997,22 +33256,25 @@ function aggregationToSql(aggregation) {
32997
33256
  * @param groupBy
32998
33257
  * @param aggregations
32999
33258
  */
33000
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33259
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33001
33260
  let sqlSelect = 'SELECT *';
33002
- const sqlFrom = ' FROM ?';
33261
+ const sqlFrom = ` FROM ${tableName}`;
33003
33262
  let sqlOrderBy = '';
33004
33263
  let sqlWhere = '';
33005
33264
  let sqlLimit = '';
33006
33265
  let sqlGroupBy = '';
33007
33266
  if (selected !== null) {
33008
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33267
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33268
+ }
33269
+ if (geometryColumn !== null) {
33270
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33009
33271
  }
33010
33272
  if (filter !== null) {
33011
33273
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33012
33274
  }
33013
33275
  if (sort?.length) {
33014
33276
  sqlOrderBy = ` ORDER BY ${sort
33015
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33277
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33016
33278
  .join(', ')}`;
33017
33279
  }
33018
33280
  if (startIndex !== null && count !== null) {
@@ -33022,17 +33284,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33022
33284
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33023
33285
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33024
33286
  const sqlGroupByFields = groupedByDistinct
33025
- .map((group) => `[${group[1]}]`)
33287
+ .map((group) => fieldToSql(group[1]))
33026
33288
  .join(', ');
33027
33289
  const sqlGroupBySelect = groupedByDistinct
33028
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33290
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33029
33291
  .join(', ');
33030
33292
  if (sqlGroupByFields && sqlGroupBySelect) {
33031
33293
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33032
33294
  sqlSelect += `, ${sqlGroupBySelect}`;
33033
33295
  }
33034
33296
  }
33035
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33297
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33298
+ }
33299
+
33300
+ function arrowTableToDataItems(table) {
33301
+ const fields = table.schema.fields;
33302
+ return table.toArray().map((row) => {
33303
+ const rowJson = row.toJSON();
33304
+ const feature = {
33305
+ type: 'Feature',
33306
+ geometry: null,
33307
+ properties: {},
33308
+ };
33309
+ const keys = Object.keys(rowJson);
33310
+ for (let i = 0; i < keys.length; i++) {
33311
+ const key = keys[i];
33312
+ const dataType = fields[i].type;
33313
+ let value = rowJson[key];
33314
+ // this might happen if we get an array inside a field
33315
+ if (value instanceof Vector) {
33316
+ value = Array.from(value);
33317
+ }
33318
+ // cast bigints to ints
33319
+ if (typeof value === 'bigint') {
33320
+ value = Number(value);
33321
+ }
33322
+ // rename columns with empty name
33323
+ if (!key) {
33324
+ feature.properties['unknown'] = value;
33325
+ continue;
33326
+ }
33327
+ // assign properties that look like an id to the geojson `id` field
33328
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33329
+ (typeof value == 'string' || typeof value === 'number')) {
33330
+ feature.id = value;
33331
+ }
33332
+ // if a date is in timestamp (number) format, convert it to native
33333
+ if (typeof value === 'number' &&
33334
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33335
+ value = new Date(value);
33336
+ }
33337
+ // if a binary field (most likely a geometry column): skip
33338
+ if (DataType.isBinary(dataType)) {
33339
+ continue;
33340
+ }
33341
+ // geometry column
33342
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33343
+ feature.geometry = JSON.parse(value);
33344
+ continue;
33345
+ }
33346
+ feature.properties[key] = value;
33347
+ }
33348
+ return feature;
33349
+ });
33350
+ }
33351
+
33352
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33353
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33354
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33355
+ const typesMapping = {
33356
+ INTEGER: 'number',
33357
+ SMALLINT: 'number',
33358
+ TINYINT: 'number',
33359
+ BIGINT: 'number',
33360
+ HUGEINT: 'number',
33361
+ UINTEGER: 'number',
33362
+ USMALLINT: 'number',
33363
+ UTINYINT: 'number',
33364
+ UBIGINT: 'number',
33365
+ UHUGEINT: 'number',
33366
+ DOUBLE: 'number',
33367
+ BIGNUM: 'number',
33368
+ DECIMAL: 'number',
33369
+ NUMERIC: 'number',
33370
+ FLOAT: 'number',
33371
+ REAL: 'number',
33372
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33373
+ TIMESTAMP: 'date',
33374
+ DATE: 'date',
33375
+ CHAR: 'string',
33376
+ VARCHAR: 'string',
33377
+ TEXT: 'string',
33378
+ UUID: 'string',
33379
+ BLOB: 'string',
33380
+ BIT: 'string',
33381
+ INTERVAL: 'string',
33382
+ LIST: 'string',
33383
+ BOOLEAN: 'boolean',
33384
+ };
33385
+ class Engine {
33386
+ async makeInit() {
33387
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33388
+ let worker_url = bundle.mainWorker;
33389
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33390
+ if (worker_url.startsWith('https://')) {
33391
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33392
+ type: 'text/javascript',
33393
+ }));
33394
+ }
33395
+ const worker = new Worker(worker_url);
33396
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33397
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33398
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33399
+ // setup duckdb options
33400
+ const conn = await this.db.connect();
33401
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33402
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33403
+ await conn.query(`SET TimeZone = '${timezone}';`);
33404
+ await conn.query(`SET threads = 1;`);
33405
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33406
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33407
+ conn.close();
33408
+ return this;
33409
+ }
33410
+ isReady() {
33411
+ if (!this.init_) {
33412
+ this.init_ = this.makeInit();
33413
+ }
33414
+ return this.init_;
33415
+ }
33416
+ /**
33417
+ * Returns information about a dataset once it's loaded:
33418
+ * - a list of properties description
33419
+ * - the name of the dataset geometry column (null if no geometry present)
33420
+ * @param datasetId name of the table under which the dataset will be stored
33421
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33422
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33423
+ */
33424
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33425
+ const conn = await this.db.connect();
33426
+ let results;
33427
+ // either we want to recreate the table, or we keep it if it already exists
33428
+ const safeLoadQuery = forceReload
33429
+ ? `DROP TABLE IF EXISTS ${datasetId};
33430
+ ${loadQuery}`
33431
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33432
+ // create the table
33433
+ try {
33434
+ results = await conn.query(safeLoadQuery);
33435
+ }
33436
+ catch (e) {
33437
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33438
+ }
33439
+ // read rows count
33440
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33441
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33442
+ // read columns
33443
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33444
+ let geometryColumn = null;
33445
+ const properties = results
33446
+ .toArray()
33447
+ .map((row) => {
33448
+ const rowObj = row.toJSON();
33449
+ if (rowObj['data_type'] === 'GEOMETRY') {
33450
+ // the geometry is not part of the properties
33451
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33452
+ // all get discarded
33453
+ geometryColumn = rowObj['column_name'];
33454
+ return null;
33455
+ }
33456
+ return {
33457
+ name: rowObj['column_name'],
33458
+ label: rowObj['column_name'],
33459
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33460
+ };
33461
+ })
33462
+ .filter((prop) => prop !== null);
33463
+ conn.close();
33464
+ return {
33465
+ properties,
33466
+ geometryColumn,
33467
+ rowsCount: Number(recordsCount),
33468
+ };
33469
+ }
33470
+ // register a Uint8 buffer using a handle in the duckdb instance
33471
+ async registerData(name, buffer) {
33472
+ return this.db.registerFileBuffer(name, buffer);
33473
+ }
33474
+ /**
33475
+ * @param query duckdb-specific query for fetching items
33476
+ */
33477
+ async queryItems(query) {
33478
+ const conn = await this.db.connect();
33479
+ const results = await conn.query(query);
33480
+ conn.close();
33481
+ return arrowTableToDataItems(results);
33482
+ }
33483
+ close() {
33484
+ this.db?.terminate();
33485
+ }
33486
+ }
33487
+ let engine = null;
33488
+ async function getEngine() {
33489
+ if (!engine) {
33490
+ engine = new Engine();
33491
+ }
33492
+ return engine.isReady();
33036
33493
  }
33037
33494
 
33038
33495
  class BaseReader {
@@ -33045,10 +33502,18 @@ class BaseReader {
33045
33502
  this.sort = null;
33046
33503
  this.startIndex = null;
33047
33504
  this.count = null;
33505
+ this.loadPromise_ = Promise.resolve();
33506
+ this.cacheEnabled = false;
33507
+ }
33508
+ enableCache(enabled) {
33509
+ this.cacheEnabled = enabled;
33048
33510
  }
33049
33511
  load() {
33050
33512
  throw new Error('not implemented');
33051
33513
  }
33514
+ get isLoaded() {
33515
+ return this.loadPromise_;
33516
+ }
33052
33517
  get properties() {
33053
33518
  throw new Error('not implemented');
33054
33519
  }
@@ -33097,178 +33562,237 @@ class BaseReader {
33097
33562
  }
33098
33563
  }
33099
33564
 
33100
- class BaseCacheReader extends BaseReader {
33101
- constructor(url, cacheActive = true) {
33102
- super(url);
33103
- this.url = url;
33104
- this.cacheActive = cacheActive;
33105
- }
33106
- setCacheActive(value) {
33107
- this.cacheActive = value;
33108
- }
33109
- }
33110
-
33111
- class BaseFileReader extends BaseCacheReader {
33112
- getData() {
33565
+ /**
33566
+ * This reader handles file formats supported natively by DuckDB
33567
+ */
33568
+ class BaseFileReader extends BaseReader {
33569
+ // a table id should not exceed 63 chars
33570
+ generateDatasetId() {
33571
+ // generate a hash out of the url
33572
+ let hash = 0;
33573
+ for (const char of this.url) {
33574
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33575
+ hash = hash >>> 0; // make it unsigned
33576
+ }
33577
+ return `datafetcher_${hash.toString(16)}`;
33578
+ }
33579
+ async getLoadQuery() {
33113
33580
  throw new Error('not implemented');
33114
33581
  }
33115
- load() {
33116
- this.parseResult_ = this.getData();
33582
+ async load() {
33583
+ this.datasetId = this.generateDatasetId();
33584
+ this.loadPromise_ = getEngine()
33585
+ .then((engine) => {
33586
+ this.engine = engine;
33587
+ return this.getLoadQuery();
33588
+ })
33589
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33590
+ .then((datasetInfo) => {
33591
+ this.properties_ = datasetInfo.properties;
33592
+ this.geometryColumn = datasetInfo.geometryColumn;
33593
+ this.rowsCount = datasetInfo.rowsCount;
33594
+ // returns void for the loaded promise
33595
+ });
33117
33596
  }
33118
33597
  get properties() {
33119
- return this.parseResult_.then((result) => result.properties);
33598
+ return this.isLoaded.then(() => this.properties_);
33120
33599
  }
33121
33600
  get info() {
33122
- return this.parseResult_.then((result) => ({
33123
- itemsCount: result.items.length,
33601
+ return this.isLoaded.then(() => ({
33602
+ itemsCount: this.rowsCount,
33603
+ hasGeometry: !!this.geometryColumn,
33124
33604
  }));
33125
33605
  }
33126
33606
  async read() {
33127
- const items = (await this.parseResult_).items;
33128
- // no query defined: return the full results as is
33129
- if (this.groupedBy == null &&
33130
- this.aggregations == null &&
33131
- this.selected == null &&
33132
- this.sort == null &&
33133
- this.filter == null &&
33134
- this.startIndex == null &&
33135
- this.count == null) {
33136
- return items;
33137
- }
33138
- const jsonItems = getJsonDataItemsProxy(items);
33139
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33140
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33141
- return result.map(jsonToGeojsonFeature);
33142
- }
33143
- }
33144
-
33145
- function parseCsv(text) {
33146
- // first parse the header to guess the delimiter
33147
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33148
- let delimiter;
33149
- try {
33150
- const header = text.split('\n')[0];
33151
- const result = Papa.parse(header, {
33152
- header: false,
33153
- });
33154
- delimiter = result.meta.delimiter;
33155
- }
33156
- catch (e) {
33157
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33158
- }
33159
- const parsed = Papa.parse(text, {
33160
- header: true,
33161
- skipEmptyLines: true,
33162
- delimiter,
33163
- });
33164
- if (parsed.errors.length) {
33165
- throw new Error('CSV parsing failed for the following reasons:\n' +
33166
- parsed.errors
33167
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33168
- .join('\n'));
33607
+ await this.isLoaded;
33608
+ // if only certain fields are selected, omit the geometry
33609
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33610
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33611
+ return this.engine.queryItems(query);
33169
33612
  }
33170
- const items = parsed.data.map(jsonToGeojsonFeature);
33171
- return processItemProperties(items, true);
33172
33613
  }
33614
+
33173
33615
  class CsvReader extends BaseFileReader {
33174
- getData() {
33175
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33616
+ async getLoadQuery() {
33617
+ // first we get a list of columns hich have a detected type of VARCHAR
33618
+ // then we make sure that for those columns, we don't cast an empty string to null; instead we want to keep the empty string
33619
+ return `
33620
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33621
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33622
+ force_not_null = getvariable('strColumns'),
33623
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33624
+ );`;
33176
33625
  }
33177
33626
  }
33178
33627
 
33179
- /**
33180
- * This parser only supports arrays of simple flat objects with properties
33181
- * @param text
33182
- */
33183
- function parseJson(text) {
33184
- const parsed = JSON.parse(text);
33185
- if (!Array.isArray(parsed)) {
33186
- throw new Error('Could not parse JSON, expected an array at root level');
33187
- }
33188
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33189
- }
33190
33628
  class JsonReader extends BaseFileReader {
33191
- getData() {
33192
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33629
+ async getLoadQuery() {
33630
+ return `
33631
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33632
+ maximum_object_size = 536870912 -- 500MB
33633
+ );`;
33193
33634
  }
33194
33635
  }
33195
33636
 
33196
- /**
33197
- * This parser supports both Geojson Feature collections or arrays
33198
- * of Features
33199
- * @param text
33200
- */
33201
- function parseGeojson(text) {
33202
- const parsed = JSON.parse(text);
33203
- const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33204
- if (!Array.isArray(features)) {
33205
- throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33206
- }
33207
- return processItemProperties(features);
33208
- }
33209
33637
  class GeojsonReader extends BaseFileReader {
33210
- getData() {
33211
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33638
+ async getLoadQuery() {
33639
+ return `
33640
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33641
+ allowed_drivers = ['GeoJSON']
33642
+ );`;
33212
33643
  }
33213
33644
  }
33214
33645
 
33646
+ async function inferDatasetType(url, typeHint) {
33647
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33648
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33649
+ ? fileExtensionMatches[1].toLowerCase()
33650
+ : null;
33651
+ // 1. type hint
33652
+ if (typeHint)
33653
+ return Promise.resolve(typeHint);
33654
+ // 2. content-type header
33655
+ const headers = await fetchHeaders(url);
33656
+ if ('supportedType' in headers)
33657
+ return headers.supportedType;
33658
+ // 3. file extension from url
33659
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33660
+ return fileExtension;
33661
+ // no type inferred or hinted
33662
+ if ('mimeType' in headers)
33663
+ throw FetchError.unsupportedType(headers.mimeType);
33664
+ else
33665
+ throw FetchError.unknownType();
33666
+ }
33667
+ function fetchHeaders(url) {
33668
+ return sharedFetch(url, 'HEAD')
33669
+ .catch((error) => {
33670
+ throw FetchError.corsOrNetwork(error.message);
33671
+ })
33672
+ .then((response) => {
33673
+ if (!response.ok) {
33674
+ throw FetchError.http(response.status);
33675
+ }
33676
+ return parseHeaders(response.headers);
33677
+ });
33678
+ }
33679
+ function fetchDataAsText(url, cacheActive) {
33680
+ const fetchFactory = () => sharedFetch(url)
33681
+ .catch((error) => {
33682
+ throw FetchError.corsOrNetwork(error.message);
33683
+ })
33684
+ .then(async (response) => {
33685
+ if (!response.ok) {
33686
+ const clonedResponse = response.clone();
33687
+ throw FetchError.http(response.status, await clonedResponse.text());
33688
+ }
33689
+ const clonedResponse = response.clone();
33690
+ return clonedResponse.text();
33691
+ });
33692
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33693
+ }
33694
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33695
+ const fetchFactory = () => sharedFetch(url)
33696
+ .catch((error) => {
33697
+ throw FetchError.corsOrNetwork(error.message);
33698
+ })
33699
+ .then(async (response) => {
33700
+ if (!response.ok) {
33701
+ throw FetchError.http(response.status, await response.text());
33702
+ }
33703
+ // convert to a numeric array so that we can store the response in cache
33704
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33705
+ });
33706
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33707
+ return new Uint8Array(array).buffer;
33708
+ });
33709
+ }
33215
33710
  /**
33216
- * This will read the first sheet of the excel workbook and expect the first
33217
- * line to contain the properties names
33218
- * @param buffer
33711
+ * This creates a Proxy that allows reading and writing to the data item properties
33712
+ * as if it was a simple array of JSON objects
33713
+ * @param items
33219
33714
  */
33220
- function parseExcel(buffer) {
33221
- return import('xlsx').then(({ read, utils }) => {
33222
- const workbook = read(buffer);
33223
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33224
- let json = utils.sheet_to_json(sheet);
33225
- if (!json.length) {
33226
- json = [];
33227
- }
33228
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33715
+ function getJsonDataItemsProxy(items) {
33716
+ return new Proxy(items, {
33717
+ get(target, p) {
33718
+ if (typeof p === 'string' &&
33719
+ !Number.isNaN(parseInt(p)) &&
33720
+ target[p]?.properties) {
33721
+ return target[p].properties;
33722
+ }
33723
+ return target[p];
33724
+ },
33725
+ set() {
33726
+ throw new Error('This object is read-only');
33727
+ },
33229
33728
  });
33230
33729
  }
33730
+
33231
33731
  class ExcelReader extends BaseFileReader {
33232
- getData() {
33233
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33732
+ async getLoadQuery() {
33733
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33734
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33735
+ const bufferHandle = `B${this.datasetId}`;
33736
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33737
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33738
+ const isXls = Array.from(magicNumber)
33739
+ .map((n) => n.toString(16).toUpperCase())
33740
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33741
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33742
+ if (isXls) {
33743
+ buffer = await import('xlsx').then(({ read, utils }) => {
33744
+ const workbook = read(buffer);
33745
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33746
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33747
+ });
33748
+ }
33749
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33750
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33751
+ return `
33752
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33234
33753
  }
33235
33754
  }
33236
33755
 
33237
- function parseGml(text, namespace, version) {
33238
- const splittedNamespace = namespace.split(':');
33239
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33756
+ class GmlReader extends BaseFileReader {
33757
+ async getLoadQuery() {
33758
+ return `
33759
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33760
+ allowed_drivers = ['GML']
33761
+ );`;
33762
+ }
33763
+ }
33764
+
33765
+ const formatGeojson = new GeoJSON$1();
33766
+ function parseGeojson(text) {
33767
+ const parsed = JSON.parse(text);
33768
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33769
+ if (!Array.isArray(features)) {
33770
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33771
+ }
33772
+ return features;
33773
+ }
33774
+ function parseGml(text, featureType, version) {
33775
+ const parts = featureType.split(':');
33776
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33240
33777
  const match = regex.exec(text);
33241
33778
  if (match && match.length >= 2) {
33242
- const wf = new WFS({
33779
+ const wfs = new WFS({
33243
33780
  featureNS: match[1],
33244
- featureType: splittedNamespace[1],
33781
+ featureType: parts[1],
33245
33782
  version: version,
33246
33783
  });
33247
33784
  let features;
33248
33785
  try {
33249
- features = wf.readFeatures(text);
33786
+ features = wfs.readFeatures(text);
33250
33787
  }
33251
33788
  catch (e) {
33252
- throw Error("Couldn't parse WFS with GML features");
33789
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33253
33790
  }
33254
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33255
- return processItemProperties(geojsonItem.features, true);
33791
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33792
+ return geojsonItem.features;
33256
33793
  }
33257
33794
  throw Error("Couldn't retrieve namespace url");
33258
33795
  }
33259
- class GmlReader extends BaseFileReader {
33260
- constructor(url, namespace, version, cacheActive = true) {
33261
- super(url);
33262
- this.url = url;
33263
- this.namespace = namespace;
33264
- this.version = version;
33265
- this.cacheActive = cacheActive;
33266
- }
33267
- getData() {
33268
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33269
- }
33270
- }
33271
-
33272
33796
  async function getWfsEndpoint(wfsUrl) {
33273
33797
  try {
33274
33798
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33296,21 +33820,65 @@ async function getWfsEndpoint(wfsUrl) {
33296
33820
  }
33297
33821
  }
33298
33822
  }
33299
- class WfsReader extends BaseCacheReader {
33300
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33301
- super(url, cacheActive);
33302
- this.endpoint = wfsEndpoint;
33303
- this.featureTypeName = featureTypeName;
33304
- this.version = this.endpoint.getVersion();
33823
+ class WfsReader extends BaseReader {
33824
+ constructor(url, featureTypeName) {
33825
+ super(url);
33826
+ this.endpoint = getWfsEndpoint(url);
33827
+ this.featureType = this.endpoint
33828
+ .then((endpoint) => {
33829
+ const featureTypes = endpoint.getFeatureTypes();
33830
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33831
+ ? featureTypes[0].name
33832
+ : featureTypeName);
33833
+ })
33834
+ .then((featureType) => {
33835
+ if (!featureType) {
33836
+ throw new Error('wfs.featuretype.notfound');
33837
+ }
33838
+ return featureType;
33839
+ });
33840
+ }
33841
+ get backupReader() {
33842
+ if (this.backupReader_) {
33843
+ return this.backupReader_;
33844
+ }
33845
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33846
+ let reader;
33847
+ if (endpoint.supportsJson(featureType.name)) {
33848
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33849
+ asJson: true,
33850
+ outputCrs: 'EPSG:4326',
33851
+ }));
33852
+ }
33853
+ else {
33854
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33855
+ (featureType.defaultCrs === 'EPSG:4326' ||
33856
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33857
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33858
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33859
+ outputCrs: 'EPSG:4326',
33860
+ }));
33861
+ }
33862
+ }
33863
+ reader.enableCache(this.cacheEnabled);
33864
+ reader.load();
33865
+ return reader;
33866
+ });
33867
+ return this.backupReader_;
33305
33868
  }
33306
33869
  get properties() {
33307
- return this.endpoint
33308
- .getFeatureTypeFull(this.featureTypeName)
33309
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33870
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33310
33871
  const originalType = featureType.properties[prop];
33311
- const type = originalType === 'float' || originalType === 'integer'
33312
- ? 'number'
33313
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33872
+ let type;
33873
+ if (originalType === 'float' || originalType === 'integer') {
33874
+ type = 'number';
33875
+ }
33876
+ else if (originalType === 'boolean') {
33877
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33878
+ }
33879
+ else {
33880
+ type = originalType;
33881
+ }
33314
33882
  return {
33315
33883
  name: prop,
33316
33884
  label: prop,
@@ -33319,83 +33887,63 @@ class WfsReader extends BaseCacheReader {
33319
33887
  }));
33320
33888
  }
33321
33889
  get info() {
33322
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33890
+ return this.featureType.then((result) => ({
33323
33891
  itemsCount: result.objectCount,
33892
+ hasGeometry: !!result.geometryName,
33324
33893
  }));
33325
33894
  }
33326
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33327
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33328
- const featureTypes = wfsEndpoint.getFeatureTypes();
33329
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33330
- ? featureTypes[0].name
33331
- : featureTypeName);
33332
- if (!featureType) {
33333
- throw new Error('wfs.featuretype.notfound');
33334
- }
33335
- if (wfsEndpoint.supportsStartIndex()) {
33336
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33337
- }
33338
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33339
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33340
- asJson: true,
33341
- outputCrs: 'EPSG:4326',
33342
- }));
33343
- }
33344
- else {
33345
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33346
- (featureType.defaultCrs === 'EPSG:4326' ||
33347
- featureType.otherCrs?.includes('EPSG:4326'))) {
33348
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33349
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33350
- outputCrs: 'EPSG:4326',
33351
- }), featureType.name, wfsEndpoint.getVersion());
33352
- }
33353
- throw new Error('wfs.geojsongml.notsupported');
33354
- }
33895
+ load() {
33896
+ // Nothing to load for Wfs
33355
33897
  }
33356
- async getData(aggregation, groupedBy) {
33357
- if (aggregation || groupedBy) {
33358
- return { items: await this.getQueryData() };
33898
+ async read() {
33899
+ const endpoint = await this.endpoint;
33900
+ const featureType = await this.featureType;
33901
+ // if we can't use the WFS protocol we fall back to the backup reader
33902
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33903
+ const backupReader = await this.backupReader;
33904
+ backupReader.selectAll();
33905
+ if (this.selected) {
33906
+ backupReader.select(...this.selected);
33907
+ }
33908
+ if (this.aggregations) {
33909
+ backupReader.aggregate(...this.aggregations);
33910
+ }
33911
+ if (this.groupedBy) {
33912
+ backupReader.groupBy(...this.groupedBy);
33913
+ }
33914
+ if (this.sort) {
33915
+ backupReader.orderBy(...this.sort);
33916
+ }
33917
+ if (this.startIndex !== null && this.count !== null) {
33918
+ backupReader.limit(this.startIndex, this.count);
33919
+ }
33920
+ return backupReader.read();
33359
33921
  }
33360
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33922
+ const asJson = endpoint.supportsJson(featureType.name);
33361
33923
  const attributes = this.selected ?? undefined;
33362
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33924
+ let sortBy = null;
33925
+ if (this.sort) {
33926
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33927
+ sortBy = Array.isArray(this.sort[0])
33928
+ ? this.sort.map(mapSort)
33929
+ : mapSort(this.sort);
33930
+ }
33931
+ const url = endpoint.getFeatureUrl(featureType.name, {
33363
33932
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33364
33933
  ...(this.count !== null && { maxFeatures: this.count }),
33365
33934
  asJson,
33366
33935
  outputCrs: 'EPSG:4326',
33367
33936
  attributes,
33368
- // sortBy: this.sort // TODO: no sort in ogc-client?
33937
+ sortBy,
33369
33938
  });
33370
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33371
- const finalUrl = new URL(url);
33372
- const sorts = this.sort
33373
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33374
- .join(',');
33375
- // Direct update on string url to prevent encoding of +A and +D
33376
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33377
- }
33378
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33939
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33379
33940
  ? parseGeojson(text)
33380
- : parseGml(text, this.featureTypeName, this.version));
33381
- }
33382
- async getQueryData() {
33383
- const items = (await this.getData()).items;
33384
- const jsonItems = getJsonDataItemsProxy(items);
33385
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33386
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33387
- return result.map(jsonToGeojsonFeature);
33388
- }
33389
- load() {
33390
- // Nothing to load for Wfs
33391
- }
33392
- async read() {
33393
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33941
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33394
33942
  }
33395
33943
  }
33396
33944
 
33397
- async function openDataset(url, typeHint, options, cacheActive) {
33398
- const fileType = await inferDatasetType(url, typeHint);
33945
+ async function openDataset(url, options) {
33946
+ const fileType = await inferDatasetType(url, options?.typeHint);
33399
33947
  let reader;
33400
33948
  try {
33401
33949
  switch (fileType) {
@@ -33412,18 +33960,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33412
33960
  reader = new ExcelReader(url);
33413
33961
  break;
33414
33962
  case 'gml':
33415
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33963
+ reader = new GmlReader(url);
33416
33964
  break;
33417
33965
  case 'wfs':
33418
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33966
+ reader = new WfsReader(url, options?.wfsFeatureType);
33419
33967
  break;
33420
33968
  }
33421
- reader.setCacheActive(cacheActive);
33969
+ reader.enableCache(options?.enableCache ?? true);
33422
33970
  reader.load();
33423
33971
  return reader;
33424
33972
  }
33425
33973
  catch (e) {
33426
- //WfsReader may already raise a FetchError
33974
+ // WfsReader may already raise a FetchError
33427
33975
  if (e instanceof FetchError)
33428
33976
  throw e;
33429
33977
  else
@@ -33434,13 +33982,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33434
33982
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33435
33983
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33436
33984
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33437
- * File type is determined liked so:
33985
+ * File type is determined like so:
33438
33986
  * 1. if a type hint is given, use it
33439
33987
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33440
33988
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33441
33989
  */
33442
- async function readDataset(url, typeHint, options, cacheActive = true) {
33443
- const reader = await openDataset(url, typeHint, options, cacheActive);
33990
+ async function readDataset(url, options) {
33991
+ const reader = await openDataset(url, options);
33444
33992
  try {
33445
33993
  return await reader.read();
33446
33994
  }
@@ -33671,9 +34219,11 @@ class DataService {
33671
34219
  getDataset(link, cacheActive) {
33672
34220
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33673
34221
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33674
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34222
+ return from(openDataset(wfsUrlEndpoint, {
34223
+ typeHint: 'wfs',
33675
34224
  wfsFeatureType: link.name,
33676
- }, cacheActive));
34225
+ enableCache: cacheActive,
34226
+ }));
33677
34227
  }
33678
34228
  else if (link.type === 'download') {
33679
34229
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33681,12 +34231,15 @@ class DataService {
33681
34231
  const supportedType = SupportedTypes.indexOf(format) > -1
33682
34232
  ? format
33683
34233
  : undefined;
33684
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34234
+ return from(openDataset(linkProxifiedUrl, {
34235
+ typeHint: supportedType,
34236
+ enableCache: cacheActive,
34237
+ })).pipe();
33685
34238
  }
33686
34239
  else if (link.type === 'service' &&
33687
34240
  link.accessServiceProtocol === 'esriRest') {
33688
34241
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33689
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34242
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33690
34243
  }
33691
34244
  else if (link.type === 'service' &&
33692
34245
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33700,7 +34253,10 @@ class DataService {
33700
34253
  }
33701
34254
  const urlWithoutLimit = new URL(geojsonUrl);
33702
34255
  urlWithoutLimit.searchParams.delete('limit');
33703
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34256
+ return openDataset(urlWithoutLimit.toString(), {
34257
+ typeHint: 'geojson',
34258
+ enableCache: cacheActive,
34259
+ });
33704
34260
  }));
33705
34261
  }
33706
34262
  return throwError(() => 'protocol not supported');
@@ -33952,20 +34508,31 @@ class DataTableComponent {
33952
34508
  this.eltRef = inject(ElementRef);
33953
34509
  this.cdr = inject(ChangeDetectorRef);
33954
34510
  this.translateService = inject(TranslateService);
33955
- this._featureAttributes = [];
34511
+ this.columnsFromFeatureCatalog = null;
34512
+ this.columnsFromDataset = [];
33956
34513
  this.selected = new EventEmitter();
33957
- this.properties$ = new BehaviorSubject(null);
33958
34514
  this.loading$ = new BehaviorSubject(false);
33959
34515
  this.error = null;
33960
34516
  }
33961
34517
  set featureAttributes(value) {
33962
- this._featureAttributes = value;
33963
- this.properties$.next(value.map((attr) => attr.value));
34518
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34519
+ name: attrs.value,
34520
+ label: attrs.label,
34521
+ }));
33964
34522
  }
33965
34523
  set dataset(value) {
33966
34524
  this.dataset_ = value;
33967
34525
  this.dataset_.load();
33968
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34526
+ this.dataset_.info.then((info) => {
34527
+ this.count = info.itemsCount;
34528
+ this.cdr.detectChanges();
34529
+ });
34530
+ }
34531
+ get columns() {
34532
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34533
+ }
34534
+ get columnNames() {
34535
+ return this.columns.map((c) => c.name);
33969
34536
  }
33970
34537
  ngOnInit() {
33971
34538
  this.dataSource = new DataTableDataSource();
@@ -34000,11 +34567,12 @@ class DataTableComponent {
34000
34567
  }
34001
34568
  async readData() {
34002
34569
  this.loading$.next(true);
34003
- // wait for properties to be read
34004
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34005
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34006
- this.dataset_.select(...propsWithoutGeom);
34007
34570
  try {
34571
+ // wait for properties to be read
34572
+ if (!this.columnsFromFeatureCatalog) {
34573
+ this.columnsFromDataset = await this.dataset_.properties;
34574
+ }
34575
+ this.dataset_.select(...this.columnNames);
34008
34576
  await this.dataSource.showData(this.dataset_.read());
34009
34577
  this.error = null;
34010
34578
  }
@@ -34034,7 +34602,7 @@ class DataTableComponent {
34034
34602
  }
34035
34603
  }
34036
34604
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34037
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34605
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34038
34606
  }
34039
34607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34040
34608
  type: Component,
@@ -34046,10 +34614,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34046
34614
  CommonModule,
34047
34615
  LoadingMaskComponent,
34048
34616
  PopupAlertComponent,
34049
- LetDirective,
34050
34617
  TranslatePipe,
34051
34618
  TranslateDirective,
34052
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34619
+ ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34053
34620
  }], propDecorators: { featureAttributes: [{
34054
34621
  type: Input
34055
34622
  }], dataset: [{
@@ -34152,7 +34719,7 @@ class ChartViewComponent {
34152
34719
  }), shareReplay$1(1));
34153
34720
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34154
34721
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34155
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34722
+ .filter((prop) => prop.type === 'number')
34156
34723
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34157
34724
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34158
34725
  const newProp = choices[0]?.value || '';
@@ -34179,6 +34746,7 @@ class ChartViewComponent {
34179
34746
  this.aggregation$,
34180
34747
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34181
34748
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34749
+ this.loading = true;
34182
34750
  return dataset
34183
34751
  .groupBy(['distinct', xProp])
34184
34752
  .aggregate(fieldAgg)
@@ -34420,6 +34988,7 @@ class GeoTableViewComponent {
34420
34988
  }));
34421
34989
  }
34422
34990
  async initMapContext() {
34991
+ this.dataset.load();
34423
34992
  this.dataset.selectAll();
34424
34993
  return {
34425
34994
  layers: [
@@ -34593,8 +35162,7 @@ class MdViewFacade {
34593
35162
  }));
34594
35163
  this.error$ = this.store.pipe(select(getMetadataError));
34595
35164
  this.related$ = this.store.pipe(select(getRelated));
34596
- this.sources$ = this.store.pipe(select(getSources));
34597
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35165
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34598
35166
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34599
35167
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34600
35168
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34713,12 +35281,9 @@ class MdViewEffects {
34713
35281
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34714
35282
  return setRelated({ related });
34715
35283
  }), catchError(() => of(setRelated({ related: null })))));
34716
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34717
- return setSources({ sources });
34718
- }), catchError(() => of(setSources({ sources: null })))));
34719
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34720
- return setSourceOf({ sourceOf });
34721
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
35284
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35285
+ return setLinkedRecords({ linkedRecords });
35286
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34722
35287
  /*
34723
35288
  UserFeedback effects
34724
35289
  */
@@ -36452,6 +37017,11 @@ const DEFAULT_CONFIGURATION = {
36452
37017
  },
36453
37018
  hidden: '${record.kind == "service"}',
36454
37019
  },
37020
+ {
37021
+ model: 'associatedRecords',
37022
+ formFieldConfig: {},
37023
+ hidden: '${record.kind == "service"}',
37024
+ },
36455
37025
  ],
36456
37026
  },
36457
37027
  // Section: Geographical coverage
@@ -37589,7 +38159,7 @@ class FormFieldTemporalExtentsComponent {
37589
38159
  provideNgIconsConfig({
37590
38160
  size: '1.5rem',
37591
38161
  }),
37592
- ], ngImport: i0, template: "<div class=\"flex gap-2 mb-2\">\n @for (addOption of addOptions$ | async; track addOption) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"onAdd(addOption.eventName)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon> &nbsp;{{\n addOption.buttonLabel\n }}</gn-ui-button\n >\n }\n</div>\n<gn-ui-sortable-list\n [items]=\"extents\"\n (itemsOrderChange)=\"onItemsOrderChange($event)\"\n [elementTemplate]=\"template\"\n></gn-ui-sortable-list>\n<ng-template #template let-extent let-index=\"index\">\n @if (extent.end === undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.date</p>\n <gn-ui-date-picker\n [date]=\"extent.start\"\n (dateChange)=\"onExtentChange({ start: $event }, index)\"\n ></gn-ui-date-picker>\n </div>\n }\n @if (extent.end !== undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.range</p>\n <gn-ui-date-range-picker\n [startDate]=\"extent.start\"\n [endDate]=\"extent.end\"\n (startDateChange)=\"onExtentChange({ start: $event }, index)\"\n (endDateChange)=\"onExtentChange({ end: $event }, index)\"\n ></gn-ui-date-range-picker>\n </div>\n }\n</ng-template>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: DatePickerComponent, selector: "gn-ui-date-picker", inputs: ["date"], outputs: ["dateChange"] }, { kind: "component", type: DateRangePickerComponent, selector: "gn-ui-date-range-picker", inputs: ["startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { 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 }); }
38162
+ ], ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <div class=\"flex gap-2\">\n @for (addOption of addOptions$ | async; track addOption) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"onAdd(addOption.eventName)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon> &nbsp;{{\n addOption.buttonLabel\n }}</gn-ui-button\n >\n }\n </div>\n @if (extents.length) {\n <gn-ui-sortable-list\n [items]=\"extents\"\n (itemsOrderChange)=\"onItemsOrderChange($event)\"\n [elementTemplate]=\"template\"\n ></gn-ui-sortable-list>\n }\n</div>\n<ng-template #template let-extent let-index=\"index\">\n @if (extent.end === undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.date</p>\n <gn-ui-date-picker\n [date]=\"extent.start\"\n (dateChange)=\"onExtentChange({ start: $event }, index)\"\n ></gn-ui-date-picker>\n </div>\n }\n @if (extent.end !== undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.range</p>\n <gn-ui-date-range-picker\n [startDate]=\"extent.start\"\n [endDate]=\"extent.end\"\n (startDateChange)=\"onExtentChange({ start: $event }, index)\"\n (endDateChange)=\"onExtentChange({ end: $event }, index)\"\n ></gn-ui-date-range-picker>\n </div>\n }\n</ng-template>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: DatePickerComponent, selector: "gn-ui-date-picker", inputs: ["date"], outputs: ["dateChange"] }, { kind: "component", type: DateRangePickerComponent, selector: "gn-ui-date-range-picker", inputs: ["startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { 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 }); }
37593
38163
  }
37594
38164
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldTemporalExtentsComponent, decorators: [{
37595
38165
  type: Component,
@@ -37606,7 +38176,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37606
38176
  provideNgIconsConfig({
37607
38177
  size: '1.5rem',
37608
38178
  }),
37609
- ], template: "<div class=\"flex gap-2 mb-2\">\n @for (addOption of addOptions$ | async; track addOption) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"onAdd(addOption.eventName)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon> &nbsp;{{\n addOption.buttonLabel\n }}</gn-ui-button\n >\n }\n</div>\n<gn-ui-sortable-list\n [items]=\"extents\"\n (itemsOrderChange)=\"onItemsOrderChange($event)\"\n [elementTemplate]=\"template\"\n></gn-ui-sortable-list>\n<ng-template #template let-extent let-index=\"index\">\n @if (extent.end === undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.date</p>\n <gn-ui-date-picker\n [date]=\"extent.start\"\n (dateChange)=\"onExtentChange({ start: $event }, index)\"\n ></gn-ui-date-picker>\n </div>\n }\n @if (extent.end !== undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.range</p>\n <gn-ui-date-range-picker\n [startDate]=\"extent.start\"\n [endDate]=\"extent.end\"\n (startDateChange)=\"onExtentChange({ start: $event }, index)\"\n (endDateChange)=\"onExtentChange({ end: $event }, index)\"\n ></gn-ui-date-range-picker>\n </div>\n }\n</ng-template>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"] }]
38179
+ ], template: "<div class=\"flex flex-col gap-2\">\n <div class=\"flex gap-2\">\n @for (addOption of addOptions$ | async; track addOption) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"onAdd(addOption.eventName)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon> &nbsp;{{\n addOption.buttonLabel\n }}</gn-ui-button\n >\n }\n </div>\n @if (extents.length) {\n <gn-ui-sortable-list\n [items]=\"extents\"\n (itemsOrderChange)=\"onItemsOrderChange($event)\"\n [elementTemplate]=\"template\"\n ></gn-ui-sortable-list>\n }\n</div>\n<ng-template #template let-extent let-index=\"index\">\n @if (extent.end === undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.date</p>\n <gn-ui-date-picker\n [date]=\"extent.start\"\n (dateChange)=\"onExtentChange({ start: $event }, index)\"\n ></gn-ui-date-picker>\n </div>\n }\n @if (extent.end !== undefined) {\n <div class=\"border rounded-lg px-4 pb-4\">\n <p class=\"my-2\" translate>editor.record.form.temporalExtents.range</p>\n <gn-ui-date-range-picker\n [startDate]=\"extent.start\"\n [endDate]=\"extent.end\"\n (startDateChange)=\"onExtentChange({ start: $event }, index)\"\n (endDateChange)=\"onExtentChange({ end: $event }, index)\"\n ></gn-ui-date-range-picker>\n </div>\n }\n</ng-template>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"] }]
37610
38180
  }], propDecorators: { value: [{
37611
38181
  type: Input
37612
38182
  }], valueChange: [{
@@ -37822,6 +38392,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37822
38392
  ], template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-generic-keywords\n [placeholder]=\"'editor.record.form.keywords.place.placeholder' | translate\"\n [keywords]=\"shownKeywords$ | async\"\n [keywordTypes]=\"['place']\"\n (deletedKeyword)=\"handleKeywordDelete($event)\"\n (addedKeyword)=\"handleKeywordAdd($event)\"\n >\n </gn-ui-generic-keywords>\n <div class=\"w-full h-96 mt-2\">\n <gn-ui-spatial-extent\n [spatialExtents]=\"spatialExtents$ | async\"\n ></gn-ui-spatial-extent>\n </div>\n</div>\n" }]
37823
38393
  }] });
37824
38394
 
38395
+ class FormFieldAssociatedRecordsComponent {
38396
+ constructor() {
38397
+ this.valueChange = new EventEmitter();
38398
+ }
38399
+ get list() {
38400
+ return this.value ?? [];
38401
+ }
38402
+ get first() {
38403
+ return this.list[0];
38404
+ }
38405
+ get rest() {
38406
+ return this.list.slice(1);
38407
+ }
38408
+ get hasParentLink() {
38409
+ return this.list.length > 0;
38410
+ }
38411
+ get uniqueIdentifier() {
38412
+ return this.first?.uniqueIdentifier ?? '';
38413
+ }
38414
+ get selectedType() {
38415
+ return this.first?.associationType;
38416
+ }
38417
+ get choices() {
38418
+ return associationTypeValues.map((value) => ({
38419
+ value,
38420
+ label: `domain.record.associationType.${value}`,
38421
+ }));
38422
+ }
38423
+ onToggle(checked) {
38424
+ this.valueChange.emit(checked
38425
+ ? [
38426
+ {
38427
+ uniqueIdentifier: '',
38428
+ associationType: associationTypeValues[0],
38429
+ },
38430
+ ...this.rest,
38431
+ ]
38432
+ : this.rest);
38433
+ }
38434
+ onUniqueIdentifierChange(uniqueIdentifier) {
38435
+ this.valueChange.emit(uniqueIdentifier
38436
+ ? [{ ...this.first, uniqueIdentifier }, ...this.rest]
38437
+ : this.rest);
38438
+ }
38439
+ onTypeChange(associationType) {
38440
+ this.valueChange.emit([
38441
+ { ...this.first, associationType: associationType },
38442
+ ...this.rest,
38443
+ ]);
38444
+ }
38445
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38446
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldAssociatedRecordsComponent, isStandalone: true, selector: "gn-ui-form-field-associated-records", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <gn-ui-check-toggle\n [label]=\"'editor.record.form.field.parentLink' | translate\"\n [value]=\"hasParentLink\"\n (toggled)=\"onToggle($event)\"\n data-cy=\"associated-record-toggle\"\n ></gn-ui-check-toggle>\n @if (hasParentLink) {\n <gn-ui-form-field-wrapper\n [label]=\"'editor.record.form.field.parentIdentifier' | translate\"\n >\n <gn-ui-text-input\n [value]=\"uniqueIdentifier\"\n (valueChange)=\"onUniqueIdentifierChange($event)\"\n placeholder=\"\"\n data-cy=\"associated-record-identifier\"\n ></gn-ui-text-input>\n </gn-ui-form-field-wrapper>\n <gn-ui-form-field-wrapper\n [label]=\"'editor.record.form.field.parentType' | translate\"\n >\n <gn-ui-dropdown-selector\n [title]=\"'editor.record.form.field.parentType' | translate\"\n [showTitle]=\"false\"\n [choices]=\"choices\"\n [selected]=\"selectedType\"\n (selectValue)=\"onTypeChange($event)\"\n [extraBtnClass]=\"'input-as-button gn-ui-text-input'\"\n data-cy=\"associated-record-type\"\n ></gn-ui-dropdown-selector>\n </gn-ui-form-field-wrapper>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: CheckToggleComponent, selector: "gn-ui-check-toggle", inputs: ["title", "label", "value", "color"], outputs: ["toggled"] }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "component", type: DropdownSelectorComponent, selector: "gn-ui-dropdown-selector", inputs: ["title", "showTitle", "ariaName", "choices", "selected", "maxRows", "extraBtnClass", "minWidth", "disabled"], outputs: ["selectValue"] }, { kind: "component", type: FormFieldWrapperComponent, selector: "gn-ui-form-field-wrapper", inputs: ["label", "hint"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38447
+ }
38448
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, decorators: [{
38449
+ type: Component,
38450
+ args: [{ selector: 'gn-ui-form-field-associated-records', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
38451
+ CheckToggleComponent,
38452
+ TextInputComponent,
38453
+ DropdownSelectorComponent,
38454
+ FormFieldWrapperComponent,
38455
+ TranslatePipe,
38456
+ ], template: "<div class=\"flex flex-col gap-2\">\n <gn-ui-check-toggle\n [label]=\"'editor.record.form.field.parentLink' | translate\"\n [value]=\"hasParentLink\"\n (toggled)=\"onToggle($event)\"\n data-cy=\"associated-record-toggle\"\n ></gn-ui-check-toggle>\n @if (hasParentLink) {\n <gn-ui-form-field-wrapper\n [label]=\"'editor.record.form.field.parentIdentifier' | translate\"\n >\n <gn-ui-text-input\n [value]=\"uniqueIdentifier\"\n (valueChange)=\"onUniqueIdentifierChange($event)\"\n placeholder=\"\"\n data-cy=\"associated-record-identifier\"\n ></gn-ui-text-input>\n </gn-ui-form-field-wrapper>\n <gn-ui-form-field-wrapper\n [label]=\"'editor.record.form.field.parentType' | translate\"\n >\n <gn-ui-dropdown-selector\n [title]=\"'editor.record.form.field.parentType' | translate\"\n [showTitle]=\"false\"\n [choices]=\"choices\"\n [selected]=\"selectedType\"\n (selectValue)=\"onTypeChange($event)\"\n [extraBtnClass]=\"'input-as-button gn-ui-text-input'\"\n data-cy=\"associated-record-type\"\n ></gn-ui-dropdown-selector>\n </gn-ui-form-field-wrapper>\n }\n</div>\n" }]
38457
+ }], propDecorators: { value: [{
38458
+ type: Input
38459
+ }], valueChange: [{
38460
+ type: Output
38461
+ }] } });
38462
+
37825
38463
  class ContactCardComponent {
37826
38464
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37827
38465
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: ContactCardComponent, isStandalone: true, selector: "gn-ui-contact-card", inputs: { contact: "contact" }, ngImport: i0, template: "<div class=\"gn-ui-card\">\n <gn-ui-thumbnail\n class=\"w-[56px] h-[56px] rounded-[4px] overflow-hidden shrink-0\"\n [thumbnailUrl]=\"contact.organization?.logoUrl?.href\"\n [fit]=\"'contain'\"\n ></gn-ui-thumbnail>\n <div class=\"flex flex-col w-full overflow-hidden leading-snug justify-center\">\n <div class=\"text-[16px] font-bold text-main\" data-test=\"contactCardName\">\n {{ contact.firstName }} {{ contact.lastName }}\n </div>\n <div class=\"text-[14px] text-gray-900\" data-test=\"contactCardEmail\">\n {{ contact.email }}\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: ThumbnailComponent, selector: "gn-ui-thumbnail", inputs: ["thumbnailUrl", "fit"], outputs: ["placeholderShown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -39207,6 +39845,9 @@ class FormFieldComponent {
39207
39845
  get valueAsUpdateFrequency() {
39208
39846
  return this.value;
39209
39847
  }
39848
+ get valueAsAssociatedRecords() {
39849
+ return this.value;
39850
+ }
39210
39851
  get valueAsTemporalExtents() {
39211
39852
  return this.value;
39212
39853
  }
@@ -39246,7 +39887,7 @@ class FormFieldComponent {
39246
39887
  }
39247
39888
  }
39248
39889
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39249
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FormFieldComponent, isStandalone: true, selector: "gn-ui-form-field", inputs: { uniqueIdentifier: "uniqueIdentifier", recordKind: "recordKind", model: "model", modelSpecifier: "modelSpecifier", componentName: "componentName", config: "config", value: "value" }, outputs: { valueChange: "valueChange" }, providers: [provideIcons({ matEditOutline, matHelpOutline })], viewQueries: [{ propertyName: "titleInput", first: true, predicate: ["titleInput"], descendants: true }], hostDirectives: [{ directive: FieldFocusDirective }], ngImport: i0, template: "<!-- TEMPORARY - disabling the open data switch -->\n<!-- <ng-container *ngIf=\"model === 'licenses'\">\n <gn-ui-form-field-open-data\n [value]=\"valueAsConstraints\"\n (valueChange)=\"valueChange.emit($event)\"\n (openDataChange)=\"toggleIsOpenData($event)\"\n ></gn-ui-form-field-open-data>\n</ng-container> -->\n<div class=\"flex flex-col h-full\">\n <ng-container *ngIf=\"withoutWrapper; else withGenericWrapper\">\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </ng-container>\n <ng-template #withGenericWrapper>\n <gn-ui-form-field-wrapper\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n >\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </gn-ui-form-field-wrapper>\n </ng-template>\n</div>\n\n<ng-template #fieldContent>\n <ng-container [ngSwitch]=\"model\">\n <ng-container *ngSwitchCase=\"'title'\">\n <div class=\"flex flex-row flex-start items-center gap-3 mb-[12px]\">\n <textarea\n #titleInput\n cdkTextareaAutosize\n #autosize=\"cdkTextareaAutosize\"\n cdkAutosizeMinRows=\"1\"\n data-test=\"recordTitleInput\"\n class=\"grow font-title text-3xl font-normal overflow-hidden text-black/80\"\n [value]=\"valueAsString\"\n (change)=\"valueChange.emit($event.target.value)\"\n [placeholder]=\"\n 'editor.record.form.field.title.placeholder'\n | translate: { recordKind }\n \"\n ></textarea>\n <div class=\"flex flex-row justify-between self-start mt-0.5\">\n <ng-icon\n name=\"matEditOutline\"\n size=\"16\"\n class=\"m-2 cursor-pointer\"\n (click)=\"focusTitleInput()\"\n ></ng-icon>\n <ng-icon\n *ngIf=\"config.hintKey\"\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"config.hintKey! | translate: { recordKind }\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n </div>\n </div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'abstract'\">\n <gn-ui-form-field-rich\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n [value]=\"valueAsString\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-rich>\n </ng-container>\n <ng-container *ngSwitchCase=\"'overviews'\">\n <gn-ui-form-field-overviews\n [metadataUuid]=\"uniqueIdentifier\"\n [recordKind]=\"recordKind\"\n [value]=\"valueAsOverviews\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-overviews>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceIdentifiers'\">\n <gn-ui-form-field-simple\n [type]=\"'text'\"\n [value]=\"valueAsResourceIdentifierCode\"\n (valueChange)=\"handleResourceIdentifierChange($event)\"\n ></gn-ui-form-field-simple>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceCreated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceUpdated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'updateFrequency'\">\n <gn-ui-form-field-update-frequency\n [value]=\"valueAsUpdateFrequency\"\n (valueChange)=\"valueChange.emit($event)\"\n [recordKind]=\"recordKind\"\n ></gn-ui-form-field-update-frequency>\n </ng-container>\n <ng-container *ngSwitchCase=\"'temporalExtents'\">\n <gn-ui-form-field-temporal-extents\n [value]=\"valueAsTemporalExtents\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-temporal-extents>\n </ng-container>\n <ng-container *ngSwitchCase=\"'spatialExtents'\">\n <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>\n </ng-container>\n <ng-container *ngSwitchCase=\"'keywords'\">\n <gn-ui-form-field-keywords\n [value]=\"valueAsKeywords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-keywords>\n </ng-container>\n <ng-container *ngSwitchCase=\"'topics'\">\n <gn-ui-form-field-topics\n [value]=\"valueAsTopics\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-topics>\n </ng-container>\n <ng-container *ngSwitchCase=\"'licenses'\">\n <gn-ui-form-field-license\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [recordLicences]=\"valueAsConstraints\"\n (recordLicencesChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-license>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'legalConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'securityConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'otherConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container\n ></ng-container>\n <ng-template #formFieldConstraints>\n <gn-ui-form-field-constraints\n [label]=\"config.labelKey\"\n [value]=\"valueAsConstraints\"\n [constraintType]=\"model\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-constraints\n ></ng-template>\n\n <ng-container *ngSwitchCase=\"'contactsForResource'\">\n <gn-ui-form-field-contacts-for-resource\n [value]=\"valueAsIndividuals\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts-for-resource>\n </ng-container>\n <ng-container *ngSwitchCase=\"'contacts'\">\n <gn-ui-form-field-contacts\n [value]=\"valueAsIndividuals\"\n [modelSpecifier]=\"modelSpecifier\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:!link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-link-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-link-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-single-link-resource\n *ngIf=\"modelSpecifier === 'onlineResourceType:singleLink'\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-single-link-resource>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"!model\">\n <ng-container [ngSwitch]=\"componentName\">\n <ng-container *ngSwitchCase=\"'form-field-constraints-shortcuts'\">\n <gn-ui-form-field-constraints-shortcuts></gn-ui-form-field-constraints-shortcuts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'form-field-spatial-toggle'\">\n <gn-ui-form-field-spatial-toggle></gn-ui-form-field-spatial-toggle>\n </ng-container>\n </ng-container>\n </ng-container>\n</ng-template>\n", styles: [":host{scroll-margin-top:90px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FormFieldWrapperComponent, selector: "gn-ui-form-field-wrapper", inputs: ["label", "hint"] }, { kind: "component", type: FormFieldLicenseComponent, selector: "gn-ui-form-field-license", inputs: ["label", "recordLicences"], outputs: ["recordLicencesChange"] }, { kind: "component", type: FormFieldDateComponent, selector: "gn-ui-form-field-date", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldUpdateFrequencyComponent, selector: "gn-ui-form-field-update-frequency", inputs: ["value", "recordKind"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldTemporalExtentsComponent, selector: "gn-ui-form-field-temporal-extents", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldSimpleComponent, selector: "gn-ui-form-field-simple", inputs: ["type", "readonly", "invalid", "placeholder", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldRichComponent, selector: "gn-ui-form-field-rich", inputs: ["label", "hint", "placeholder", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldSpatialExtentComponent, selector: "gn-ui-form-field-spatial-extent" }, { kind: "component", type: FormFieldKeywordsComponent, selector: "gn-ui-form-field-keywords", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOverviewsComponent, selector: "gn-ui-form-field-overviews", inputs: ["metadataUuid", "recordKind", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldContactsForResourceComponent, selector: "gn-ui-form-field-contacts-for-resource", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineResourcesComponent, selector: "gn-ui-form-field-online-resources", inputs: ["metadataUuid", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineLinkResourcesComponent, selector: "gn-ui-form-field-online-link-resources", inputs: ["metadataUuid", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineSingleLinkResourceComponent, selector: "gn-ui-form-field-online-single-link-resource", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldContactsComponent, selector: "gn-ui-form-field-contacts", inputs: ["value", "modelSpecifier"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldConstraintsComponent, selector: "gn-ui-form-field-constraints", inputs: ["label", "value", "constraintType"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldConstraintsShortcutsComponent, selector: "gn-ui-form-field-constraints-shortcuts" }, { kind: "component", type: FormFieldSpatialToggleComponent, selector: "gn-ui-form-field-spatial-toggle" }, { kind: "component", type: FormFieldTopicsComponent, selector: "gn-ui-form-field-topics", inputs: ["value"], outputs: ["valueChange"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "directive", type: i4$1.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39890
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FormFieldComponent, isStandalone: true, selector: "gn-ui-form-field", inputs: { uniqueIdentifier: "uniqueIdentifier", recordKind: "recordKind", model: "model", modelSpecifier: "modelSpecifier", componentName: "componentName", config: "config", value: "value" }, outputs: { valueChange: "valueChange" }, providers: [provideIcons({ matEditOutline, matHelpOutline })], viewQueries: [{ propertyName: "titleInput", first: true, predicate: ["titleInput"], descendants: true }], hostDirectives: [{ directive: FieldFocusDirective }], ngImport: i0, template: "<!-- TEMPORARY - disabling the open data switch -->\n<!-- <ng-container *ngIf=\"model === 'licenses'\">\n <gn-ui-form-field-open-data\n [value]=\"valueAsConstraints\"\n (valueChange)=\"valueChange.emit($event)\"\n (openDataChange)=\"toggleIsOpenData($event)\"\n ></gn-ui-form-field-open-data>\n</ng-container> -->\n<div class=\"flex flex-col h-full\">\n <ng-container *ngIf=\"withoutWrapper; else withGenericWrapper\">\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </ng-container>\n <ng-template #withGenericWrapper>\n <gn-ui-form-field-wrapper\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n >\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </gn-ui-form-field-wrapper>\n </ng-template>\n</div>\n\n<ng-template #fieldContent>\n <ng-container [ngSwitch]=\"model\">\n <ng-container *ngSwitchCase=\"'title'\">\n <div class=\"flex flex-row flex-start items-center gap-3 mb-[12px]\">\n <textarea\n #titleInput\n cdkTextareaAutosize\n #autosize=\"cdkTextareaAutosize\"\n cdkAutosizeMinRows=\"1\"\n data-test=\"recordTitleInput\"\n class=\"grow font-title text-3xl font-normal overflow-hidden text-black/80\"\n [value]=\"valueAsString\"\n (change)=\"valueChange.emit($event.target.value)\"\n [placeholder]=\"\n 'editor.record.form.field.title.placeholder'\n | translate: { recordKind }\n \"\n ></textarea>\n <div class=\"flex flex-row justify-between self-start mt-0.5\">\n <ng-icon\n name=\"matEditOutline\"\n size=\"16\"\n class=\"m-2 cursor-pointer\"\n (click)=\"focusTitleInput()\"\n ></ng-icon>\n <ng-icon\n *ngIf=\"config.hintKey\"\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"config.hintKey! | translate: { recordKind }\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n </div>\n </div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'abstract'\">\n <gn-ui-form-field-rich\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n [value]=\"valueAsString\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-rich>\n </ng-container>\n <ng-container *ngSwitchCase=\"'overviews'\">\n <gn-ui-form-field-overviews\n [metadataUuid]=\"uniqueIdentifier\"\n [recordKind]=\"recordKind\"\n [value]=\"valueAsOverviews\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-overviews>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceIdentifiers'\">\n <gn-ui-form-field-simple\n [type]=\"'text'\"\n [value]=\"valueAsResourceIdentifierCode\"\n (valueChange)=\"handleResourceIdentifierChange($event)\"\n ></gn-ui-form-field-simple>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceCreated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceUpdated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'updateFrequency'\">\n <gn-ui-form-field-update-frequency\n [value]=\"valueAsUpdateFrequency\"\n (valueChange)=\"valueChange.emit($event)\"\n [recordKind]=\"recordKind\"\n ></gn-ui-form-field-update-frequency>\n </ng-container>\n <ng-container *ngSwitchCase=\"'associatedRecords'\">\n <gn-ui-form-field-associated-records\n [value]=\"valueAsAssociatedRecords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-associated-records>\n </ng-container>\n <ng-container *ngSwitchCase=\"'temporalExtents'\">\n <gn-ui-form-field-temporal-extents\n [value]=\"valueAsTemporalExtents\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-temporal-extents>\n </ng-container>\n <ng-container *ngSwitchCase=\"'spatialExtents'\">\n <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>\n </ng-container>\n <ng-container *ngSwitchCase=\"'keywords'\">\n <gn-ui-form-field-keywords\n [value]=\"valueAsKeywords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-keywords>\n </ng-container>\n <ng-container *ngSwitchCase=\"'topics'\">\n <gn-ui-form-field-topics\n [value]=\"valueAsTopics\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-topics>\n </ng-container>\n <ng-container *ngSwitchCase=\"'licenses'\">\n <gn-ui-form-field-license\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [recordLicences]=\"valueAsConstraints\"\n (recordLicencesChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-license>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'legalConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'securityConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'otherConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container\n ></ng-container>\n <ng-template #formFieldConstraints>\n <gn-ui-form-field-constraints\n [label]=\"config.labelKey\"\n [value]=\"valueAsConstraints\"\n [constraintType]=\"model\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-constraints\n ></ng-template>\n\n <ng-container *ngSwitchCase=\"'contactsForResource'\">\n <gn-ui-form-field-contacts-for-resource\n [value]=\"valueAsIndividuals\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts-for-resource>\n </ng-container>\n <ng-container *ngSwitchCase=\"'contacts'\">\n <gn-ui-form-field-contacts\n [value]=\"valueAsIndividuals\"\n [modelSpecifier]=\"modelSpecifier\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:!link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-link-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-link-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-single-link-resource\n *ngIf=\"modelSpecifier === 'onlineResourceType:singleLink'\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-single-link-resource>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"!model\">\n <ng-container [ngSwitch]=\"componentName\">\n <ng-container *ngSwitchCase=\"'form-field-constraints-shortcuts'\">\n <gn-ui-form-field-constraints-shortcuts></gn-ui-form-field-constraints-shortcuts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'form-field-spatial-toggle'\">\n <gn-ui-form-field-spatial-toggle></gn-ui-form-field-spatial-toggle>\n </ng-container>\n </ng-container>\n </ng-container>\n</ng-template>\n", styles: [":host{scroll-margin-top:90px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FormFieldWrapperComponent, selector: "gn-ui-form-field-wrapper", inputs: ["label", "hint"] }, { kind: "component", type: FormFieldLicenseComponent, selector: "gn-ui-form-field-license", inputs: ["label", "recordLicences"], outputs: ["recordLicencesChange"] }, { kind: "component", type: FormFieldDateComponent, selector: "gn-ui-form-field-date", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldUpdateFrequencyComponent, selector: "gn-ui-form-field-update-frequency", inputs: ["value", "recordKind"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldAssociatedRecordsComponent, selector: "gn-ui-form-field-associated-records", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldTemporalExtentsComponent, selector: "gn-ui-form-field-temporal-extents", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldSimpleComponent, selector: "gn-ui-form-field-simple", inputs: ["type", "readonly", "invalid", "placeholder", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldRichComponent, selector: "gn-ui-form-field-rich", inputs: ["label", "hint", "placeholder", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldSpatialExtentComponent, selector: "gn-ui-form-field-spatial-extent" }, { kind: "component", type: FormFieldKeywordsComponent, selector: "gn-ui-form-field-keywords", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOverviewsComponent, selector: "gn-ui-form-field-overviews", inputs: ["metadataUuid", "recordKind", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldContactsForResourceComponent, selector: "gn-ui-form-field-contacts-for-resource", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineResourcesComponent, selector: "gn-ui-form-field-online-resources", inputs: ["metadataUuid", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineLinkResourcesComponent, selector: "gn-ui-form-field-online-link-resources", inputs: ["metadataUuid", "value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldOnlineSingleLinkResourceComponent, selector: "gn-ui-form-field-online-single-link-resource", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldContactsComponent, selector: "gn-ui-form-field-contacts", inputs: ["value", "modelSpecifier"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldConstraintsComponent, selector: "gn-ui-form-field-constraints", inputs: ["label", "value", "constraintType"], outputs: ["valueChange"] }, { kind: "component", type: FormFieldConstraintsShortcutsComponent, selector: "gn-ui-form-field-constraints-shortcuts" }, { kind: "component", type: FormFieldSpatialToggleComponent, selector: "gn-ui-form-field-spatial-toggle" }, { kind: "component", type: FormFieldTopicsComponent, selector: "gn-ui-form-field-topics", inputs: ["value"], outputs: ["valueChange"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "directive", type: i4$1.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39250
39891
  }
39251
39892
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldComponent, decorators: [{
39252
39893
  type: Component,
@@ -39258,6 +39899,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39258
39899
  FormFieldLicenseComponent,
39259
39900
  FormFieldDateComponent,
39260
39901
  FormFieldUpdateFrequencyComponent,
39902
+ FormFieldAssociatedRecordsComponent,
39261
39903
  FormFieldTemporalExtentsComponent,
39262
39904
  FormFieldSimpleComponent,
39263
39905
  FormFieldRichComponent,
@@ -39275,7 +39917,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39275
39917
  FormFieldTopicsComponent,
39276
39918
  TextFieldModule,
39277
39919
  NgIconComponent,
39278
- ], providers: [provideIcons({ matEditOutline, matHelpOutline })], hostDirectives: [FieldFocusDirective], template: "<!-- TEMPORARY - disabling the open data switch -->\n<!-- <ng-container *ngIf=\"model === 'licenses'\">\n <gn-ui-form-field-open-data\n [value]=\"valueAsConstraints\"\n (valueChange)=\"valueChange.emit($event)\"\n (openDataChange)=\"toggleIsOpenData($event)\"\n ></gn-ui-form-field-open-data>\n</ng-container> -->\n<div class=\"flex flex-col h-full\">\n <ng-container *ngIf=\"withoutWrapper; else withGenericWrapper\">\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </ng-container>\n <ng-template #withGenericWrapper>\n <gn-ui-form-field-wrapper\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n >\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </gn-ui-form-field-wrapper>\n </ng-template>\n</div>\n\n<ng-template #fieldContent>\n <ng-container [ngSwitch]=\"model\">\n <ng-container *ngSwitchCase=\"'title'\">\n <div class=\"flex flex-row flex-start items-center gap-3 mb-[12px]\">\n <textarea\n #titleInput\n cdkTextareaAutosize\n #autosize=\"cdkTextareaAutosize\"\n cdkAutosizeMinRows=\"1\"\n data-test=\"recordTitleInput\"\n class=\"grow font-title text-3xl font-normal overflow-hidden text-black/80\"\n [value]=\"valueAsString\"\n (change)=\"valueChange.emit($event.target.value)\"\n [placeholder]=\"\n 'editor.record.form.field.title.placeholder'\n | translate: { recordKind }\n \"\n ></textarea>\n <div class=\"flex flex-row justify-between self-start mt-0.5\">\n <ng-icon\n name=\"matEditOutline\"\n size=\"16\"\n class=\"m-2 cursor-pointer\"\n (click)=\"focusTitleInput()\"\n ></ng-icon>\n <ng-icon\n *ngIf=\"config.hintKey\"\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"config.hintKey! | translate: { recordKind }\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n </div>\n </div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'abstract'\">\n <gn-ui-form-field-rich\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n [value]=\"valueAsString\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-rich>\n </ng-container>\n <ng-container *ngSwitchCase=\"'overviews'\">\n <gn-ui-form-field-overviews\n [metadataUuid]=\"uniqueIdentifier\"\n [recordKind]=\"recordKind\"\n [value]=\"valueAsOverviews\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-overviews>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceIdentifiers'\">\n <gn-ui-form-field-simple\n [type]=\"'text'\"\n [value]=\"valueAsResourceIdentifierCode\"\n (valueChange)=\"handleResourceIdentifierChange($event)\"\n ></gn-ui-form-field-simple>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceCreated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceUpdated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'updateFrequency'\">\n <gn-ui-form-field-update-frequency\n [value]=\"valueAsUpdateFrequency\"\n (valueChange)=\"valueChange.emit($event)\"\n [recordKind]=\"recordKind\"\n ></gn-ui-form-field-update-frequency>\n </ng-container>\n <ng-container *ngSwitchCase=\"'temporalExtents'\">\n <gn-ui-form-field-temporal-extents\n [value]=\"valueAsTemporalExtents\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-temporal-extents>\n </ng-container>\n <ng-container *ngSwitchCase=\"'spatialExtents'\">\n <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>\n </ng-container>\n <ng-container *ngSwitchCase=\"'keywords'\">\n <gn-ui-form-field-keywords\n [value]=\"valueAsKeywords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-keywords>\n </ng-container>\n <ng-container *ngSwitchCase=\"'topics'\">\n <gn-ui-form-field-topics\n [value]=\"valueAsTopics\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-topics>\n </ng-container>\n <ng-container *ngSwitchCase=\"'licenses'\">\n <gn-ui-form-field-license\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [recordLicences]=\"valueAsConstraints\"\n (recordLicencesChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-license>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'legalConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'securityConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'otherConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container\n ></ng-container>\n <ng-template #formFieldConstraints>\n <gn-ui-form-field-constraints\n [label]=\"config.labelKey\"\n [value]=\"valueAsConstraints\"\n [constraintType]=\"model\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-constraints\n ></ng-template>\n\n <ng-container *ngSwitchCase=\"'contactsForResource'\">\n <gn-ui-form-field-contacts-for-resource\n [value]=\"valueAsIndividuals\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts-for-resource>\n </ng-container>\n <ng-container *ngSwitchCase=\"'contacts'\">\n <gn-ui-form-field-contacts\n [value]=\"valueAsIndividuals\"\n [modelSpecifier]=\"modelSpecifier\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:!link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-link-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-link-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-single-link-resource\n *ngIf=\"modelSpecifier === 'onlineResourceType:singleLink'\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-single-link-resource>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"!model\">\n <ng-container [ngSwitch]=\"componentName\">\n <ng-container *ngSwitchCase=\"'form-field-constraints-shortcuts'\">\n <gn-ui-form-field-constraints-shortcuts></gn-ui-form-field-constraints-shortcuts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'form-field-spatial-toggle'\">\n <gn-ui-form-field-spatial-toggle></gn-ui-form-field-spatial-toggle>\n </ng-container>\n </ng-container>\n </ng-container>\n</ng-template>\n", styles: [":host{scroll-margin-top:90px}\n"] }]
39920
+ ], providers: [provideIcons({ matEditOutline, matHelpOutline })], hostDirectives: [FieldFocusDirective], template: "<!-- TEMPORARY - disabling the open data switch -->\n<!-- <ng-container *ngIf=\"model === 'licenses'\">\n <gn-ui-form-field-open-data\n [value]=\"valueAsConstraints\"\n (valueChange)=\"valueChange.emit($event)\"\n (openDataChange)=\"toggleIsOpenData($event)\"\n ></gn-ui-form-field-open-data>\n</ng-container> -->\n<div class=\"flex flex-col h-full\">\n <ng-container *ngIf=\"withoutWrapper; else withGenericWrapper\">\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </ng-container>\n <ng-template #withGenericWrapper>\n <gn-ui-form-field-wrapper\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n >\n <ng-container *ngTemplateOutlet=\"fieldContent\"></ng-container>\n </gn-ui-form-field-wrapper>\n </ng-template>\n</div>\n\n<ng-template #fieldContent>\n <ng-container [ngSwitch]=\"model\">\n <ng-container *ngSwitchCase=\"'title'\">\n <div class=\"flex flex-row flex-start items-center gap-3 mb-[12px]\">\n <textarea\n #titleInput\n cdkTextareaAutosize\n #autosize=\"cdkTextareaAutosize\"\n cdkAutosizeMinRows=\"1\"\n data-test=\"recordTitleInput\"\n class=\"grow font-title text-3xl font-normal overflow-hidden text-black/80\"\n [value]=\"valueAsString\"\n (change)=\"valueChange.emit($event.target.value)\"\n [placeholder]=\"\n 'editor.record.form.field.title.placeholder'\n | translate: { recordKind }\n \"\n ></textarea>\n <div class=\"flex flex-row justify-between self-start mt-0.5\">\n <ng-icon\n name=\"matEditOutline\"\n size=\"16\"\n class=\"m-2 cursor-pointer\"\n (click)=\"focusTitleInput()\"\n ></ng-icon>\n <ng-icon\n *ngIf=\"config.hintKey\"\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"config.hintKey! | translate: { recordKind }\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n </div>\n </div>\n </ng-container>\n <ng-container *ngSwitchCase=\"'abstract'\">\n <gn-ui-form-field-rich\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [hint]=\"config.hintKey! | translate: { recordKind }\"\n [value]=\"valueAsString\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-rich>\n </ng-container>\n <ng-container *ngSwitchCase=\"'overviews'\">\n <gn-ui-form-field-overviews\n [metadataUuid]=\"uniqueIdentifier\"\n [recordKind]=\"recordKind\"\n [value]=\"valueAsOverviews\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-overviews>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceIdentifiers'\">\n <gn-ui-form-field-simple\n [type]=\"'text'\"\n [value]=\"valueAsResourceIdentifierCode\"\n (valueChange)=\"handleResourceIdentifierChange($event)\"\n ></gn-ui-form-field-simple>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceCreated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'resourceUpdated'\">\n <gn-ui-form-field-date\n [value]=\"valueAsDate\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-date>\n </ng-container>\n <ng-container *ngSwitchCase=\"'updateFrequency'\">\n <gn-ui-form-field-update-frequency\n [value]=\"valueAsUpdateFrequency\"\n (valueChange)=\"valueChange.emit($event)\"\n [recordKind]=\"recordKind\"\n ></gn-ui-form-field-update-frequency>\n </ng-container>\n <ng-container *ngSwitchCase=\"'associatedRecords'\">\n <gn-ui-form-field-associated-records\n [value]=\"valueAsAssociatedRecords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-associated-records>\n </ng-container>\n <ng-container *ngSwitchCase=\"'temporalExtents'\">\n <gn-ui-form-field-temporal-extents\n [value]=\"valueAsTemporalExtents\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-temporal-extents>\n </ng-container>\n <ng-container *ngSwitchCase=\"'spatialExtents'\">\n <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>\n </ng-container>\n <ng-container *ngSwitchCase=\"'keywords'\">\n <gn-ui-form-field-keywords\n [value]=\"valueAsKeywords\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-keywords>\n </ng-container>\n <ng-container *ngSwitchCase=\"'topics'\">\n <gn-ui-form-field-topics\n [value]=\"valueAsTopics\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-topics>\n </ng-container>\n <ng-container *ngSwitchCase=\"'licenses'\">\n <gn-ui-form-field-license\n [label]=\"config.labelKey! | translate: { recordKind }\"\n [recordLicences]=\"valueAsConstraints\"\n (recordLicencesChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-license>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'legalConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'securityConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container>\n </ng-container>\n <ng-container *ngSwitchCase=\"'otherConstraints'\">\n <ng-container *ngTemplateOutlet=\"formFieldConstraints\"></ng-container\n ></ng-container>\n <ng-template #formFieldConstraints>\n <gn-ui-form-field-constraints\n [label]=\"config.labelKey\"\n [value]=\"valueAsConstraints\"\n [constraintType]=\"model\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-constraints\n ></ng-template>\n\n <ng-container *ngSwitchCase=\"'contactsForResource'\">\n <gn-ui-form-field-contacts-for-resource\n [value]=\"valueAsIndividuals\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts-for-resource>\n </ng-container>\n <ng-container *ngSwitchCase=\"'contacts'\">\n <gn-ui-form-field-contacts\n [value]=\"valueAsIndividuals\"\n [modelSpecifier]=\"modelSpecifier\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-contacts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:!link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-link-resources\n *ngIf=\"modelSpecifier === 'onlineResourceType:link'\"\n [metadataUuid]=\"uniqueIdentifier\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-link-resources>\n </ng-container>\n <ng-container *ngSwitchCase=\"'onlineResources'\">\n <gn-ui-form-field-online-single-link-resource\n *ngIf=\"modelSpecifier === 'onlineResourceType:singleLink'\"\n [value]=\"valueAsOnlineResources\"\n (valueChange)=\"valueChange.emit($event)\"\n ></gn-ui-form-field-online-single-link-resource>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"!model\">\n <ng-container [ngSwitch]=\"componentName\">\n <ng-container *ngSwitchCase=\"'form-field-constraints-shortcuts'\">\n <gn-ui-form-field-constraints-shortcuts></gn-ui-form-field-constraints-shortcuts>\n </ng-container>\n <ng-container *ngSwitchCase=\"'form-field-spatial-toggle'\">\n <gn-ui-form-field-spatial-toggle></gn-ui-form-field-spatial-toggle>\n </ng-container>\n </ng-container>\n </ng-container>\n</ng-template>\n", styles: [":host{scroll-margin-top:90px}\n"] }]
39279
39921
  }], propDecorators: { uniqueIdentifier: [{
39280
39922
  type: Input
39281
39923
  }], recordKind: [{
@@ -39860,7 +40502,7 @@ class RouterService {
39860
40502
  return ROUTER_ROUTE_ORGANIZATION;
39861
40503
  }
39862
40504
  getDefaultSort() {
39863
- return SortByEnum.RESOURCE_DATES;
40505
+ return SortByEnum.RESOURCE_DATE;
39864
40506
  }
39865
40507
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39866
40508
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, providedIn: 'root' }); }
@@ -39920,7 +40562,7 @@ function flattenQueryParams(params) {
39920
40562
  const start = flattened[key].start;
39921
40563
  const end = flattened[key].end;
39922
40564
  flattened[key] = [
39923
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40565
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
39924
40566
  ];
39925
40567
  }
39926
40568
  }
@@ -40277,5 +40919,5 @@ const CHART_TYPE_VALUES = [
40277
40919
  * Generated bundle index. Do not edit.
40278
40920
  */
40279
40921
 
40280
- export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40922
+ export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AutofocusDirective, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, BoundingBoxSearchField, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_BASEMAP_LAYER, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpatialExtentDropdownComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isBoundingBox, isConfigLoaded, isDateRange, isFileExtensionValid, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideLocalizedDateAdapter, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readFileAsText, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40281
40923
  //# sourceMappingURL=geonetwork-ui.mjs.map