geonetwork-ui 2.11.0-dev.8442dae6e → 2.11.0-dev.8aa6985e0

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 (104) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1572 -869
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +314 -128
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +130 -37
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +22 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  18. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  20. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  21. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  22. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  23. package/src/libs/feature/map/src/index.ts +0 -1
  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/map/src/lib/feature-map.module.ts +1 -8
  26. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  27. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  28. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  29. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  30. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  31. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  32. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  33. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  34. package/src/libs/feature/search/src/index.ts +2 -0
  35. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  36. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  37. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  38. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  39. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  40. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  41. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  42. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  43. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  44. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  45. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  46. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  47. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  48. package/src/libs/ui/inputs/src/index.ts +2 -0
  49. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  50. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  51. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  52. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  53. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  54. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  55. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  56. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  57. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  58. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  59. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  60. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  61. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  62. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  63. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  64. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  65. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  66. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  67. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  68. package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
  69. package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
  70. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  71. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  72. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  73. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  74. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  75. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  76. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  77. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  78. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  79. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  80. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  81. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  82. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  83. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  84. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  85. package/src/libs/util/shared/src/index.ts +1 -0
  86. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  87. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  88. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  89. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  90. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  91. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  92. package/tailwind.base.css +5 -0
  93. package/translations/de.json +31 -4
  94. package/translations/en.json +30 -3
  95. package/translations/es.json +28 -1
  96. package/translations/fr.json +31 -4
  97. package/translations/it.json +29 -2
  98. package/translations/nl.json +28 -1
  99. package/translations/pt.json +28 -1
  100. package/translations/sk.json +29 -2
  101. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  102. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  103. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  104. /package/src/libs/feature/{map/src/lib/geocoding/geocoding.component.css → editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css} +0 -0
@@ -6,9 +6,9 @@ import GeoJSON from 'ol/format/GeoJSON.js';
6
6
  import { parse as parse$1 } from 'ol/xml.js';
7
7
  import { format } from 'date-fns/format';
8
8
  import { Namespace, Literal, lit, parse as parse$2, sym, BlankNode, graph } from 'rdflib';
9
- import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, combineLatest, from, exhaustMap, throwError, forkJoin, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
9
+ import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, forkJoin, combineLatest, from, exhaustMap, throwError, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1, takeUntil } from 'rxjs';
10
10
  import * as i0 from '@angular/core';
11
- import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, HostListener, Input, Directive, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, afterNextRender, viewChildren } from '@angular/core';
11
+ import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, afterNextRender, booleanAttribute, Input, Directive, HostListener, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
13
13
  import * as i4 from '@ngx-translate/core';
14
14
  import { TranslateLoader, TranslateCompiler, TranslateDefaultParser, TranslateParser, TranslateService, provideTranslateService, TranslateDirective, TranslatePipe, TranslateModule } from '@ngx-translate/core';
@@ -18,7 +18,7 @@ import { map as map$1, shareReplay as shareReplay$1, catchError, tap as tap$1, f
18
18
  import { lt, valid, coerce, satisfies, ltr } from 'semver';
19
19
  import chroma from 'chroma-js';
20
20
  import * as i1$1 from '@angular/common';
21
- import { Location, CommonModule, NgClass, NgTemplateOutlet, DatePipe, AsyncPipe } from '@angular/common';
21
+ import { Location, CommonModule, NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common';
22
22
  import { formatDistance } from 'date-fns/formatDistance';
23
23
  import { Scroll, NavigationEnd, Router, RouteReuseStrategy } from '@angular/router';
24
24
  import { WmtsEndpoint, WmsEndpoint, WfsEndpoint, OgcApiEndpoint, sharedFetch, useCache, StacEndpoint, TmsEndpoint } from '@camptocamp/ogc-client';
@@ -26,7 +26,6 @@ import { getLength } from 'ol/sphere.js';
26
26
  import { LineString } from 'ol/geom.js';
27
27
  import * as i1 from '@ngrx/store';
28
28
  import { createAction, props, createReducer, on, createFeatureSelector, createSelector, Store, select, StoreModule } from '@ngrx/store';
29
- import { queryDataGouvFr, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
30
29
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
31
30
  import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, MapExtentChangeEventType, SourceLoadErrorType, computeMapContextDiff, createViewFromLayer } from '@geospatial-sdk/core';
32
31
  import { createMapFromContext, applyContextDiffToMap, listen } from '@geospatial-sdk/openlayers';
@@ -41,10 +40,10 @@ import * as TOML from '@ltd/j-toml';
41
40
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
41
  import CircleStyle from 'ol/style/Circle.js';
43
42
  import EmblaCarousel from 'embla-carousel';
44
- import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirImport, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirCheckCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
43
+ import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirUpload, iconoirTrash, iconoirSquareDashed, iconoirCheckCircle, 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, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
45
44
  import { MatButtonModule } from '@angular/material/button';
46
- import * as i1$8 from '@angular/cdk/overlay';
47
- import { OverlayContainer, ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
45
+ import * as i1$7 from '@angular/cdk/overlay';
46
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
47
  import * as i1$3 from '@angular/forms';
49
48
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
49
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +51,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
51
  import tippy from 'tippy.js';
53
52
  import * as i2 from '@angular/material/progress-spinner';
54
53
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
55
- import { matClose, matContentCopy, matExpandLess, matExpandMore, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
54
+ import { matClose, matContentCopy, matExpandMore, matExpandLess, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
56
55
  import * as i1$4 from '@angular/material/checkbox';
57
56
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
57
  import * as i1$5 from '@angular/material/tooltip';
59
58
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
59
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
60
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
61
+ import { enUS } from 'date-fns/locale/en-US';
61
62
  import * as i1$6 from '@angular/material/datepicker';
62
63
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
64
+ import * as i1$8 from 'ngx-dropzone';
65
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
66
  import * as i1$9 from '@angular/material/button-toggle';
66
67
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
68
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -72,6 +73,7 @@ import { MatTabsModule } from '@angular/material/tabs';
72
73
  import * as i2$2 from '@ngrx/effects';
73
74
  import { Actions, createEffect, ofType, EffectsModule } from '@ngrx/effects';
74
75
  import { valid as valid$1 } from 'geojson-validation';
76
+ import { queryGeoplateforme, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
75
77
  import { TemplatePortal } from '@angular/cdk/portal';
76
78
  import { parse as parse$3 } from 'marked';
77
79
  import * as i2$3 from '@angular/cdk/scrolling';
@@ -79,10 +81,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
81
  import Duration from 'duration-relativetimeformat';
80
82
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
83
  import { trigger, transition, animate, keyframes, style } from '@angular/animations';
82
- import * as Papa from 'papaparse';
83
- import { parse as parse$4 } from 'date-fns/parse';
84
- import { parseISO } from 'date-fns/parseISO';
85
- import { WFS, GeoJSON as GeoJSON$1 } from 'ol/format.js';
84
+ import * as duckdb from '@duckdb/duckdb-wasm';
85
+ import { Vector, DataType } from 'apache-arrow';
86
+ import WFS from 'ol/format/WFS';
87
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
88
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
89
  import * as i2$4 from '@angular/material/sort';
88
90
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +92,6 @@ import * as i1$b from '@angular/material/table';
90
92
  import { MatTableModule } from '@angular/material/table';
91
93
  import * as i3 from '@angular/material/paginator';
92
94
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
95
  import { Meta } from '@angular/platform-browser';
95
96
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
97
  import * as i3$1 from '@angular/material/radio';
@@ -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,24 @@ 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
+ }
19599
+ /**
19600
+ * Checks that a bounding box's coordinates stay within the valid
19601
+ * longitude (-180/180) and latitude (-90/90) ranges.
19602
+ */
19603
+ function isBoundingBoxWithinWorldExtent(bbox) {
19604
+ const [minX, minY, maxX, maxY] = bbox;
19605
+ return (minX >= -180 &&
19606
+ maxX <= 180 &&
19607
+ minY >= -90 &&
19608
+ maxY <= 90 &&
19609
+ minX <= maxX &&
19610
+ minY <= maxY);
19611
+ }
19551
19612
  function getGeometryBoundingBox(geometry) {
19552
19613
  // use the bounding box if specified in the GeoJSON object
19553
19614
  if (geometry.bbox) {
@@ -20216,7 +20277,7 @@ function getLinkLabel(link) {
20216
20277
  const label = link.description || ('name' in link ? link.name : '');
20217
20278
  return format ? `${label} (${format})` : label;
20218
20279
  }
20219
- async function getLayers(url, serviceProtocol) {
20280
+ async function getLayers(url, serviceProtocol, deep = false) {
20220
20281
  switch (serviceProtocol) {
20221
20282
  case 'ogcFeatures': {
20222
20283
  const layers = await new OgcApiEndpoint(url).allCollections;
@@ -20224,7 +20285,10 @@ async function getLayers(url, serviceProtocol) {
20224
20285
  }
20225
20286
  case 'wfs': {
20226
20287
  const endpointWfs = await new WfsEndpoint(url).isReady();
20227
- const featureTypes = await endpointWfs.getFeatureTypes();
20288
+ const featureTypes = endpointWfs.getFeatureTypes();
20289
+ if (!deep) {
20290
+ return featureTypes;
20291
+ }
20228
20292
  const layers = (await Promise.allSettled(featureTypes.map((collection) => {
20229
20293
  return endpointWfs.getFeatureTypeFull(collection.name);
20230
20294
  })))
@@ -20234,12 +20298,14 @@ async function getLayers(url, serviceProtocol) {
20234
20298
  }
20235
20299
  case 'wms': {
20236
20300
  const endpointWms = await new WmsEndpoint(url).isReady();
20237
- const layers = (await endpointWms
20301
+ const layersSummary = endpointWms
20238
20302
  .getLayers()
20239
20303
  .flatMap(wmsLayerFlatten)
20240
- .filter((l) => l.name)).map((collection) => {
20241
- return endpointWms.getLayerByName(collection.name);
20242
- });
20304
+ .filter((l) => l.name);
20305
+ if (!deep) {
20306
+ return layersSummary;
20307
+ }
20308
+ const layers = layersSummary.map((collection) => endpointWms.getLayerByName(collection.name));
20243
20309
  return layers;
20244
20310
  }
20245
20311
  case 'wmts': {
@@ -20333,6 +20399,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20333
20399
  }]
20334
20400
  }] });
20335
20401
 
20402
+ class AutofocusDirective {
20403
+ constructor() {
20404
+ this.el = inject(ElementRef);
20405
+ this.injector = inject(Injector);
20406
+ }
20407
+ set autofocus(active) {
20408
+ if (!active)
20409
+ return;
20410
+ afterNextRender(() => this.el.nativeElement.focus(), {
20411
+ injector: this.injector,
20412
+ });
20413
+ }
20414
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20415
+ 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 }); }
20416
+ }
20417
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20418
+ type: Directive,
20419
+ args: [{
20420
+ selector: '[gnUiAutofocus]',
20421
+ standalone: true,
20422
+ }]
20423
+ }], propDecorators: { autofocus: [{
20424
+ type: Input,
20425
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20426
+ }] } });
20427
+
20336
20428
  class ImageFallbackDirective {
20337
20429
  constructor() {
20338
20430
  this.el = inject(ElementRef);
@@ -20360,7 +20452,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20360
20452
  }] } });
20361
20453
 
20362
20454
  var name = "geonetwork-ui";
20363
- var version = "2.11.0-dev.8442dae6e";
20455
+ var version = "2.11.0-dev.8aa6985e0";
20364
20456
  var engines = {
20365
20457
  node: ">=24"
20366
20458
  };
@@ -20381,6 +20473,7 @@ var peerDependencies = {
20381
20473
  "@angular/core": "19.x || 20.x || 21.x",
20382
20474
  "@angular/forms": "19.x || 20.x || 21.x",
20383
20475
  "@angular/material": "19.x || 20.x || 21.x",
20476
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20384
20477
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20385
20478
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20386
20479
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20390,20 +20483,21 @@ var peerDependencies = {
20390
20483
  "@ngrx/store": "19.x || 20.x || 21.x",
20391
20484
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20392
20485
  "@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",
20486
+ "@ngx-translate/core": "17.x",
20487
+ "@ngx-translate/http-loader": "17.x",
20488
+ "flag-icons": "~7.5.0",
20396
20489
  rxjs: "7.x",
20397
20490
  "zone.js": "*",
20398
20491
  tailwindcss: "3.x"
20399
20492
  };
20400
20493
  var dependencies = {
20401
20494
  "@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",
20495
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20496
+ "@duckdb/duckdb-wasm": "~1.32.0",
20497
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20498
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20499
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20500
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20407
20501
  "@ltd/j-toml": "~1.38.0",
20408
20502
  "@messageformat/core": "~3.4.0",
20409
20503
  "@ng-icons/core": "~32.5.0",
@@ -20411,7 +20505,7 @@ var dependencies = {
20411
20505
  "@ng-icons/material-icons": "~32.5.0",
20412
20506
  "@ng-icons/tabler-icons": "~32.4.0",
20413
20507
  "@rgrove/parse-xml": "4.2.0",
20414
- alasql: "~4.17.0",
20508
+ "apache-arrow": "~17.0.0",
20415
20509
  "chart.js": "~4.5.1",
20416
20510
  "chroma-js": "~3.2.0",
20417
20511
  "date-fns": "4.1.0",
@@ -20424,7 +20518,6 @@ var dependencies = {
20424
20518
  "ngx-dropzone": "~3.1.0",
20425
20519
  "ngx-translate-messageformat-compiler": "~7.2.0",
20426
20520
  ol: "~10.8.0",
20427
- papaparse: "~5.5.3",
20428
20521
  proj4: "~2.20.4",
20429
20522
  rdflib: "~2.3.5",
20430
20523
  semver: "~7.7.4",
@@ -20622,8 +20715,8 @@ class ElasticsearchService {
20622
20715
  if (!payload.runtime_mappings)
20623
20716
  payload.runtime_mappings = {};
20624
20717
  payload.runtime_mappings[fieldName] = {
20625
- type: 'keyword',
20626
- script: this.runtimeFields[fieldName],
20718
+ type: this.runtimeFields[fieldName].type,
20719
+ script: this.runtimeFields[fieldName].script,
20627
20720
  };
20628
20721
  };
20629
20722
  const lookForField = (node) => {
@@ -20643,6 +20736,12 @@ class ElasticsearchService {
20643
20736
  (node[runtimeField] === 'asc' || node[runtimeField] === 'desc')) {
20644
20737
  addMapping(runtimeField);
20645
20738
  }
20739
+ if (runtimeField in node &&
20740
+ typeof node[runtimeField] === 'object' &&
20741
+ node[runtimeField] !== null &&
20742
+ ('gte' in node[runtimeField] || 'lte' in node[runtimeField])) {
20743
+ addMapping(runtimeField);
20744
+ }
20646
20745
  if ('query' in node &&
20647
20746
  typeof node.query === 'string' &&
20648
20747
  node.query.indexOf(runtimeField + ':') > -1) {
@@ -20659,8 +20758,8 @@ class ElasticsearchService {
20659
20758
  lookForField(payload.query);
20660
20759
  return payload;
20661
20760
  }
20662
- registerRuntimeField(fieldName, expression) {
20663
- this.runtimeFields[fieldName] = expression;
20761
+ registerRuntimeField(fieldName, expression, type = 'keyword') {
20762
+ this.runtimeFields[fieldName] = { script: expression, type };
20664
20763
  }
20665
20764
  getMetadataByIdsPayload(uuids) {
20666
20765
  return {
@@ -20669,6 +20768,7 @@ class ElasticsearchService {
20669
20768
  values: uuids,
20670
20769
  },
20671
20770
  },
20771
+ size: uuids.length,
20672
20772
  };
20673
20773
  }
20674
20774
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20719,10 +20819,28 @@ class ElasticsearchService {
20719
20819
  };
20720
20820
  }
20721
20821
  buildPayloadSort(sortBy) {
20722
- if (sortBy === null)
20822
+ if (!sortBy || sortBy.length === 0)
20723
20823
  return undefined;
20724
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy];
20725
- return fields.map((field) => ({ [field[1]]: field[0] }));
20824
+ const fields = Array.isArray(sortBy[0])
20825
+ ? sortBy
20826
+ : [sortBy];
20827
+ return fields.map((field) => {
20828
+ // Sort by nested array of dates only works with the explicit syntax
20829
+ if (field[1].endsWith('.date')) {
20830
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'));
20831
+ return {
20832
+ [field[1]]: {
20833
+ order: field[0],
20834
+ mode: field[0] === 'desc' ? 'max' : 'min',
20835
+ missing: '_last',
20836
+ nested: {
20837
+ path: nestedPath,
20838
+ },
20839
+ },
20840
+ };
20841
+ }
20842
+ return { [field[1]]: field[0] };
20843
+ });
20726
20844
  }
20727
20845
  injectLangInQueryStringFields(queryFieldsPriority) {
20728
20846
  const queryLang = this.getQueryLang();
@@ -20758,7 +20876,49 @@ class ElasticsearchService {
20758
20876
  isCurrentSearchLang() {
20759
20877
  return this.metadataLang === 'current';
20760
20878
  }
20761
- filtersToQuery(filters) {
20879
+ findSpatialFilterExtent(filters) {
20880
+ if (typeof filters === 'string') {
20881
+ return undefined;
20882
+ }
20883
+ return Object.values(filters).find(isBoundingBox);
20884
+ }
20885
+ // if a field registered min/max runtime fields, its dates form an interval
20886
+ // the record matches if that interval intersects the filter range
20887
+ buildDateRangeQuery(searchField, dateRange) {
20888
+ const minField = `${searchField}Min`;
20889
+ const maxField = `${searchField}Max`;
20890
+ if (minField in this.runtimeFields && maxField in this.runtimeFields) {
20891
+ const filter = [
20892
+ dateRange.start && {
20893
+ range: {
20894
+ [maxField]: {
20895
+ gte: formatDate(dateRange.start),
20896
+ format: 'yyyy-MM-dd',
20897
+ },
20898
+ },
20899
+ },
20900
+ dateRange.end && {
20901
+ range: {
20902
+ [minField]: {
20903
+ lte: formatDate(dateRange.end),
20904
+ format: 'yyyy-MM-dd',
20905
+ },
20906
+ },
20907
+ },
20908
+ ].filter(Boolean);
20909
+ return { bool: { filter } };
20910
+ }
20911
+ return {
20912
+ range: {
20913
+ [searchField]: {
20914
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
20915
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
20916
+ format: 'yyyy-MM-dd',
20917
+ },
20918
+ },
20919
+ };
20920
+ }
20921
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20762
20922
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20763
20923
  const makeQuery = (filter) => {
20764
20924
  if (typeof filter === 'string') {
@@ -20777,7 +20937,9 @@ class ElasticsearchService {
20777
20937
  ? filters
20778
20938
  : Object.keys(filters)
20779
20939
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20940
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20780
20941
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20942
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20781
20943
  .filter((fieldname) => filters[fieldname] &&
20782
20944
  JSON.stringify(filters[fieldname]) !== '{}')
20783
20945
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20785,31 +20947,26 @@ class ElasticsearchService {
20785
20947
  if (filters['gn-ui-crossFieldFilter']) {
20786
20948
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`;
20787
20949
  }
20788
- const queryRange = Object.entries(filters)
20789
- .filter(([, value]) => isDateRange(value))
20790
- .map(([searchField, dateRange]) => {
20791
- return {
20792
- searchField,
20793
- dateRange,
20794
- };
20795
- })[0];
20950
+ const queryRanges = Object.entries(filters).filter(([, value]) => isDateRange(value));
20796
20951
  const queryParts = [
20797
20952
  queryString && {
20798
20953
  query_string: {
20799
20954
  query: queryString,
20800
20955
  },
20801
20956
  },
20802
- queryRange &&
20803
- queryRange.dateRange && {
20804
- range: {
20805
- [queryRange.searchField]: {
20806
- ...(queryRange.dateRange.start && {
20807
- gte: formatDate(queryRange.dateRange.start),
20808
- }),
20809
- ...(queryRange.dateRange.end && {
20810
- lte: formatDate(queryRange.dateRange.end),
20811
- }),
20812
- format: 'yyyy-MM-dd',
20957
+ ...queryRanges.map(([searchField, dateRange]) => this.buildDateRangeQuery(searchField, dateRange)),
20958
+ spatialFilterExtent && {
20959
+ geo_shape: {
20960
+ geom: {
20961
+ shape: {
20962
+ type: 'envelope',
20963
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20964
+ coordinates: [
20965
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20966
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20967
+ ],
20968
+ },
20969
+ relation: 'intersects',
20813
20970
  },
20814
20971
  },
20815
20972
  },
@@ -20839,7 +20996,9 @@ class ElasticsearchService {
20839
20996
  },
20840
20997
  });
20841
20998
  }
20842
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20999
+ // a spatial extent filter takes precedence over the preference geometry for boosting
21000
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
21001
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20843
21002
  if (queryFilters) {
20844
21003
  filter.push(...queryFilters);
20845
21004
  }
@@ -20850,7 +21009,10 @@ class ElasticsearchService {
20850
21009
  },
20851
21010
  });
20852
21011
  }
20853
- if (geometry) {
21012
+ const boostGeometry = spatialFilterExtent
21013
+ ? bboxToPolygon(spatialFilterExtent)
21014
+ : geometry;
21015
+ if (boostGeometry) {
20854
21016
  // boosts applied using the filter geometry:
20855
21017
  // * records completely within the geometry receive a boost of 5
20856
21018
  // * records intersecting the geometry receive a boost of 2
@@ -20859,7 +21021,7 @@ class ElasticsearchService {
20859
21021
  should.push({
20860
21022
  geo_shape: {
20861
21023
  geom: {
20862
- shape: geometry,
21024
+ shape: boostGeometry,
20863
21025
  relation: 'within',
20864
21026
  },
20865
21027
  boost: 5.0,
@@ -20867,7 +21029,7 @@ class ElasticsearchService {
20867
21029
  }, {
20868
21030
  geo_shape: {
20869
21031
  geom: {
20870
- shape: geometry,
21032
+ shape: boostGeometry,
20871
21033
  relation: 'intersects',
20872
21034
  },
20873
21035
  boost: 2.0,
@@ -20876,7 +21038,7 @@ class ElasticsearchService {
20876
21038
  // this will boost the results variably depending on their distance from the given geometry
20877
21039
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20878
21040
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20879
- const bbox = getGeometryBoundingBox(geometry);
21041
+ const bbox = getGeometryBoundingBox(boostGeometry);
20880
21042
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20881
21043
  const northToCenter = new LineString([
20882
21044
  [center[0], bbox[3]],
@@ -21202,7 +21364,7 @@ class Gn4Repository {
21202
21364
  }
21203
21365
  getRecord(uniqueIdentifier) {
21204
21366
  return this.gn4SearchApi
21205
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21367
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21206
21368
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21207
21369
  }
21208
21370
  getMultipleRecords(uniqueIdentifiers) {
@@ -21256,19 +21418,28 @@ class Gn4Repository {
21256
21418
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21257
21419
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21258
21420
  }
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);
21421
+ getLinkedRecords(record) {
21422
+ const siblings = (record.extras?.['siblings'] ?? []);
21423
+ const relations = [
21424
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21425
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21426
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21427
+ [
21428
+ 'associated',
21429
+ (record.extras?.['associatedIdentifiers'] ?? []),
21430
+ ],
21431
+ ];
21432
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21433
+ if (requested.length === 0) {
21434
+ return of([]);
21270
21435
  }
21271
- return of(null);
21436
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21437
+ record,
21438
+ relation,
21439
+ associationType: relation === 'sibling'
21440
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21441
+ : undefined,
21442
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21272
21443
  }
21273
21444
  aggregate(params) {
21274
21445
  // if aggregations are empty, return an empty object right away
@@ -22457,48 +22628,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22457
22628
  type: Injectable
22458
22629
  }] });
22459
22630
 
22460
- const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider');
22461
- class GeocodingService {
22462
- constructor() {
22463
- this.provider = inject(GEOCODING_PROVIDER);
22464
- }
22465
- query(text) {
22466
- let queryObservable;
22467
- switch (this.provider[0]) {
22468
- case 'geoadmin':
22469
- queryObservable = from(queryGeoadmin(text, this.provider[1]));
22470
- break;
22471
- case 'geonames':
22472
- queryObservable = from(queryGeonames(text, this.provider[1]));
22473
- break;
22474
- case 'data-gouv-fr':
22475
- queryObservable = from(queryDataGouvFr(text, this.provider[1]));
22476
- break;
22477
- default:
22478
- return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
22479
- }
22480
- return queryObservable.pipe(catchError((error) => throwError(error)));
22481
- }
22482
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22483
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
22484
- }
22485
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
22486
- type: Injectable,
22487
- args: [{
22488
- providedIn: 'root',
22489
- }]
22490
- }] });
22491
-
22492
22631
  class FeatureMapModule {
22493
22632
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
22494
22633
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, imports: [i1.StoreFeatureModule] }); }
22495
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [
22496
- MapFacade,
22497
- {
22498
- provide: GEOCODING_PROVIDER,
22499
- useValue: ['geonames', { maxRows: 5 }],
22500
- },
22501
- ], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22634
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [MapFacade], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22502
22635
  }
22503
22636
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, decorators: [{
22504
22637
  type: NgModule,
@@ -22506,13 +22639,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22506
22639
  imports: [
22507
22640
  StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer),
22508
22641
  ],
22509
- providers: [
22510
- MapFacade,
22511
- {
22512
- provide: GEOCODING_PROVIDER,
22513
- useValue: ['geonames', { maxRows: 5 }],
22514
- },
22515
- ],
22642
+ providers: [MapFacade],
22516
22643
  }]
22517
22644
  }] });
22518
22645
 
@@ -22529,9 +22656,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22529
22656
  });
22530
22657
 
22531
22658
  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>`,
22659
+ type: 'maplibre-style',
22660
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22661
+ clickable: false,
22662
+ hoverable: false,
22535
22663
  };
22536
22664
  const DEFAULT_VIEW = {
22537
22665
  center: [0, 15],
@@ -23246,8 +23374,12 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23246
23374
  'search_preset',
23247
23375
  'advanced_filters',
23248
23376
  'limit',
23377
+ 'spatial_extent_max_file_size',
23378
+ 'geocoding_provider',
23379
+ 'geocoding_provider_options',
23249
23380
  ], warnings, errors);
23250
23381
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23382
+ const parsedCustomFilters = parseMultiConfigSection(parsed, 'custom_filter', ['name', 'base_filter'], ['exclude_values', 'include_values', 'label_key'], warnings, errors);
23251
23383
  searchConfig =
23252
23384
  parsedSearchSection === null
23253
23385
  ? null
@@ -23262,7 +23394,17 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23262
23394
  filters: param.filters,
23263
23395
  })),
23264
23396
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23397
+ CUSTOM_FILTERS: parsedCustomFilters.map((customFilter) => ({
23398
+ name: customFilter.name,
23399
+ baseFilter: customFilter.base_filter,
23400
+ excludeValues: customFilter.exclude_values,
23401
+ includeValues: customFilter.include_values,
23402
+ labelKey: customFilter.label_key,
23403
+ })),
23265
23404
  LIMIT: parsedSearchSection.limit,
23405
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23406
+ GEOCODING_PROVIDER: parsedSearchSection.geocoding_provider,
23407
+ GEOCODING_PROVIDER_OPTIONS: parsedSearchSection.geocoding_provider_options,
23266
23408
  };
23267
23409
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23268
23410
  metadataQualityConfig =
@@ -23371,7 +23513,22 @@ fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans"
23371
23513
  record_kind_quick_filter = false
23372
23514
  filter_geometry_url = 'https://my.domain.org/geom.json'
23373
23515
  do_not_use_default_search_preset = false
23374
- advanced_filters = ['publicationYear', 'documentStandard', 'inspireKeyword', 'topic', 'license']
23516
+ geocoding_provider = "geoplateforme"
23517
+ geocoding_provider_options.category = "administratif"
23518
+ geocoding_provider_options.limit = 5
23519
+ advanced_filters = ['publicationYear', 'documentStandard', 'inspireKeyword', 'topic', 'license', 'myOrg:firstCustomFilter', 'myOrg:secondCustomFilter']
23520
+
23521
+ [[custom_filter]]
23522
+ name = 'myOrg:firstCustomFilter'
23523
+ base_filter = 'keyword'
23524
+ exclude_values = ['my keyword 1', 'my keyword 2']
23525
+ label_key = 'myOrg.firstCustomFilter'
23526
+
23527
+ [[custom_filter]]
23528
+ name = 'myOrg:secondCustomFilter'
23529
+ base_filter = 'keyword'
23530
+ include_values = ['my keyword 3', 'my keyword 4']
23531
+ label_key = 'myOrg.secondCustomFilter'
23375
23532
 
23376
23533
  [[search_preset]]
23377
23534
  sort = "-createDate"
@@ -24320,7 +24477,7 @@ class AutocompleteComponent {
24320
24477
  this.enterBtnPosition = event.target['value'].length * 8 + 80;
24321
24478
  }
24322
24479
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24323
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24480
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithTemplate: "displayWithTemplate", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24324
24481
  provideIcons({
24325
24482
  iconoirSearch,
24326
24483
  matClose,
@@ -24329,7 +24486,7 @@ class AutocompleteComponent {
24329
24486
  provideNgIconsConfig({
24330
24487
  size: '1.75rem',
24331
24488
  }),
24332
- ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24489
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24333
24490
  }
24334
24491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, decorators: [{
24335
24492
  type: Component,
@@ -24350,7 +24507,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24350
24507
  provideNgIconsConfig({
24351
24508
  size: '1.75rem',
24352
24509
  }),
24353
- ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24510
+ ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24354
24511
  }], propDecorators: { placeholder: [{
24355
24512
  type: Input
24356
24513
  }], enterButton: [{
@@ -24388,6 +24545,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24388
24545
  }], inputRef: [{
24389
24546
  type: ViewChild,
24390
24547
  args: ['searchInput']
24548
+ }], displayWithTemplate: [{
24549
+ type: Input
24391
24550
  }], displayWithFn: [{
24392
24551
  type: Input
24393
24552
  }] } });
@@ -24520,12 +24679,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24520
24679
  type: Input
24521
24680
  }] } });
24522
24681
 
24682
+ function provideLocalizedDateAdapter() {
24683
+ return [
24684
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24685
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24686
+ {
24687
+ provide: DateAdapter,
24688
+ useFactory: () => {
24689
+ const dateService = inject(DateService);
24690
+ const adapter = new DateFnsAdapter();
24691
+ dateService
24692
+ .getDateFnsLocale()
24693
+ .then((locale) => adapter.setLocale(locale));
24694
+ return adapter;
24695
+ },
24696
+ },
24697
+ ];
24698
+ }
24699
+
24523
24700
  class DatePickerComponent {
24524
24701
  constructor() {
24525
- this.dateAdapter = inject(DateAdapter);
24526
- this.translate = inject(TranslateService);
24527
24702
  this.dateChange = new EventEmitter();
24528
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24529
24703
  }
24530
24704
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24531
24705
  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 +24707,19 @@ class DatePickerComponent {
24533
24707
  provideNgIconsConfig({
24534
24708
  size: '1.5rem',
24535
24709
  }),
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 }); }
24710
+ provideLocalizedDateAdapter(),
24711
+ ], 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
24712
  }
24542
24713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24543
24714
  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: [
24715
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24550
24716
  provideIcons({ iconoirCalendar }),
24551
24717
  provideNgIconsConfig({
24552
24718
  size: '1.5rem',
24553
24719
  }),
24554
- {
24555
- provide: MAT_DATE_LOCALE,
24556
- useFactory: (locale) => locale,
24557
- },
24720
+ provideLocalizedDateAdapter(),
24558
24721
  ], 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: [{
24722
+ }], propDecorators: { date: [{
24560
24723
  type: Input
24561
24724
  }], dateChange: [{
24562
24725
  type: Output
@@ -24564,58 +24727,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24564
24727
 
24565
24728
  class DateRangeDropdownComponent {
24566
24729
  constructor() {
24567
- this.overlayContainer = inject(OverlayContainer);
24730
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24731
+ this.dateAdapter = inject(DateAdapter);
24732
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24568
24733
  this.cdr = inject(ChangeDetectorRef);
24569
- this.startDateChange = new EventEmitter();
24570
- this.endDateChange = new EventEmitter();
24571
- this.isPickerDisplayed = false;
24734
+ this.dateRange = {};
24735
+ this.dateRangeChange = new EventEmitter();
24736
+ this.overlayPositions = [
24737
+ {
24738
+ originX: 'start',
24739
+ originY: 'bottom',
24740
+ overlayX: 'start',
24741
+ overlayY: 'top',
24742
+ offsetY: 8,
24743
+ },
24744
+ {
24745
+ originX: 'start',
24746
+ originY: 'top',
24747
+ overlayX: 'start',
24748
+ overlayY: 'bottom',
24749
+ offsetY: -8,
24750
+ },
24751
+ ];
24752
+ this.scrollStrategy = this.scrollStrategies.reposition();
24753
+ this.overlayOpen = false;
24754
+ this.expandedBound = 'start';
24755
+ this.invalidBounds = { start: false, end: false };
24756
+ // redraw when the lazily loaded UI locale reaches the adapter
24757
+ this.dateAdapter.localeChanges
24758
+ .pipe(takeUntilDestroyed())
24759
+ .subscribe(() => this.cdr.markForCheck());
24572
24760
  }
24573
- ngAfterViewChecked() {
24574
- this.checkPickerOverlay();
24761
+ get startDate() {
24762
+ return this.dateRange.start;
24575
24763
  }
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
24764
+ get endDate() {
24765
+ return this.dateRange.end;
24766
+ }
24767
+ get selectedDatesCount() {
24768
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24769
+ }
24770
+ openOverlay() {
24771
+ this.expandedBound = 'start';
24772
+ this.overlayOpen = true;
24773
+ }
24774
+ closeOverlay() {
24775
+ this.overlayOpen = false;
24776
+ }
24777
+ toggleBound(bound) {
24778
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24779
+ }
24780
+ formatDate(date) {
24781
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24782
+ }
24783
+ onDateInput(bound, event) {
24784
+ const typedText = event.target.value.trim();
24785
+ const date = typedText
24786
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24787
+ : null;
24788
+ if (!this.isAcceptable(bound, typedText, date)) {
24789
+ // keep the text as typed and flag it; the filter stays on its last value
24790
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24791
+ return;
24792
+ }
24793
+ if (bound === 'start')
24794
+ this.setStartDate(date);
24795
+ else
24796
+ this.setEndDate(date);
24797
+ }
24798
+ normalizeDateInput(bound, event) {
24799
+ if (this.invalidBounds[bound])
24800
+ return;
24801
+ const date = bound === 'start' ? this.startDate : this.endDate;
24802
+ const input = event.target;
24803
+ input.value = date ? this.formatDate(date) : '';
24804
+ }
24805
+ isAcceptable(bound, typedText, date) {
24806
+ if (!typedText)
24807
+ return true;
24808
+ if (!date || !this.dateAdapter.isValid(date))
24809
+ return false;
24810
+ return bound === 'start'
24811
+ ? !this.endDate || date <= this.endDate
24812
+ : !this.startDate || date >= this.startDate;
24813
+ }
24814
+ setStartDate(date) {
24815
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24816
+ this.applyDateRange({ ...this.dateRange, start: date });
24817
+ this.expandedBound = 'end';
24818
+ }
24819
+ setEndDate(date) {
24820
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24821
+ this.applyDateRange({ ...this.dateRange, end: date });
24822
+ this.expandedBound = null;
24823
+ }
24824
+ clearDates(event) {
24825
+ this.invalidBounds = { start: false, end: false };
24826
+ this.expandedBound = 'start';
24827
+ this.applyDateRange({});
24828
+ propagateToDocumentOnly(event);
24829
+ }
24830
+ applyDateRange(dateRange) {
24831
+ this.dateRange = {
24832
+ ...(dateRange.start && { start: dateRange.start }),
24833
+ ...(dateRange.end && { end: dateRange.end }),
24834
+ };
24835
+ this.dateRangeChange.emit(this.dateRange);
24584
24836
  }
24585
24837
  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: [
24838
+ 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
24839
  provideIcons({
24588
- matExpandMore,
24840
+ iconoirCalendar,
24841
+ matClose,
24589
24842
  matExpandLess,
24843
+ matExpandMore,
24844
+ }),
24845
+ provideNgIconsConfig({
24846
+ size: '1.5rem',
24590
24847
  }),
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"] }] }); }
24848
+ provideLocalizedDateAdapter(),
24849
+ ], 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
24850
  }
24593
24851
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24594
24852
  type: Component,
24595
24853
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24596
- NgIconComponent,
24597
- MatNativeDateModule,
24598
- MatDatepickerModule,
24854
+ AutofocusDirective,
24599
24855
  ButtonComponent,
24856
+ MatDatepickerModule,
24857
+ NgIcon,
24858
+ NgTemplateOutlet,
24859
+ OverlayModule,
24860
+ TranslateDirective,
24861
+ TranslatePipe,
24600
24862
  ], providers: [
24601
24863
  provideIcons({
24602
- matExpandMore,
24864
+ iconoirCalendar,
24865
+ matClose,
24603
24866
  matExpandLess,
24867
+ matExpandMore,
24604
24868
  }),
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: [{
24869
+ provideNgIconsConfig({
24870
+ size: '1.5rem',
24871
+ }),
24872
+ provideLocalizedDateAdapter(),
24873
+ ], 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"] }]
24874
+ }], ctorParameters: () => [], propDecorators: { title: [{
24609
24875
  type: Input
24610
- }], endDate: [{
24876
+ }], dateRange: [{
24611
24877
  type: Input
24612
- }], startDateChange: [{
24878
+ }], dateRangeChange: [{
24613
24879
  type: Output
24614
- }], endDateChange: [{
24615
- type: Output
24616
- }], picker: [{
24880
+ }], overlayOrigin: [{
24617
24881
  type: ViewChild,
24618
- args: ['picker']
24882
+ args: ['overlayOrigin']
24619
24883
  }] } });
24620
24884
 
24621
24885
  class DateRangePickerComponent {
@@ -24629,20 +24893,17 @@ class DateRangePickerComponent {
24629
24893
  provideNgIconsConfig({
24630
24894
  size: '1.5rem',
24631
24895
  }),
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 }); }
24896
+ provideLocalizedDateAdapter(),
24897
+ ], 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
24898
  }
24634
24899
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24635
24900
  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: [
24901
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24642
24902
  provideIcons({ iconoirCalendar }),
24643
24903
  provideNgIconsConfig({
24644
24904
  size: '1.5rem',
24645
24905
  }),
24906
+ provideLocalizedDateAdapter(),
24646
24907
  ], 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
24908
  }], propDecorators: { startDate: [{
24648
24909
  type: Input
@@ -24660,28 +24921,68 @@ class DragAndDropFileInputComponent {
24660
24921
  constructor() {
24661
24922
  this.placeholder = placeholder;
24662
24923
  this.accept = '*';
24924
+ this.maxFileSizeMb = null;
24925
+ this.icon = null;
24926
+ this.dropzoneBackgroundColor = null;
24927
+ this.textClass = '';
24928
+ this.extraClass = '';
24929
+ this.showFileName = true;
24663
24930
  this.fileChange = new EventEmitter();
24931
+ this.errorChange = new EventEmitter();
24664
24932
  this.selectedFile = null;
24665
24933
  }
24666
24934
  get fileName() {
24667
24935
  return this.selectedFile && this.selectedFile.name;
24668
24936
  }
24937
+ get maxFileSizeBytes() {
24938
+ return typeof this.maxFileSizeMb === 'number'
24939
+ ? megabytesToBytes(this.maxFileSizeMb)
24940
+ : null;
24941
+ }
24669
24942
  selectFile(event) {
24943
+ if (event.rejectedFiles?.length) {
24944
+ const reason = event.rejectedFiles[0].reason;
24945
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24946
+ return;
24947
+ }
24670
24948
  this.selectedFile = event.addedFiles[0];
24671
24949
  this.fileChange.emit(this.selectedFile);
24672
24950
  }
24951
+ openFileSelector() {
24952
+ this.dropzone.showFileSelector();
24953
+ }
24954
+ clear() {
24955
+ this.selectedFile = null;
24956
+ }
24673
24957
  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"] }] }); }
24958
+ 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
24959
  }
24676
24960
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24677
24961
  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"] }]
24962
+ 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
24963
  }], propDecorators: { placeholder: [{
24680
24964
  type: Input
24681
24965
  }], accept: [{
24682
24966
  type: Input
24967
+ }], maxFileSizeMb: [{
24968
+ type: Input
24969
+ }], icon: [{
24970
+ type: Input
24971
+ }], dropzoneBackgroundColor: [{
24972
+ type: Input
24973
+ }], textClass: [{
24974
+ type: Input
24975
+ }], extraClass: [{
24976
+ type: Input
24977
+ }], showFileName: [{
24978
+ type: Input
24683
24979
  }], fileChange: [{
24684
24980
  type: Output
24981
+ }], errorChange: [{
24982
+ type: Output
24983
+ }], dropzone: [{
24984
+ type: ViewChild,
24985
+ args: [NgxDropzoneComponent]
24685
24986
  }] } });
24686
24987
 
24687
24988
  class DropdownMultiselectComponent {
@@ -24730,7 +25031,7 @@ class DropdownMultiselectComponent {
24730
25031
  }
24731
25032
  setFocus() {
24732
25033
  setTimeout(() => {
24733
- this.searchFieldInput.nativeElement.focus();
25034
+ this.searchFieldInput?.nativeElement.focus();
24734
25035
  }, 0);
24735
25036
  }
24736
25037
  openOverlay() {
@@ -24836,7 +25137,10 @@ class DropdownMultiselectComponent {
24836
25137
  matExpandMore,
24837
25138
  matExpandLess,
24838
25139
  }),
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 }); }
25140
+ provideNgIconsConfig({
25141
+ size: '1.5rem',
25142
+ }),
25143
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-1 flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24840
25144
  }
24841
25145
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24842
25146
  type: Component,
@@ -24846,7 +25150,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24846
25150
  matExpandMore,
24847
25151
  matExpandLess,
24848
25152
  }),
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" }]
25153
+ provideNgIconsConfig({
25154
+ size: '1.5rem',
25155
+ }),
25156
+ ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-1 flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
24850
25157
  }], propDecorators: { title: [{
24851
25158
  type: Input
24852
25159
  }], choices: [{
@@ -25018,7 +25325,7 @@ class DropdownSelectorComponent {
25018
25325
  provideNgIconsConfig({
25019
25326
  size: '1.5em',
25020
25327
  }),
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 }); }
25328
+ ], 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
25329
  }
25023
25330
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25024
25331
  type: Component,
@@ -25758,6 +26065,180 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25758
26065
  type: Output
25759
26066
  }] } });
25760
26067
 
26068
+ marker('search.filters.spatialExtent.import');
26069
+ marker('search.filters.spatialExtent.helpText');
26070
+ marker('search.filters.spatialExtent.error.title');
26071
+ marker('search.filters.spatialExtent.error.outOfBounds');
26072
+ const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile');
26073
+ const LABEL_FROM_FILE_DELETE = marker('search.filters.spatialExtent.bboxFromFileDelete');
26074
+ const LABEL_INITIAL = marker('search.filters.spatialExtent.bboxInitial');
26075
+ const LABEL_INITIAL_DELETE = marker('search.filters.spatialExtent.bboxInitialDelete');
26076
+ class SpatialExtentDropdownComponent {
26077
+ constructor() {
26078
+ this.cd = inject(ChangeDetectorRef);
26079
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26080
+ this.maxFileSizeMb = null;
26081
+ this.bboxChange = new EventEmitter();
26082
+ this.errorChange = new EventEmitter();
26083
+ this.bbox = null;
26084
+ this.fileName = '';
26085
+ this.overlayPositions = [
26086
+ {
26087
+ originX: 'start',
26088
+ originY: 'bottom',
26089
+ overlayX: 'start',
26090
+ overlayY: 'top',
26091
+ offsetY: 8,
26092
+ },
26093
+ {
26094
+ originX: 'start',
26095
+ originY: 'top',
26096
+ overlayX: 'start',
26097
+ overlayY: 'bottom',
26098
+ offsetY: -8,
26099
+ },
26100
+ ];
26101
+ this.scrollStrategy = this.scrollStrategies.reposition();
26102
+ this.overlayOpen = false;
26103
+ this.overlayMinWidth = 'none';
26104
+ this.errorKey = null;
26105
+ }
26106
+ set initialBbox(value) {
26107
+ if (!this.bbox && value) {
26108
+ this.bbox = value;
26109
+ }
26110
+ }
26111
+ get hasSelection() {
26112
+ return !!this.bbox;
26113
+ }
26114
+ get selectionLabelKey() {
26115
+ return this.fileName ? LABEL_FROM_FILE : LABEL_INITIAL;
26116
+ }
26117
+ get selectionDeleteLabelKey() {
26118
+ return this.fileName ? LABEL_FROM_FILE_DELETE : LABEL_INITIAL_DELETE;
26119
+ }
26120
+ get selectionLabelParams() {
26121
+ return { fileName: this.fileName };
26122
+ }
26123
+ openOverlay() {
26124
+ this.overlayMinWidth =
26125
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26126
+ .width + 'px';
26127
+ this.overlayOpen = true;
26128
+ }
26129
+ closeOverlay() {
26130
+ this.overlayOpen = false;
26131
+ }
26132
+ toggleOverlay() {
26133
+ if (this.overlayOpen) {
26134
+ this.closeOverlay();
26135
+ }
26136
+ else {
26137
+ this.openOverlay();
26138
+ }
26139
+ }
26140
+ async handleFileSelected(file) {
26141
+ this.errorKey = null;
26142
+ let content;
26143
+ try {
26144
+ content = await readFileAsText(file);
26145
+ const parsed = JSON.parse(content);
26146
+ const geometry = getGeometryFromGeoJSON(parsed);
26147
+ if (!geometry) {
26148
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26149
+ return;
26150
+ }
26151
+ const bbox = getGeometryBoundingBox(geometry);
26152
+ if (!isBoundingBoxWithinWorldExtent(bbox)) {
26153
+ this.setError(marker('search.filters.spatialExtent.error.outOfBounds'));
26154
+ return;
26155
+ }
26156
+ this.bbox = bbox;
26157
+ this.fileName = file.name;
26158
+ this.bboxChange.emit(bbox);
26159
+ this.cd.markForCheck();
26160
+ }
26161
+ catch {
26162
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26163
+ return;
26164
+ }
26165
+ }
26166
+ handleFileError(error) {
26167
+ if (error === 'file-too-large') {
26168
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26169
+ maxSize: this.maxFileSizeMb,
26170
+ });
26171
+ }
26172
+ else {
26173
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26174
+ }
26175
+ }
26176
+ setError(errorKey, params) {
26177
+ this.errorKey = errorKey;
26178
+ this.errorChange.emit({ key: errorKey, params });
26179
+ this.cd.markForCheck();
26180
+ }
26181
+ removeSelection(event) {
26182
+ this.bbox = null;
26183
+ this.fileName = '';
26184
+ this.errorKey = null;
26185
+ this.fileInput?.clear();
26186
+ this.bboxChange.emit(null);
26187
+ propagateToDocumentOnly(event);
26188
+ }
26189
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26190
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SpatialExtentDropdownComponent, isStandalone: true, selector: "gn-ui-spatial-extent-dropdown", inputs: { title: "title", maxFileSizeMb: "maxFileSizeMb", initialBbox: "initialBbox" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26191
+ provideIcons({
26192
+ iconoirCheckCircle,
26193
+ iconoirSquareDashed,
26194
+ iconoirTrash,
26195
+ iconoirUpload,
26196
+ matClose,
26197
+ matExpandLess,
26198
+ matExpandMore,
26199
+ }),
26200
+ ], 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-[12px]') + ' 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=\"iconoirUpload\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26201
+ }
26202
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26203
+ type: Component,
26204
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26205
+ ButtonComponent,
26206
+ NgIcon,
26207
+ OverlayModule,
26208
+ TranslatePipe,
26209
+ DragAndDropFileInputComponent,
26210
+ ], providers: [
26211
+ provideIcons({
26212
+ iconoirCheckCircle,
26213
+ iconoirSquareDashed,
26214
+ iconoirTrash,
26215
+ iconoirUpload,
26216
+ matClose,
26217
+ matExpandLess,
26218
+ matExpandMore,
26219
+ }),
26220
+ ], 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-[12px]') + ' 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=\"iconoirUpload\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n" }]
26221
+ }], propDecorators: { title: [{
26222
+ type: Input
26223
+ }], maxFileSizeMb: [{
26224
+ type: Input
26225
+ }], initialBbox: [{
26226
+ type: Input
26227
+ }], bboxChange: [{
26228
+ type: Output
26229
+ }], errorChange: [{
26230
+ type: Output
26231
+ }], overlayOrigin: [{
26232
+ type: ViewChild,
26233
+ args: ['overlayOrigin']
26234
+ }], overlay: [{
26235
+ type: ViewChild,
26236
+ args: [CdkConnectedOverlay]
26237
+ }], fileInput: [{
26238
+ type: ViewChild,
26239
+ args: [DragAndDropFileInputComponent]
26240
+ }] } });
26241
+
25761
26242
  class CellPopinComponent {
25762
26243
  constructor() {
25763
26244
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25834,7 +26315,7 @@ class CellPopinComponent {
25834
26315
  this.isOpen = false;
25835
26316
  }
25836
26317
  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"] }] }); }
26318
+ 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
26319
  }
25839
26320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25840
26321
  type: Component,
@@ -26262,11 +26743,11 @@ class BlockListComponent {
26262
26743
  this.goToPage(this.currentPage + 1);
26263
26744
  }
26264
26745
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: BlockListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26265
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: BlockListComponent, isStandalone: true, selector: "gn-ui-block-list", inputs: { containerClass: "containerClass", paginationContainerClass: "paginationContainerClass" }, outputs: { listChanges: "listChanges" }, host: { listeners: { "window:resize": "onResize()" } }, queries: [{ propertyName: "blocks", predicate: ["block"], read: ElementRef }], viewQueries: [{ propertyName: "blockContainer", first: true, predicate: ["blockContainer"], descendants: true }], ngImport: i0, template: "<div\n class=\"grid gap-4 w-full sm:grid-cols-2\"\n #blockContainer\n [ngClass]=\"[containerClass, 'size-' + subComponentSize]\"\n>\n <ng-content></ng-content>\n</div>\n<gn-ui-pagination-dots\n [containerClass]=\"'absolute ' + paginationContainerClass\"\n [listComponent]=\"this\"\n></gn-ui-pagination-dots>\n", styles: [":host .block-list-container ::ng-deep>*{flex-shrink:0}:host{position:relative}@media screen and (min-width: 769px){.size-L{grid-template-columns:repeat(1,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-M{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-S{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-XS{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PaginationDotsComponent, selector: "gn-ui-pagination-dots", inputs: ["listComponent", "containerClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26746
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: BlockListComponent, isStandalone: true, selector: "gn-ui-block-list", inputs: { containerClass: "containerClass", paginationContainerClass: "paginationContainerClass" }, outputs: { listChanges: "listChanges" }, host: { listeners: { "window:resize": "onResize()" } }, queries: [{ propertyName: "blocks", predicate: ["block"], read: ElementRef }], viewQueries: [{ propertyName: "blockContainer", first: true, predicate: ["blockContainer"], descendants: true }], ngImport: i0, template: "<div\n class=\"grid gap-4 w-full sm:grid-cols-2\"\n #blockContainer\n [ngClass]=\"[containerClass, 'size-' + subComponentSize]\"\n>\n <ng-content></ng-content>\n</div>\n<gn-ui-pagination-dots\n [containerClass]=\"'absolute ' + paginationContainerClass\"\n [listComponent]=\"this\"\n></gn-ui-pagination-dots>\n", styles: [":host .block-list-container ::ng-deep>*{flex-shrink:0}:host{display:block;position:relative}@media screen and (min-width: 769px){.size-L{grid-template-columns:repeat(1,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-M{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-S{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-XS{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PaginationDotsComponent, selector: "gn-ui-pagination-dots", inputs: ["listComponent", "containerClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26266
26747
  }
26267
26748
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: BlockListComponent, decorators: [{
26268
26749
  type: Component,
26269
- args: [{ selector: 'gn-ui-block-list', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, PaginationDotsComponent], template: "<div\n class=\"grid gap-4 w-full sm:grid-cols-2\"\n #blockContainer\n [ngClass]=\"[containerClass, 'size-' + subComponentSize]\"\n>\n <ng-content></ng-content>\n</div>\n<gn-ui-pagination-dots\n [containerClass]=\"'absolute ' + paginationContainerClass\"\n [listComponent]=\"this\"\n></gn-ui-pagination-dots>\n", styles: [":host .block-list-container ::ng-deep>*{flex-shrink:0}:host{position:relative}@media screen and (min-width: 769px){.size-L{grid-template-columns:repeat(1,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-M{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-S{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-XS{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}}\n"] }]
26750
+ args: [{ selector: 'gn-ui-block-list', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, PaginationDotsComponent], template: "<div\n class=\"grid gap-4 w-full sm:grid-cols-2\"\n #blockContainer\n [ngClass]=\"[containerClass, 'size-' + subComponentSize]\"\n>\n <ng-content></ng-content>\n</div>\n<gn-ui-pagination-dots\n [containerClass]=\"'absolute ' + paginationContainerClass\"\n [listComponent]=\"this\"\n></gn-ui-pagination-dots>\n", styles: [":host .block-list-container ::ng-deep>*{flex-shrink:0}:host{display:block;position:relative}@media screen and (min-width: 769px){.size-L{grid-template-columns:repeat(1,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-M{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-S{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}.size-XS{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,min-content)}}\n"] }]
26270
26751
  }], propDecorators: { containerClass: [{
26271
26752
  type: Input
26272
26753
  }], paginationContainerClass: [{
@@ -27456,6 +27937,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
27456
27937
  }]
27457
27938
  }] });
27458
27939
 
27940
+ const DEFAULT_GEOCODING_PROVIDER = [
27941
+ 'geonames',
27942
+ { maxRows: 5 },
27943
+ ];
27944
+ const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider', {
27945
+ providedIn: 'root',
27946
+ factory: () => DEFAULT_GEOCODING_PROVIDER,
27947
+ });
27948
+ class GeocodingService {
27949
+ constructor() {
27950
+ this.provider = inject(GEOCODING_PROVIDER);
27951
+ }
27952
+ query(text) {
27953
+ let queryObservable;
27954
+ switch (this.provider[0]) {
27955
+ case 'geoadmin':
27956
+ queryObservable = from(queryGeoadmin(text, this.provider[1]));
27957
+ break;
27958
+ case 'geonames':
27959
+ queryObservable = from(queryGeonames(text, this.provider[1]));
27960
+ break;
27961
+ case 'geoplateforme':
27962
+ queryObservable = from(queryGeoplateforme(text, this.provider[1]));
27963
+ break;
27964
+ default:
27965
+ return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
27966
+ }
27967
+ return queryObservable.pipe(catchError((error) => throwError(error)));
27968
+ }
27969
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
27970
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
27971
+ }
27972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
27973
+ type: Injectable,
27974
+ args: [{
27975
+ providedIn: 'root',
27976
+ }]
27977
+ }] });
27978
+
27979
+ class LocationSearchComponent {
27980
+ constructor() {
27981
+ this.geocodingService = inject(GeocodingService);
27982
+ this.placeholder = '';
27983
+ this.resultSelected = new EventEmitter();
27984
+ this.displayWithFn = (item) => item.label;
27985
+ this.searchAction = (text) => this.geocodingService.query(text);
27986
+ }
27987
+ handleItemSelected(item) {
27988
+ this.resultSelected.emit(item);
27989
+ }
27990
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
27991
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: LocationSearchComponent, isStandalone: true, selector: "gn-ui-location-search", inputs: { placeholder: "placeholder", displayWithTemplate: "displayWithTemplate" }, outputs: { resultSelected: "resultSelected" }, ngImport: i0, template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
27992
+ }
27993
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, decorators: [{
27994
+ type: Component,
27995
+ args: [{ selector: 'gn-ui-location-search', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [AutocompleteComponent], template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n" }]
27996
+ }], propDecorators: { placeholder: [{
27997
+ type: Input
27998
+ }], displayWithTemplate: [{
27999
+ type: Input
28000
+ }], resultSelected: [{
28001
+ type: Output
28002
+ }] } });
28003
+
27459
28004
  // expects the replacement key ${uuid}
27460
28005
  const RECORD_DATASET_URL_TOKEN = new InjectionToken('record-dataset-url-token');
27461
28006
  const RECORD_SERVICE_URL_TOKEN = new InjectionToken('record-service-url-token');
@@ -27924,6 +28469,45 @@ class DateRangeSearchField extends SimpleSearchField {
27924
28469
  return 'dateRange';
27925
28470
  }
27926
28471
  }
28472
+ // treats a resource's creation/revision dates as an interval (its min and max), so a record matches
28473
+ // when that interval intersects the filter range, not just when a single date falls inside it
28474
+ class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
28475
+ constructor(injector, order = 'desc') {
28476
+ super('resourceCreationRevisionDate', injector, order);
28477
+ const dateSourceFields = [
28478
+ 'creationDateForResource',
28479
+ 'revisionDateForResource',
28480
+ ];
28481
+ const collectSortedDates = dateSourceFields
28482
+ .map((field) => `if (doc.containsKey('${field}')) {
28483
+ for (def date : doc['${field}']) { dates.add(date.millis); }
28484
+ }`)
28485
+ .join('\n');
28486
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMin', `def dates = [];
28487
+ ${collectSortedDates}
28488
+ if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`, 'date');
28489
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMax', `def dates = [];
28490
+ ${collectSortedDates}
28491
+ if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`, 'date');
28492
+ }
28493
+ }
28494
+ class BoundingBoxSearchField extends SimpleSearchField {
28495
+ getAvailableValues() {
28496
+ return of([]);
28497
+ }
28498
+ getFiltersForValues(values) {
28499
+ return of({
28500
+ [this.esFieldName]: values.map(Number),
28501
+ });
28502
+ }
28503
+ getValuesForFilter(filters) {
28504
+ const filter = filters[this.esFieldName];
28505
+ return of(isBoundingBox(filter) ? filter : []);
28506
+ }
28507
+ getType() {
28508
+ return 'spatialExtent';
28509
+ }
28510
+ }
27927
28511
  marker('search.filters.availableServices.view');
27928
28512
  marker('search.filters.availableServices.download');
27929
28513
  class AvailableServicesField extends SimpleSearchField {
@@ -28068,6 +28652,8 @@ marker('search.filters.producerOrg');
28068
28652
  marker('search.filters.publisherOrg');
28069
28653
  marker('search.filters.user');
28070
28654
  marker('search.filters.changeDate');
28655
+ marker('search.filters.resourceCreationRevisionDate');
28656
+ marker('search.filters.spatialExtent');
28071
28657
  class FieldsService {
28072
28658
  constructor() {
28073
28659
  this.injector = inject(Injector);
@@ -28090,7 +28676,9 @@ class FieldsService {
28090
28676
  publisherOrg: new MultilingualSearchField('distributorOrgForResourceObject', this.injector, 'asc', 'key'),
28091
28677
  user: new UserSearchField(this.injector),
28092
28678
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28679
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(this.injector, 'desc'),
28093
28680
  availableServices: new AvailableServicesField(this.injector),
28681
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28094
28682
  };
28095
28683
  }
28096
28684
  get supportedFields() {
@@ -29353,7 +29941,7 @@ class ContactPillComponent {
29353
29941
  this.overlayOpen = false;
29354
29942
  }
29355
29943
  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 }); }
29944
+ 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
29945
  }
29358
29946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29359
29947
  type: Component,
@@ -30079,7 +30667,7 @@ class ServiceCapabilitiesComponent {
30079
30667
  if (this.apiLinks.length > 0 &&
30080
30668
  this.apiLinks[0].accessServiceProtocol !== 'ogcFeatures') {
30081
30669
  this.loading = true;
30082
- this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol);
30670
+ this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol, true);
30083
30671
  this.loading = false;
30084
30672
  this.cdr.detectChanges();
30085
30673
  this.filteredLayers = this.availableLayers;
@@ -31275,19 +31863,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31275
31863
  type: Output
31276
31864
  }] } });
31277
31865
 
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
+ setTimeout(() => {
31876
+ this.removeNotificationById(id);
31877
+ }, timeoutMs);
31878
+ }
31879
+ return id;
31880
+ }
31881
+ removeNotificationById(id) {
31882
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31883
+ }
31884
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31885
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31886
+ }
31887
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31888
+ type: Injectable,
31889
+ args: [{
31890
+ providedIn: 'root',
31891
+ }]
31892
+ }] });
31893
+
31894
+ class NotificationsContainerComponent {
31895
+ constructor() {
31896
+ this.notificationsService = inject(NotificationsService);
31897
+ }
31898
+ trackById(index, notification) {
31899
+ return notification.id;
31900
+ }
31901
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31902
+ 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: [
31903
+ trigger('enterExit', [
31904
+ transition(':enter', [
31905
+ animate('150ms', keyframes([
31906
+ style({ transform: 'scale(1)', opacity: 0 }),
31907
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31908
+ style({ transform: 'scale(1)', opacity: 1 }),
31909
+ ])),
31910
+ ]),
31911
+ transition(':leave', [
31912
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31913
+ ]),
31914
+ ]),
31915
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31916
+ }
31917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31918
+ type: Component,
31919
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31920
+ trigger('enterExit', [
31921
+ transition(':enter', [
31922
+ animate('150ms', keyframes([
31923
+ style({ transform: 'scale(1)', opacity: 0 }),
31924
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31925
+ style({ transform: 'scale(1)', opacity: 1 }),
31926
+ ])),
31927
+ ]),
31928
+ transition(':leave', [
31929
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31930
+ ]),
31931
+ ]),
31932
+ ], 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" }]
31933
+ }] });
31934
+
31278
31935
  class FilterDropdownComponent {
31279
31936
  constructor() {
31280
31937
  this.searchFacade = inject(SearchFacade);
31281
31938
  this.searchService = inject(SearchService);
31282
31939
  this.fieldsService = inject(FieldsService);
31940
+ this.notificationsService = inject(NotificationsService);
31941
+ this.translateService = inject(TranslateService);
31942
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31283
31943
  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));
31944
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31945
+ this.selectedBoundingBox$ = this.selected$.pipe(map$1((selected) => Array.isArray(selected) && selected.length > 0
31946
+ ? selected
31947
+ : null));
31948
+ this.spatialExtentErrorNotificationId = null;
31285
31949
  }
31286
31950
  onSelectedValues(values) {
31287
31951
  this.fieldsService
31288
31952
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31289
31953
  .subscribe((filters) => this.searchService.updateFilters(filters));
31290
31954
  }
31955
+ onBboxChange(bbox) {
31956
+ this.fieldsService
31957
+ .buildFiltersFromFieldValues({
31958
+ [this.fieldName]: bbox,
31959
+ })
31960
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31961
+ this.clearSpatialExtentErrorNotification();
31962
+ }
31963
+ onSpatialExtentError(error) {
31964
+ this.clearSpatialExtentErrorNotification();
31965
+ this.spatialExtentErrorNotificationId =
31966
+ this.notificationsService.showNotification({
31967
+ type: 'error',
31968
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31969
+ text: this.translateService.instant(error.key, error.params),
31970
+ });
31971
+ }
31972
+ clearSpatialExtentErrorNotification() {
31973
+ if (this.spatialExtentErrorNotificationId === null)
31974
+ return;
31975
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31976
+ this.spatialExtentErrorNotificationId = null;
31977
+ }
31291
31978
  ngOnInit() {
31292
31979
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31293
31980
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31295,27 +31982,15 @@ class FilterDropdownComponent {
31295
31982
  value: v.value.toString(), // converting to string for the dropdown
31296
31983
  }))), catchError(() => of([])));
31297
31984
  }
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
- }
31985
+ onDateRangeChange(dateRange) {
31986
+ this.fieldsService
31987
+ .buildFiltersFromFieldValues({
31988
+ [this.fieldName]: dateRange,
31989
+ })
31990
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31316
31991
  }
31317
31992
  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 }); }
31993
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb", "initialBbox"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31319
31994
  }
31320
31995
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31321
31996
  type: Component,
@@ -31323,7 +31998,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31323
31998
  CommonModule,
31324
31999
  DateRangeDropdownComponent,
31325
32000
  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" }]
32001
+ SpatialExtentDropdownComponent,
32002
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31327
32003
  }], propDecorators: { fieldName: [{
31328
32004
  type: Input
31329
32005
  }], title: [{
@@ -31384,7 +32060,7 @@ class FuzzySearchComponent {
31384
32060
  this.isSearchActive.emit(event);
31385
32061
  }
31386
32062
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31387
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
32063
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31388
32064
  }
31389
32065
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, decorators: [{
31390
32066
  type: Component,
@@ -31793,11 +32469,7 @@ const SortByEnum = {
31793
32469
  RELEVANCY: ['desc', '_score'],
31794
32470
  QUALITY_SCORE: ['desc', 'qualityScore'],
31795
32471
  CHANGE_DATE: ['desc', 'changeDate'],
31796
- RESOURCE_DATES: [
31797
- ['desc', 'revisionDateForResource'],
31798
- ['desc', 'publicationDateForResource'],
31799
- ['desc', 'creationDateForResource'],
31800
- ],
32472
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
31801
32473
  };
31802
32474
 
31803
32475
  class SortByComponent {
@@ -31811,7 +32483,7 @@ class SortByComponent {
31811
32483
  },
31812
32484
  {
31813
32485
  label: marker('results.sortBy.dateStamp'),
31814
- value: SortByEnum.RESOURCE_DATES,
32486
+ value: SortByEnum.RESOURCE_DATE,
31815
32487
  },
31816
32488
  {
31817
32489
  label: marker('results.sortBy.popularity'),
@@ -31863,74 +32535,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31863
32535
  args: ['gnUiSearchStateContainer']
31864
32536
  }] } });
31865
32537
 
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
32538
  class ResultsTableContainerComponent {
31935
32539
  constructor() {
31936
32540
  this.searchFacade = inject(SearchFacade);
@@ -32015,12 +32619,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32015
32619
 
32016
32620
  marker('search.filters.summaryLabel.user');
32017
32621
  marker('search.filters.summaryLabel.changeDate');
32622
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate');
32623
+ const OPEN_BOUND = '…';
32018
32624
  class SearchFiltersSummaryItemComponent {
32019
32625
  constructor() {
32020
32626
  this.searchFacade = inject(SearchFacade);
32021
32627
  this.searchService = inject(SearchService);
32022
32628
  this.fieldsService = inject(FieldsService);
32023
- this.datePipe = inject(DatePipe);
32629
+ this.dateService = inject(DateService);
32024
32630
  this.translate = inject(TranslateService);
32025
32631
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32026
32632
  ? fieldValues[this.fieldName]
@@ -32047,9 +32653,10 @@ class SearchFiltersSummaryItemComponent {
32047
32653
  getReadableValues(fieldValues) {
32048
32654
  return fieldValues.map((value) => {
32049
32655
  if (this.fieldType === 'dateRange') {
32656
+ const { start, end } = value;
32050
32657
  return {
32051
32658
  value,
32052
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32659
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32053
32660
  };
32054
32661
  }
32055
32662
  else if (this.fieldName === 'user') {
@@ -32060,6 +32667,15 @@ class SearchFiltersSummaryItemComponent {
32060
32667
  }
32061
32668
  });
32062
32669
  }
32670
+ formatBound(date) {
32671
+ return date
32672
+ ? this.dateService.formatDate(date, {
32673
+ day: '2-digit',
32674
+ month: '2-digit',
32675
+ year: 'numeric',
32676
+ })
32677
+ : OPEN_BOUND;
32678
+ }
32063
32679
  async removeFilterValue(fieldValue) {
32064
32680
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32065
32681
  const updatedFieldValues = currentFieldValues
@@ -32072,11 +32688,11 @@ class SearchFiltersSummaryItemComponent {
32072
32688
  .subscribe((filters) => this.searchService.updateFilters(filters));
32073
32689
  }
32074
32690
  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" }] }); }
32691
+ 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
32692
  }
32077
32693
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32078
32694
  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" }]
32695
+ 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
32696
  }], propDecorators: { fieldName: [{
32081
32697
  type: Input
32082
32698
  }] } });
@@ -32320,11 +32936,11 @@ class AddLayerFromFileComponent {
32320
32936
  }, 5000);
32321
32937
  }
32322
32938
  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" }] }); }
32939
+ 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
32940
  }
32325
32941
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32326
32942
  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" }]
32943
+ 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
32944
  }] });
32329
32945
 
32330
32946
  class LayersPanelComponent {
@@ -32385,95 +33001,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32385
33001
  ], template: "<div\n class=\"bg-white rounded shadow shadow-lg relative h-full w-[400px] overflow-hidden\"\n>\n <div class=\"p-3 border-b border-gray-300 flex items-center\">\n <ng-icon class=\"mr-2\" name=\"matLayersOutline\"></ng-icon>\n <span translate>map.layers.list</span>\n </div>\n <div class=\"flex flex-col px-4 divide-y divide-y-gray-50\">\n @for (layer of layers$ | async; track layer; let index = $index) {\n <div class=\"flex flex-row py-3\">\n <ng-icon class=\"-ml-2 mr-2 shrink-0\" name=\"matChevronRight\"></ng-icon>\n <span class=\"mr-2 grow\">{{ layer.label }}</span>\n <a\n href\n class=\"underline text-sm text-red-700 hover:text-red-900\"\n (click)=\"deleteLayer(index); $event.preventDefault()\"\n >\n delete\n </a>\n </div>\n }\n </div>\n <gn-ui-expandable-panel-button [titleTemplate]=\"addLayerTitle\">\n <mat-tab-group animationDuration=\"200ms\" class=\"h-full\">\n <mat-tab [label]=\"'map.add.layer.catalog' | translate\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-catalog></gn-ui-add-layer-from-catalog>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wms' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wms></gn-ui-add-layer-from-wms>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wfs' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wfs></gn-ui-add-layer-from-wfs>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.ogc.api' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-ogc-api\n [ogcUrl]=\"ogcUrl\"\n (layerAdded)=\"addLayer($event)\"\n ></gn-ui-add-layer-from-ogc-api>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.file' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-file></gn-ui-add-layer-from-file>\n </div>\n </mat-tab>\n </mat-tab-group>\n </gn-ui-expandable-panel-button>\n</div>\n\n<ng-template #addLayerTitle>\n <ng-icon class=\"mr-4\" name=\"matAddCircleOutlineOutline\"> </ng-icon>\n <span translate>map.add.layer</span>\n</ng-template>\n" }]
32386
33002
  }] });
32387
33003
 
32388
- class GeocodingComponent {
32389
- constructor() {
32390
- this.mapFacade = inject(MapFacade);
32391
- this.geocodingService = inject(GeocodingService);
32392
- this.searchText = '';
32393
- this.results = [];
32394
- this.searchTextChanged = new Subject();
32395
- this.destroy$ = new Subject();
32396
- this.errorMessage = null;
32397
- this.searchTextChanged
32398
- .pipe(debounceTime(300), switchMap$1((searchText) => {
32399
- return from(this.geocodingService.query(searchText)).pipe(catchError$1((error) => {
32400
- this.errorMessage =
32401
- 'An error occurred while searching. Please try again.';
32402
- console.error(error);
32403
- return [];
32404
- }));
32405
- }), takeUntil(this.destroy$))
32406
- .subscribe((results) => {
32407
- this.results = results;
32408
- });
32409
- }
32410
- ngOnDestroy() {
32411
- this.destroy$.next();
32412
- this.destroy$.complete();
32413
- }
32414
- onSearchChange(searchText) {
32415
- if (!searchText) {
32416
- this.clearSearch();
32417
- return;
32418
- }
32419
- else {
32420
- this.searchTextChanged.next(searchText);
32421
- }
32422
- }
32423
- clearSearch() {
32424
- this.searchText = '';
32425
- this.results = [];
32426
- this.errorMessage = null;
32427
- }
32428
- async zoomToLocation(result) {
32429
- const geometry = result.geom;
32430
- if (geometry.type === 'Point') {
32431
- await this.zoomToPoint(geometry.coordinates);
32432
- }
32433
- else if (geometry.type === 'Polygon') {
32434
- await this.zoomToPolygon(geometry.coordinates);
32435
- }
32436
- else {
32437
- console.error(`Unsupported geometry type: ${geometry.type}`);
32438
- }
32439
- }
32440
- async zoomToPoint(pointCoords) {
32441
- const context = await firstValueFrom(this.mapFacade.context$);
32442
- const view = {
32443
- center: pointCoords,
32444
- zoom: 10,
32445
- };
32446
- this.mapFacade.applyContext({
32447
- ...context,
32448
- view,
32449
- });
32450
- }
32451
- async zoomToPolygon(polygonCoords) {
32452
- const context = await firstValueFrom(this.mapFacade.context$);
32453
- const view = {
32454
- geometry: {
32455
- type: 'Polygon',
32456
- coordinates: polygonCoords,
32457
- },
32458
- };
32459
- this.mapFacade.applyContext({
32460
- ...context,
32461
- view,
32462
- });
32463
- }
32464
- onEnterPress() {
32465
- if (this.results && this.results.length > 0) {
32466
- this.zoomToLocation(this.results[0]);
32467
- }
32468
- }
32469
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32470
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GeocodingComponent, isStandalone: true, selector: "gn-ui-geocoding", ngImport: i0, template: "<gn-ui-search-input\n [(value)]=\"searchText\"\n (valueChange)=\"onSearchChange($event)\"\n (keyup.enter)=\"onEnterPress()\"\n [placeholder]=\"'map.geocoding.placeholder' | translate\"\n>\n</gn-ui-search-input>\n@if (results && results.length) {\n <ul\n class=\"bg-gray-50 border border-gray-200 w-full mt-2 shadow-sm rounded-lg\"\n >\n @for (result of results; track result) {\n <li\n (click)=\"zoomToLocation(result)\"\n class=\"flex items-center pl-8 pr-4 py-2 border-b border-gray-200 relative cursor-pointer hover:bg-blue-100 hover:text-gray-800 transition duration-300 ease-in-out\"\n >\n <svg\n class=\"stroke-current text-blue-500 absolute w-5 h-5 left-3 top-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <span class=\"font-sans font-semibold ml-4\">{{ result.label }}</span>\n </li>\n }\n </ul>\n}\n", styles: [""], dependencies: [{ kind: "component", type: SearchInputComponent, selector: "gn-ui-search-input", inputs: ["value", "placeholder"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32471
- }
32472
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, decorators: [{
32473
- type: Component,
32474
- args: [{ selector: 'gn-ui-geocoding', standalone: true, imports: [SearchInputComponent, TranslatePipe], template: "<gn-ui-search-input\n [(value)]=\"searchText\"\n (valueChange)=\"onSearchChange($event)\"\n (keyup.enter)=\"onEnterPress()\"\n [placeholder]=\"'map.geocoding.placeholder' | translate\"\n>\n</gn-ui-search-input>\n@if (results && results.length) {\n <ul\n class=\"bg-gray-50 border border-gray-200 w-full mt-2 shadow-sm rounded-lg\"\n >\n @for (result of results; track result) {\n <li\n (click)=\"zoomToLocation(result)\"\n class=\"flex items-center pl-8 pr-4 py-2 border-b border-gray-200 relative cursor-pointer hover:bg-blue-100 hover:text-gray-800 transition duration-300 ease-in-out\"\n >\n <svg\n class=\"stroke-current text-blue-500 absolute w-5 h-5 left-3 top-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <span class=\"font-sans font-semibold ml-4\">{{ result.label }}</span>\n </li>\n }\n </ul>\n}\n" }]
32475
- }], ctorParameters: () => [] });
32476
-
32477
33004
  /*
32478
33005
  Metadata actions
32479
33006
  */
@@ -32489,8 +33016,7 @@ const closeMetadata = createAction('[Metadata view] close');
32489
33016
  Related actions
32490
33017
  */
32491
33018
  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());
33019
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32494
33020
  /*
32495
33021
  ChartConfig actions
32496
33022
  */
@@ -32522,9 +33048,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32522
33048
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32523
33049
  setChartConfig: setChartConfig,
32524
33050
  setIncompleteMetadata: setIncompleteMetadata,
32525
- setRelated: setRelated,
32526
- setSourceOf: setSourceOf,
32527
- setSources: setSources
33051
+ setLinkedRecords: setLinkedRecords,
33052
+ setRelated: setRelated
32528
33053
  });
32529
33054
 
32530
33055
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32567,12 +33092,9 @@ on(loadFullMetadata, (state) => ({
32567
33092
  on(setRelated, (state, { related }) => ({
32568
33093
  ...state,
32569
33094
  related,
32570
- })), on(setSources, (state, { sources }) => ({
32571
- ...state,
32572
- sources,
32573
- })), on(setSourceOf, (state, { sourceOf }) => ({
33095
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32574
33096
  ...state,
32575
- sourceOf,
33097
+ linkedRecords,
32576
33098
  })),
32577
33099
  /*
32578
33100
  ChartConfig reducers
@@ -32634,8 +33156,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32634
33156
  Related selectors
32635
33157
  */
32636
33158
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32637
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32638
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33159
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32639
33160
  /*
32640
33161
  Metadata selectors
32641
33162
  */
@@ -32736,212 +33257,10 @@ function parseHeaders(httpHeaders) {
32736
33257
  return result;
32737
33258
  }
32738
33259
 
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
- });
33260
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33261
+ function fieldToSql(name) {
33262
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32771
33263
  }
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
- });
32943
- }
32944
-
32945
33264
  function filterToSql(filter) {
32946
33265
  const operator = filter[0];
32947
33266
  const args = filter.slice(1);
@@ -32956,10 +33275,10 @@ function filterToSql(filter) {
32956
33275
  case '=':
32957
33276
  case '!=':
32958
33277
  case 'like':
32959
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33278
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32960
33279
  case 'in': {
32961
33280
  const values = args.slice(1);
32962
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33281
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32963
33282
  }
32964
33283
  case 'and':
32965
33284
  case 'or': {
@@ -32978,17 +33297,18 @@ function aggregationToSql(aggregation) {
32978
33297
  const field = aggregation[1];
32979
33298
  switch (operation) {
32980
33299
  case 'average':
32981
- return `AVG([${field}]) as [average(${field})]`;
33300
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
32982
33301
  case 'sum':
32983
33302
  case 'max':
32984
33303
  case 'min':
32985
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33304
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
32986
33305
  case 'count':
32987
- return 'COUNT(*) as [count()]';
33306
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
32988
33307
  }
32989
33308
  }
32990
33309
  /**
32991
33310
  * Leave arguments at null if not used
33311
+ * @param tableName
32992
33312
  * @param selected
32993
33313
  * @param filter
32994
33314
  * @param sort
@@ -32997,22 +33317,25 @@ function aggregationToSql(aggregation) {
32997
33317
  * @param groupBy
32998
33318
  * @param aggregations
32999
33319
  */
33000
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33320
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33001
33321
  let sqlSelect = 'SELECT *';
33002
- const sqlFrom = ' FROM ?';
33322
+ const sqlFrom = ` FROM ${tableName}`;
33003
33323
  let sqlOrderBy = '';
33004
33324
  let sqlWhere = '';
33005
33325
  let sqlLimit = '';
33006
33326
  let sqlGroupBy = '';
33007
33327
  if (selected !== null) {
33008
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33328
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33329
+ }
33330
+ if (geometryColumn !== null) {
33331
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33009
33332
  }
33010
33333
  if (filter !== null) {
33011
33334
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33012
33335
  }
33013
33336
  if (sort?.length) {
33014
33337
  sqlOrderBy = ` ORDER BY ${sort
33015
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33338
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33016
33339
  .join(', ')}`;
33017
33340
  }
33018
33341
  if (startIndex !== null && count !== null) {
@@ -33022,17 +33345,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33022
33345
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33023
33346
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33024
33347
  const sqlGroupByFields = groupedByDistinct
33025
- .map((group) => `[${group[1]}]`)
33348
+ .map((group) => fieldToSql(group[1]))
33026
33349
  .join(', ');
33027
33350
  const sqlGroupBySelect = groupedByDistinct
33028
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33351
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33029
33352
  .join(', ');
33030
33353
  if (sqlGroupByFields && sqlGroupBySelect) {
33031
33354
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33032
33355
  sqlSelect += `, ${sqlGroupBySelect}`;
33033
33356
  }
33034
33357
  }
33035
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33358
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33359
+ }
33360
+
33361
+ function arrowTableToDataItems(table) {
33362
+ const fields = table.schema.fields;
33363
+ return table.toArray().map((row) => {
33364
+ const rowJson = row.toJSON();
33365
+ const feature = {
33366
+ type: 'Feature',
33367
+ geometry: null,
33368
+ properties: {},
33369
+ };
33370
+ const keys = Object.keys(rowJson);
33371
+ for (let i = 0; i < keys.length; i++) {
33372
+ const key = keys[i];
33373
+ const dataType = fields[i].type;
33374
+ let value = rowJson[key];
33375
+ // this might happen if we get an array inside a field
33376
+ if (value instanceof Vector) {
33377
+ value = Array.from(value);
33378
+ }
33379
+ // cast bigints to ints
33380
+ if (typeof value === 'bigint') {
33381
+ value = Number(value);
33382
+ }
33383
+ // rename columns with empty name
33384
+ if (!key) {
33385
+ feature.properties['unknown'] = value;
33386
+ continue;
33387
+ }
33388
+ // assign properties that look like an id to the geojson `id` field
33389
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33390
+ (typeof value == 'string' || typeof value === 'number')) {
33391
+ feature.id = value;
33392
+ }
33393
+ // if a date is in timestamp (number) format, convert it to native
33394
+ if (typeof value === 'number' &&
33395
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33396
+ value = new Date(value);
33397
+ }
33398
+ // if a binary field (most likely a geometry column): skip
33399
+ if (DataType.isBinary(dataType)) {
33400
+ continue;
33401
+ }
33402
+ // geometry column
33403
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33404
+ feature.geometry = JSON.parse(value);
33405
+ continue;
33406
+ }
33407
+ feature.properties[key] = value;
33408
+ }
33409
+ return feature;
33410
+ });
33411
+ }
33412
+
33413
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33414
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33415
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33416
+ const typesMapping = {
33417
+ INTEGER: 'number',
33418
+ SMALLINT: 'number',
33419
+ TINYINT: 'number',
33420
+ BIGINT: 'number',
33421
+ HUGEINT: 'number',
33422
+ UINTEGER: 'number',
33423
+ USMALLINT: 'number',
33424
+ UTINYINT: 'number',
33425
+ UBIGINT: 'number',
33426
+ UHUGEINT: 'number',
33427
+ DOUBLE: 'number',
33428
+ BIGNUM: 'number',
33429
+ DECIMAL: 'number',
33430
+ NUMERIC: 'number',
33431
+ FLOAT: 'number',
33432
+ REAL: 'number',
33433
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33434
+ TIMESTAMP: 'date',
33435
+ DATE: 'date',
33436
+ CHAR: 'string',
33437
+ VARCHAR: 'string',
33438
+ TEXT: 'string',
33439
+ UUID: 'string',
33440
+ BLOB: 'string',
33441
+ BIT: 'string',
33442
+ INTERVAL: 'string',
33443
+ LIST: 'string',
33444
+ BOOLEAN: 'boolean',
33445
+ };
33446
+ class Engine {
33447
+ async makeInit() {
33448
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33449
+ let worker_url = bundle.mainWorker;
33450
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33451
+ if (worker_url.startsWith('https://')) {
33452
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33453
+ type: 'text/javascript',
33454
+ }));
33455
+ }
33456
+ const worker = new Worker(worker_url);
33457
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33458
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33459
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33460
+ // setup duckdb options
33461
+ const conn = await this.db.connect();
33462
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33463
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33464
+ await conn.query(`SET TimeZone = '${timezone}';`);
33465
+ await conn.query(`SET threads = 1;`);
33466
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33467
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33468
+ conn.close();
33469
+ return this;
33470
+ }
33471
+ isReady() {
33472
+ if (!this.init_) {
33473
+ this.init_ = this.makeInit();
33474
+ }
33475
+ return this.init_;
33476
+ }
33477
+ /**
33478
+ * Returns information about a dataset once it's loaded:
33479
+ * - a list of properties description
33480
+ * - the name of the dataset geometry column (null if no geometry present)
33481
+ * @param datasetId name of the table under which the dataset will be stored
33482
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33483
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33484
+ */
33485
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33486
+ const conn = await this.db.connect();
33487
+ let results;
33488
+ // either we want to recreate the table, or we keep it if it already exists
33489
+ const safeLoadQuery = forceReload
33490
+ ? `DROP TABLE IF EXISTS ${datasetId};
33491
+ ${loadQuery}`
33492
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33493
+ // create the table
33494
+ try {
33495
+ results = await conn.query(safeLoadQuery);
33496
+ }
33497
+ catch (e) {
33498
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33499
+ }
33500
+ // read rows count
33501
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33502
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33503
+ // read columns
33504
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33505
+ let geometryColumn = null;
33506
+ const properties = results
33507
+ .toArray()
33508
+ .map((row) => {
33509
+ const rowObj = row.toJSON();
33510
+ if (rowObj['data_type'] === 'GEOMETRY') {
33511
+ // the geometry is not part of the properties
33512
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33513
+ // all get discarded
33514
+ geometryColumn = rowObj['column_name'];
33515
+ return null;
33516
+ }
33517
+ return {
33518
+ name: rowObj['column_name'],
33519
+ label: rowObj['column_name'],
33520
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33521
+ };
33522
+ })
33523
+ .filter((prop) => prop !== null);
33524
+ conn.close();
33525
+ return {
33526
+ properties,
33527
+ geometryColumn,
33528
+ rowsCount: Number(recordsCount),
33529
+ };
33530
+ }
33531
+ // register a Uint8 buffer using a handle in the duckdb instance
33532
+ async registerData(name, buffer) {
33533
+ return this.db.registerFileBuffer(name, buffer);
33534
+ }
33535
+ /**
33536
+ * @param query duckdb-specific query for fetching items
33537
+ */
33538
+ async queryItems(query) {
33539
+ const conn = await this.db.connect();
33540
+ const results = await conn.query(query);
33541
+ conn.close();
33542
+ return arrowTableToDataItems(results);
33543
+ }
33544
+ close() {
33545
+ this.db?.terminate();
33546
+ }
33547
+ }
33548
+ let engine = null;
33549
+ async function getEngine() {
33550
+ if (!engine) {
33551
+ engine = new Engine();
33552
+ }
33553
+ return engine.isReady();
33036
33554
  }
33037
33555
 
33038
33556
  class BaseReader {
@@ -33045,10 +33563,18 @@ class BaseReader {
33045
33563
  this.sort = null;
33046
33564
  this.startIndex = null;
33047
33565
  this.count = null;
33566
+ this.loadPromise_ = Promise.resolve();
33567
+ this.cacheEnabled = false;
33568
+ }
33569
+ enableCache(enabled) {
33570
+ this.cacheEnabled = enabled;
33048
33571
  }
33049
33572
  load() {
33050
33573
  throw new Error('not implemented');
33051
33574
  }
33575
+ get isLoaded() {
33576
+ return this.loadPromise_;
33577
+ }
33052
33578
  get properties() {
33053
33579
  throw new Error('not implemented');
33054
33580
  }
@@ -33097,178 +33623,237 @@ class BaseReader {
33097
33623
  }
33098
33624
  }
33099
33625
 
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() {
33626
+ /**
33627
+ * This reader handles file formats supported natively by DuckDB
33628
+ */
33629
+ class BaseFileReader extends BaseReader {
33630
+ // a table id should not exceed 63 chars
33631
+ generateDatasetId() {
33632
+ // generate a hash out of the url
33633
+ let hash = 0;
33634
+ for (const char of this.url) {
33635
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33636
+ hash = hash >>> 0; // make it unsigned
33637
+ }
33638
+ return `datafetcher_${hash.toString(16)}`;
33639
+ }
33640
+ async getLoadQuery() {
33113
33641
  throw new Error('not implemented');
33114
33642
  }
33115
- load() {
33116
- this.parseResult_ = this.getData();
33643
+ async load() {
33644
+ this.datasetId = this.generateDatasetId();
33645
+ this.loadPromise_ = getEngine()
33646
+ .then((engine) => {
33647
+ this.engine = engine;
33648
+ return this.getLoadQuery();
33649
+ })
33650
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33651
+ .then((datasetInfo) => {
33652
+ this.properties_ = datasetInfo.properties;
33653
+ this.geometryColumn = datasetInfo.geometryColumn;
33654
+ this.rowsCount = datasetInfo.rowsCount;
33655
+ // returns void for the loaded promise
33656
+ });
33117
33657
  }
33118
33658
  get properties() {
33119
- return this.parseResult_.then((result) => result.properties);
33659
+ return this.isLoaded.then(() => this.properties_);
33120
33660
  }
33121
33661
  get info() {
33122
- return this.parseResult_.then((result) => ({
33123
- itemsCount: result.items.length,
33662
+ return this.isLoaded.then(() => ({
33663
+ itemsCount: this.rowsCount,
33664
+ hasGeometry: !!this.geometryColumn,
33124
33665
  }));
33125
33666
  }
33126
33667
  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'));
33668
+ await this.isLoaded;
33669
+ // if only certain fields are selected, omit the geometry
33670
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33671
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33672
+ return this.engine.queryItems(query);
33169
33673
  }
33170
- const items = parsed.data.map(jsonToGeojsonFeature);
33171
- return processItemProperties(items, true);
33172
33674
  }
33675
+
33173
33676
  class CsvReader extends BaseFileReader {
33174
- getData() {
33175
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33677
+ async getLoadQuery() {
33678
+ // first we get a list of columns hich have a detected type of VARCHAR
33679
+ // 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
33680
+ return `
33681
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33682
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33683
+ force_not_null = getvariable('strColumns'),
33684
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33685
+ );`;
33176
33686
  }
33177
33687
  }
33178
33688
 
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
33689
  class JsonReader extends BaseFileReader {
33191
- getData() {
33192
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33690
+ async getLoadQuery() {
33691
+ return `
33692
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33693
+ maximum_object_size = 536870912 -- 500MB
33694
+ );`;
33193
33695
  }
33194
33696
  }
33195
33697
 
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
33698
  class GeojsonReader extends BaseFileReader {
33210
- getData() {
33211
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33699
+ async getLoadQuery() {
33700
+ return `
33701
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33702
+ allowed_drivers = ['GeoJSON']
33703
+ );`;
33212
33704
  }
33213
33705
  }
33214
33706
 
33707
+ async function inferDatasetType(url, typeHint) {
33708
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33709
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33710
+ ? fileExtensionMatches[1].toLowerCase()
33711
+ : null;
33712
+ // 1. type hint
33713
+ if (typeHint)
33714
+ return Promise.resolve(typeHint);
33715
+ // 2. content-type header
33716
+ const headers = await fetchHeaders(url);
33717
+ if ('supportedType' in headers)
33718
+ return headers.supportedType;
33719
+ // 3. file extension from url
33720
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33721
+ return fileExtension;
33722
+ // no type inferred or hinted
33723
+ if ('mimeType' in headers)
33724
+ throw FetchError.unsupportedType(headers.mimeType);
33725
+ else
33726
+ throw FetchError.unknownType();
33727
+ }
33728
+ function fetchHeaders(url) {
33729
+ return sharedFetch(url, 'HEAD')
33730
+ .catch((error) => {
33731
+ throw FetchError.corsOrNetwork(error.message);
33732
+ })
33733
+ .then((response) => {
33734
+ if (!response.ok) {
33735
+ throw FetchError.http(response.status);
33736
+ }
33737
+ return parseHeaders(response.headers);
33738
+ });
33739
+ }
33740
+ function fetchDataAsText(url, cacheActive) {
33741
+ const fetchFactory = () => sharedFetch(url)
33742
+ .catch((error) => {
33743
+ throw FetchError.corsOrNetwork(error.message);
33744
+ })
33745
+ .then(async (response) => {
33746
+ if (!response.ok) {
33747
+ const clonedResponse = response.clone();
33748
+ throw FetchError.http(response.status, await clonedResponse.text());
33749
+ }
33750
+ const clonedResponse = response.clone();
33751
+ return clonedResponse.text();
33752
+ });
33753
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33754
+ }
33755
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33756
+ const fetchFactory = () => sharedFetch(url)
33757
+ .catch((error) => {
33758
+ throw FetchError.corsOrNetwork(error.message);
33759
+ })
33760
+ .then(async (response) => {
33761
+ if (!response.ok) {
33762
+ throw FetchError.http(response.status, await response.text());
33763
+ }
33764
+ // convert to a numeric array so that we can store the response in cache
33765
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33766
+ });
33767
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33768
+ return new Uint8Array(array).buffer;
33769
+ });
33770
+ }
33215
33771
  /**
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
33772
+ * This creates a Proxy that allows reading and writing to the data item properties
33773
+ * as if it was a simple array of JSON objects
33774
+ * @param items
33219
33775
  */
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);
33776
+ function getJsonDataItemsProxy(items) {
33777
+ return new Proxy(items, {
33778
+ get(target, p) {
33779
+ if (typeof p === 'string' &&
33780
+ !Number.isNaN(parseInt(p)) &&
33781
+ target[p]?.properties) {
33782
+ return target[p].properties;
33783
+ }
33784
+ return target[p];
33785
+ },
33786
+ set() {
33787
+ throw new Error('This object is read-only');
33788
+ },
33229
33789
  });
33230
33790
  }
33791
+
33231
33792
  class ExcelReader extends BaseFileReader {
33232
- getData() {
33233
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33793
+ async getLoadQuery() {
33794
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33795
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33796
+ const bufferHandle = `B${this.datasetId}`;
33797
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33798
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33799
+ const isXls = Array.from(magicNumber)
33800
+ .map((n) => n.toString(16).toUpperCase())
33801
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33802
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33803
+ if (isXls) {
33804
+ buffer = await import('xlsx').then(({ read, utils }) => {
33805
+ const workbook = read(buffer);
33806
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33807
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33808
+ });
33809
+ }
33810
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33811
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33812
+ return `
33813
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33234
33814
  }
33235
33815
  }
33236
33816
 
33237
- function parseGml(text, namespace, version) {
33238
- const splittedNamespace = namespace.split(':');
33239
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33817
+ class GmlReader extends BaseFileReader {
33818
+ async getLoadQuery() {
33819
+ return `
33820
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33821
+ allowed_drivers = ['GML']
33822
+ );`;
33823
+ }
33824
+ }
33825
+
33826
+ const formatGeojson = new GeoJSON$1();
33827
+ function parseGeojson(text) {
33828
+ const parsed = JSON.parse(text);
33829
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33830
+ if (!Array.isArray(features)) {
33831
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33832
+ }
33833
+ return features;
33834
+ }
33835
+ function parseGml(text, featureType, version) {
33836
+ const parts = featureType.split(':');
33837
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33240
33838
  const match = regex.exec(text);
33241
33839
  if (match && match.length >= 2) {
33242
- const wf = new WFS({
33840
+ const wfs = new WFS({
33243
33841
  featureNS: match[1],
33244
- featureType: splittedNamespace[1],
33842
+ featureType: parts[1],
33245
33843
  version: version,
33246
33844
  });
33247
33845
  let features;
33248
33846
  try {
33249
- features = wf.readFeatures(text);
33847
+ features = wfs.readFeatures(text);
33250
33848
  }
33251
33849
  catch (e) {
33252
- throw Error("Couldn't parse WFS with GML features");
33850
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33253
33851
  }
33254
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33255
- return processItemProperties(geojsonItem.features, true);
33852
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33853
+ return geojsonItem.features;
33256
33854
  }
33257
33855
  throw Error("Couldn't retrieve namespace url");
33258
33856
  }
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
33857
  async function getWfsEndpoint(wfsUrl) {
33273
33858
  try {
33274
33859
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33296,21 +33881,65 @@ async function getWfsEndpoint(wfsUrl) {
33296
33881
  }
33297
33882
  }
33298
33883
  }
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();
33884
+ class WfsReader extends BaseReader {
33885
+ constructor(url, featureTypeName) {
33886
+ super(url);
33887
+ this.endpoint = getWfsEndpoint(url);
33888
+ this.featureType = this.endpoint
33889
+ .then((endpoint) => {
33890
+ const featureTypes = endpoint.getFeatureTypes();
33891
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33892
+ ? featureTypes[0].name
33893
+ : featureTypeName);
33894
+ })
33895
+ .then((featureType) => {
33896
+ if (!featureType) {
33897
+ throw new Error('wfs.featuretype.notfound');
33898
+ }
33899
+ return featureType;
33900
+ });
33901
+ }
33902
+ get backupReader() {
33903
+ if (this.backupReader_) {
33904
+ return this.backupReader_;
33905
+ }
33906
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33907
+ let reader;
33908
+ if (endpoint.supportsJson(featureType.name)) {
33909
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33910
+ asJson: true,
33911
+ outputCrs: 'EPSG:4326',
33912
+ }));
33913
+ }
33914
+ else {
33915
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33916
+ (featureType.defaultCrs === 'EPSG:4326' ||
33917
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33918
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33919
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33920
+ outputCrs: 'EPSG:4326',
33921
+ }));
33922
+ }
33923
+ }
33924
+ reader.enableCache(this.cacheEnabled);
33925
+ reader.load();
33926
+ return reader;
33927
+ });
33928
+ return this.backupReader_;
33305
33929
  }
33306
33930
  get properties() {
33307
- return this.endpoint
33308
- .getFeatureTypeFull(this.featureTypeName)
33309
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33931
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33310
33932
  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
33933
+ let type;
33934
+ if (originalType === 'float' || originalType === 'integer') {
33935
+ type = 'number';
33936
+ }
33937
+ else if (originalType === 'boolean') {
33938
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33939
+ }
33940
+ else {
33941
+ type = originalType;
33942
+ }
33314
33943
  return {
33315
33944
  name: prop,
33316
33945
  label: prop,
@@ -33319,83 +33948,63 @@ class WfsReader extends BaseCacheReader {
33319
33948
  }));
33320
33949
  }
33321
33950
  get info() {
33322
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33951
+ return this.featureType.then((result) => ({
33323
33952
  itemsCount: result.objectCount,
33953
+ hasGeometry: !!result.geometryName,
33324
33954
  }));
33325
33955
  }
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
- }
33956
+ load() {
33957
+ // Nothing to load for Wfs
33355
33958
  }
33356
- async getData(aggregation, groupedBy) {
33357
- if (aggregation || groupedBy) {
33358
- return { items: await this.getQueryData() };
33959
+ async read() {
33960
+ const endpoint = await this.endpoint;
33961
+ const featureType = await this.featureType;
33962
+ // if we can't use the WFS protocol we fall back to the backup reader
33963
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33964
+ const backupReader = await this.backupReader;
33965
+ backupReader.selectAll();
33966
+ if (this.selected) {
33967
+ backupReader.select(...this.selected);
33968
+ }
33969
+ if (this.aggregations) {
33970
+ backupReader.aggregate(...this.aggregations);
33971
+ }
33972
+ if (this.groupedBy) {
33973
+ backupReader.groupBy(...this.groupedBy);
33974
+ }
33975
+ if (this.sort) {
33976
+ backupReader.orderBy(...this.sort);
33977
+ }
33978
+ if (this.startIndex !== null && this.count !== null) {
33979
+ backupReader.limit(this.startIndex, this.count);
33980
+ }
33981
+ return backupReader.read();
33359
33982
  }
33360
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33983
+ const asJson = endpoint.supportsJson(featureType.name);
33361
33984
  const attributes = this.selected ?? undefined;
33362
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33985
+ let sortBy = null;
33986
+ if (this.sort) {
33987
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33988
+ sortBy = Array.isArray(this.sort[0])
33989
+ ? this.sort.map(mapSort)
33990
+ : mapSort(this.sort);
33991
+ }
33992
+ const url = endpoint.getFeatureUrl(featureType.name, {
33363
33993
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33364
33994
  ...(this.count !== null && { maxFeatures: this.count }),
33365
33995
  asJson,
33366
33996
  outputCrs: 'EPSG:4326',
33367
33997
  attributes,
33368
- // sortBy: this.sort // TODO: no sort in ogc-client?
33998
+ sortBy,
33369
33999
  });
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
34000
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33379
34001
  ? 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;
34002
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33394
34003
  }
33395
34004
  }
33396
34005
 
33397
- async function openDataset(url, typeHint, options, cacheActive) {
33398
- const fileType = await inferDatasetType(url, typeHint);
34006
+ async function openDataset(url, options) {
34007
+ const fileType = await inferDatasetType(url, options?.typeHint);
33399
34008
  let reader;
33400
34009
  try {
33401
34010
  switch (fileType) {
@@ -33412,18 +34021,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33412
34021
  reader = new ExcelReader(url);
33413
34022
  break;
33414
34023
  case 'gml':
33415
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
34024
+ reader = new GmlReader(url);
33416
34025
  break;
33417
34026
  case 'wfs':
33418
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
34027
+ reader = new WfsReader(url, options?.wfsFeatureType);
33419
34028
  break;
33420
34029
  }
33421
- reader.setCacheActive(cacheActive);
34030
+ reader.enableCache(options?.enableCache ?? true);
33422
34031
  reader.load();
33423
34032
  return reader;
33424
34033
  }
33425
34034
  catch (e) {
33426
- //WfsReader may already raise a FetchError
34035
+ // WfsReader may already raise a FetchError
33427
34036
  if (e instanceof FetchError)
33428
34037
  throw e;
33429
34038
  else
@@ -33434,13 +34043,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33434
34043
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33435
34044
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33436
34045
  * 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:
34046
+ * File type is determined like so:
33438
34047
  * 1. if a type hint is given, use it
33439
34048
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33440
34049
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33441
34050
  */
33442
- async function readDataset(url, typeHint, options, cacheActive = true) {
33443
- const reader = await openDataset(url, typeHint, options, cacheActive);
34051
+ async function readDataset(url, options) {
34052
+ const reader = await openDataset(url, options);
33444
34053
  try {
33445
34054
  return await reader.read();
33446
34055
  }
@@ -33671,9 +34280,11 @@ class DataService {
33671
34280
  getDataset(link, cacheActive) {
33672
34281
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33673
34282
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33674
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34283
+ return from(openDataset(wfsUrlEndpoint, {
34284
+ typeHint: 'wfs',
33675
34285
  wfsFeatureType: link.name,
33676
- }, cacheActive));
34286
+ enableCache: cacheActive,
34287
+ }));
33677
34288
  }
33678
34289
  else if (link.type === 'download') {
33679
34290
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33681,12 +34292,15 @@ class DataService {
33681
34292
  const supportedType = SupportedTypes.indexOf(format) > -1
33682
34293
  ? format
33683
34294
  : undefined;
33684
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34295
+ return from(openDataset(linkProxifiedUrl, {
34296
+ typeHint: supportedType,
34297
+ enableCache: cacheActive,
34298
+ })).pipe();
33685
34299
  }
33686
34300
  else if (link.type === 'service' &&
33687
34301
  link.accessServiceProtocol === 'esriRest') {
33688
34302
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33689
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34303
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33690
34304
  }
33691
34305
  else if (link.type === 'service' &&
33692
34306
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33700,7 +34314,10 @@ class DataService {
33700
34314
  }
33701
34315
  const urlWithoutLimit = new URL(geojsonUrl);
33702
34316
  urlWithoutLimit.searchParams.delete('limit');
33703
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34317
+ return openDataset(urlWithoutLimit.toString(), {
34318
+ typeHint: 'geojson',
34319
+ enableCache: cacheActive,
34320
+ });
33704
34321
  }));
33705
34322
  }
33706
34323
  return throwError(() => 'protocol not supported');
@@ -33952,20 +34569,31 @@ class DataTableComponent {
33952
34569
  this.eltRef = inject(ElementRef);
33953
34570
  this.cdr = inject(ChangeDetectorRef);
33954
34571
  this.translateService = inject(TranslateService);
33955
- this._featureAttributes = [];
34572
+ this.columnsFromFeatureCatalog = null;
34573
+ this.columnsFromDataset = [];
33956
34574
  this.selected = new EventEmitter();
33957
- this.properties$ = new BehaviorSubject(null);
33958
34575
  this.loading$ = new BehaviorSubject(false);
33959
34576
  this.error = null;
33960
34577
  }
33961
34578
  set featureAttributes(value) {
33962
- this._featureAttributes = value;
33963
- this.properties$.next(value.map((attr) => attr.value));
34579
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34580
+ name: attrs.value,
34581
+ label: attrs.label,
34582
+ }));
33964
34583
  }
33965
34584
  set dataset(value) {
33966
34585
  this.dataset_ = value;
33967
34586
  this.dataset_.load();
33968
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34587
+ this.dataset_.info.then((info) => {
34588
+ this.count = info.itemsCount;
34589
+ this.cdr.detectChanges();
34590
+ });
34591
+ }
34592
+ get columns() {
34593
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34594
+ }
34595
+ get columnNames() {
34596
+ return this.columns.map((c) => c.name);
33969
34597
  }
33970
34598
  ngOnInit() {
33971
34599
  this.dataSource = new DataTableDataSource();
@@ -34000,11 +34628,12 @@ class DataTableComponent {
34000
34628
  }
34001
34629
  async readData() {
34002
34630
  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
34631
  try {
34632
+ // wait for properties to be read
34633
+ if (!this.columnsFromFeatureCatalog) {
34634
+ this.columnsFromDataset = await this.dataset_.properties;
34635
+ }
34636
+ this.dataset_.select(...this.columnNames);
34008
34637
  await this.dataSource.showData(this.dataset_.read());
34009
34638
  this.error = null;
34010
34639
  }
@@ -34034,7 +34663,7 @@ class DataTableComponent {
34034
34663
  }
34035
34664
  }
34036
34665
  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 }); }
34666
+ 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
34667
  }
34039
34668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34040
34669
  type: Component,
@@ -34046,10 +34675,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34046
34675
  CommonModule,
34047
34676
  LoadingMaskComponent,
34048
34677
  PopupAlertComponent,
34049
- LetDirective,
34050
34678
  TranslatePipe,
34051
34679
  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"] }]
34680
+ ], 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
34681
  }], propDecorators: { featureAttributes: [{
34054
34682
  type: Input
34055
34683
  }], dataset: [{
@@ -34152,7 +34780,7 @@ class ChartViewComponent {
34152
34780
  }), shareReplay$1(1));
34153
34781
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34154
34782
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34155
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34783
+ .filter((prop) => prop.type === 'number')
34156
34784
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34157
34785
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34158
34786
  const newProp = choices[0]?.value || '';
@@ -34179,6 +34807,7 @@ class ChartViewComponent {
34179
34807
  this.aggregation$,
34180
34808
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34181
34809
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34810
+ this.loading = true;
34182
34811
  return dataset
34183
34812
  .groupBy(['distinct', xProp])
34184
34813
  .aggregate(fieldAgg)
@@ -34420,6 +35049,7 @@ class GeoTableViewComponent {
34420
35049
  }));
34421
35050
  }
34422
35051
  async initMapContext() {
35052
+ this.dataset.load();
34423
35053
  this.dataset.selectAll();
34424
35054
  return {
34425
35055
  layers: [
@@ -34593,8 +35223,7 @@ class MdViewFacade {
34593
35223
  }));
34594
35224
  this.error$ = this.store.pipe(select(getMetadataError));
34595
35225
  this.related$ = this.store.pipe(select(getRelated));
34596
- this.sources$ = this.store.pipe(select(getSources));
34597
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35226
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34598
35227
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34599
35228
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34600
35229
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34713,12 +35342,9 @@ class MdViewEffects {
34713
35342
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34714
35343
  return setRelated({ related });
34715
35344
  }), 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 })))));
35345
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35346
+ return setLinkedRecords({ linkedRecords });
35347
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34722
35348
  /*
34723
35349
  UserFeedback effects
34724
35350
  */
@@ -36452,6 +37078,11 @@ const DEFAULT_CONFIGURATION = {
36452
37078
  },
36453
37079
  hidden: '${record.kind == "service"}',
36454
37080
  },
37081
+ {
37082
+ model: 'associatedRecords',
37083
+ formFieldConfig: {},
37084
+ hidden: '${record.kind == "service"}',
37085
+ },
36455
37086
  ],
36456
37087
  },
36457
37088
  // Section: Geographical coverage
@@ -37414,7 +38045,7 @@ class GenericKeywordsComponent {
37414
38045
  this.deletedKeyword.emit(keyword);
37415
38046
  }
37416
38047
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37417
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38048
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37418
38049
  }
37419
38050
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, decorators: [{
37420
38051
  type: Component,
@@ -37589,7 +38220,7 @@ class FormFieldTemporalExtentsComponent {
37589
38220
  provideNgIconsConfig({
37590
38221
  size: '1.5rem',
37591
38222
  }),
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 }); }
38223
+ ], 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
38224
  }
37594
38225
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldTemporalExtentsComponent, decorators: [{
37595
38226
  type: Component,
@@ -37606,7 +38237,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37606
38237
  provideNgIconsConfig({
37607
38238
  size: '1.5rem',
37608
38239
  }),
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"] }]
38240
+ ], 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
38241
  }], propDecorators: { value: [{
37611
38242
  type: Input
37612
38243
  }], valueChange: [{
@@ -37822,6 +38453,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37822
38453
  ], 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
38454
  }] });
37824
38455
 
38456
+ class FormFieldAssociatedRecordsComponent {
38457
+ constructor() {
38458
+ this.valueChange = new EventEmitter();
38459
+ }
38460
+ get list() {
38461
+ return this.value ?? [];
38462
+ }
38463
+ get first() {
38464
+ return this.list[0];
38465
+ }
38466
+ get rest() {
38467
+ return this.list.slice(1);
38468
+ }
38469
+ get hasParentLink() {
38470
+ return this.list.length > 0;
38471
+ }
38472
+ get uniqueIdentifier() {
38473
+ return this.first?.uniqueIdentifier ?? '';
38474
+ }
38475
+ get selectedType() {
38476
+ return this.first?.associationType;
38477
+ }
38478
+ get choices() {
38479
+ return associationTypeValues.map((value) => ({
38480
+ value,
38481
+ label: `domain.record.associationType.${value}`,
38482
+ }));
38483
+ }
38484
+ onToggle(checked) {
38485
+ this.valueChange.emit(checked
38486
+ ? [
38487
+ {
38488
+ uniqueIdentifier: '',
38489
+ associationType: associationTypeValues[0],
38490
+ },
38491
+ ...this.rest,
38492
+ ]
38493
+ : this.rest);
38494
+ }
38495
+ onUniqueIdentifierChange(uniqueIdentifier) {
38496
+ this.valueChange.emit(uniqueIdentifier
38497
+ ? [{ ...this.first, uniqueIdentifier }, ...this.rest]
38498
+ : this.rest);
38499
+ }
38500
+ onTypeChange(associationType) {
38501
+ this.valueChange.emit([
38502
+ { ...this.first, associationType: associationType },
38503
+ ...this.rest,
38504
+ ]);
38505
+ }
38506
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38507
+ 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 }); }
38508
+ }
38509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, decorators: [{
38510
+ type: Component,
38511
+ args: [{ selector: 'gn-ui-form-field-associated-records', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
38512
+ CheckToggleComponent,
38513
+ TextInputComponent,
38514
+ DropdownSelectorComponent,
38515
+ FormFieldWrapperComponent,
38516
+ TranslatePipe,
38517
+ ], 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" }]
38518
+ }], propDecorators: { value: [{
38519
+ type: Input
38520
+ }], valueChange: [{
38521
+ type: Output
38522
+ }] } });
38523
+
37825
38524
  class ContactCardComponent {
37826
38525
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37827
38526
  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 }); }
@@ -37938,7 +38637,7 @@ class FormFieldContactsForResourceComponent {
37938
38637
  provideNgIconsConfig({
37939
38638
  size: '1.5rem',
37940
38639
  }),
37941
- ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38640
+ ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37942
38641
  }
37943
38642
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsForResourceComponent, decorators: [{
37944
38643
  type: Component,
@@ -38038,7 +38737,7 @@ class FormFieldContactsComponent {
38038
38737
  this.subscription.unsubscribe();
38039
38738
  }
38040
38739
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38041
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38740
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38042
38741
  }
38043
38742
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, decorators: [{
38044
38743
  type: Component,
@@ -38391,7 +39090,7 @@ class OnlineServiceResourceInputComponent {
38391
39090
  provideNgIconsConfig({
38392
39091
  size: '1.5em',
38393
39092
  }),
38394
- ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39093
+ ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38395
39094
  }
38396
39095
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OnlineServiceResourceInputComponent, decorators: [{
38397
39096
  type: Component,
@@ -39207,6 +39906,9 @@ class FormFieldComponent {
39207
39906
  get valueAsUpdateFrequency() {
39208
39907
  return this.value;
39209
39908
  }
39909
+ get valueAsAssociatedRecords() {
39910
+ return this.value;
39911
+ }
39210
39912
  get valueAsTemporalExtents() {
39211
39913
  return this.value;
39212
39914
  }
@@ -39246,7 +39948,7 @@ class FormFieldComponent {
39246
39948
  }
39247
39949
  }
39248
39950
  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 }); }
39951
+ 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
39952
  }
39251
39953
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldComponent, decorators: [{
39252
39954
  type: Component,
@@ -39258,6 +39960,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39258
39960
  FormFieldLicenseComponent,
39259
39961
  FormFieldDateComponent,
39260
39962
  FormFieldUpdateFrequencyComponent,
39963
+ FormFieldAssociatedRecordsComponent,
39261
39964
  FormFieldTemporalExtentsComponent,
39262
39965
  FormFieldSimpleComponent,
39263
39966
  FormFieldRichComponent,
@@ -39275,7 +39978,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39275
39978
  FormFieldTopicsComponent,
39276
39979
  TextFieldModule,
39277
39980
  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"] }]
39981
+ ], 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
39982
  }], propDecorators: { uniqueIdentifier: [{
39280
39983
  type: Input
39281
39984
  }], recordKind: [{
@@ -39860,7 +40563,7 @@ class RouterService {
39860
40563
  return ROUTER_ROUTE_ORGANIZATION;
39861
40564
  }
39862
40565
  getDefaultSort() {
39863
- return SortByEnum.RESOURCE_DATES;
40566
+ return SortByEnum.RESOURCE_DATE;
39864
40567
  }
39865
40568
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39866
40569
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, providedIn: 'root' }); }
@@ -39920,7 +40623,7 @@ function flattenQueryParams(params) {
39920
40623
  const start = flattened[key].start;
39921
40624
  const end = flattened[key].end;
39922
40625
  flattened[key] = [
39923
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40626
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
39924
40627
  ];
39925
40628
  }
39926
40629
  }
@@ -40277,5 +40980,5 @@ const CHART_TYPE_VALUES = [
40277
40980
  * Generated bundle index. Do not edit.
40278
40981
  */
40279
40982
 
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 };
40983
+ 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_GEOCODING_PROVIDER, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEOCODING_PROVIDER, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingService, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LocationSearchComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceCreationRevisionDateSearchField, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpatialExtentDropdownComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isBoundingBox, isBoundingBoxWithinWorldExtent, 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
40984
  //# sourceMappingURL=geonetwork-ui.mjs.map