geonetwork-ui 2.10.2 → 2.11.0-dev.0ddf1324b

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 (108) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1661 -986
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +298 -128
  4. package/index.d.ts.map +1 -1
  5. package/package.json +9 -8
  6. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +5 -5
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -5
  8. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  9. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  10. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +2 -2
  11. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +9 -5
  12. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  13. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +2 -2
  14. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +130 -37
  15. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  16. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +40 -2
  17. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  18. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  19. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  20. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  21. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  22. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  23. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  24. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  25. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  26. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  27. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  28. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  29. package/src/libs/feature/map/src/index.ts +0 -1
  30. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  31. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  32. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  33. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  34. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  35. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  36. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  37. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  38. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  39. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  40. package/src/libs/feature/search/src/index.ts +2 -0
  41. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  42. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  43. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +5 -1
  44. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  45. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  46. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  47. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  48. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  49. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  50. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  51. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  52. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  53. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  54. package/src/libs/ui/inputs/src/index.ts +2 -0
  55. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  56. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  57. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  58. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  59. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  60. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  61. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  62. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  63. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  64. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  65. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  66. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  67. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  68. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  69. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  70. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  71. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
  72. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  73. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  74. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  75. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  76. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  77. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  78. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  79. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  80. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  81. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  82. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  83. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  84. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  85. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  86. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  87. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  88. package/src/libs/util/shared/src/index.ts +1 -0
  89. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  90. package/src/libs/util/shared/src/lib/gn-ui-version.ts +1 -1
  91. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  92. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  93. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  94. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  95. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  96. package/tailwind.base.css +5 -0
  97. package/translations/de.json +30 -4
  98. package/translations/en.json +29 -3
  99. package/translations/es.json +27 -1
  100. package/translations/fr.json +30 -4
  101. package/translations/it.json +28 -2
  102. package/translations/nl.json +27 -1
  103. package/translations/pt.json +27 -1
  104. package/translations/sk.json +28 -2
  105. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  106. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  107. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  108. /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
@@ -1,14 +1,14 @@
1
1
  import { parseXml, XmlDocument, XmlElement, XmlText } from '@rgrove/parse-xml';
2
2
  export { XmlDocument, XmlElement } from '@rgrove/parse-xml';
3
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker';
3
4
  import GML32 from 'ol/format/GML32.js';
4
5
  import GeoJSON from 'ol/format/GeoJSON.js';
5
6
  import { parse as parse$1 } from 'ol/xml.js';
6
- import { marker } from '@biesbjerg/ngx-translate-extract-marker';
7
7
  import { format } from 'date-fns/format';
8
8
  import { Namespace, Literal, lit, parse as parse$2, sym, BlankNode, graph } from 'rdflib';
9
- import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, combineLatest, from, exhaustMap, throwError, forkJoin, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
9
+ import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, forkJoin, combineLatest, from, exhaustMap, throwError, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1, takeUntil } from 'rxjs';
10
10
  import * as i0 from '@angular/core';
11
- import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, HostListener, Input, Directive, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, afterNextRender, viewChildren } from '@angular/core';
11
+ import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, afterNextRender, booleanAttribute, Input, Directive, HostListener, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
13
13
  import * as i4 from '@ngx-translate/core';
14
14
  import { TranslateLoader, TranslateCompiler, TranslateDefaultParser, TranslateParser, TranslateService, provideTranslateService, TranslateDirective, TranslatePipe, TranslateModule } from '@ngx-translate/core';
@@ -18,7 +18,7 @@ import { map as map$1, shareReplay as shareReplay$1, catchError, tap as tap$1, f
18
18
  import { lt, valid, coerce, satisfies, ltr } from 'semver';
19
19
  import chroma from 'chroma-js';
20
20
  import * as i1$1 from '@angular/common';
21
- import { Location, CommonModule, NgClass, NgTemplateOutlet, DatePipe, AsyncPipe } from '@angular/common';
21
+ import { Location, CommonModule, NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common';
22
22
  import { formatDistance } from 'date-fns/formatDistance';
23
23
  import { Scroll, NavigationEnd, Router, RouteReuseStrategy } from '@angular/router';
24
24
  import { WmtsEndpoint, WmsEndpoint, WfsEndpoint, OgcApiEndpoint, sharedFetch, useCache, StacEndpoint, TmsEndpoint } from '@camptocamp/ogc-client';
@@ -26,7 +26,6 @@ import { getLength } from 'ol/sphere.js';
26
26
  import { LineString } from 'ol/geom.js';
27
27
  import * as i1 from '@ngrx/store';
28
28
  import { createAction, props, createReducer, on, createFeatureSelector, createSelector, Store, select, StoreModule } from '@ngrx/store';
29
- import { queryDataGouvFr, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
30
29
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
31
30
  import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, MapExtentChangeEventType, SourceLoadErrorType, computeMapContextDiff, createViewFromLayer } from '@geospatial-sdk/core';
32
31
  import { createMapFromContext, applyContextDiffToMap, listen } from '@geospatial-sdk/openlayers';
@@ -41,10 +40,10 @@ import * as TOML from '@ltd/j-toml';
41
40
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
41
  import CircleStyle from 'ol/style/Circle.js';
43
42
  import EmblaCarousel from 'embla-carousel';
44
- import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirImport, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirCheckCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
43
+ import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirTrash, iconoirSquareDashed, iconoirImport, iconoirCheckCircle, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
45
44
  import { MatButtonModule } from '@angular/material/button';
46
- import * as i1$8 from '@angular/cdk/overlay';
47
- import { OverlayContainer, ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
45
+ import * as i1$7 from '@angular/cdk/overlay';
46
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
47
  import * as i1$3 from '@angular/forms';
49
48
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
49
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +51,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
51
  import tippy from 'tippy.js';
53
52
  import * as i2 from '@angular/material/progress-spinner';
54
53
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
55
- import { matClose, matContentCopy, matExpandLess, matExpandMore, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
54
+ import { matClose, matContentCopy, matExpandMore, matExpandLess, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
56
55
  import * as i1$4 from '@angular/material/checkbox';
57
56
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
57
  import * as i1$5 from '@angular/material/tooltip';
59
58
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
59
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
60
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
61
+ import { enUS } from 'date-fns/locale/en-US';
61
62
  import * as i1$6 from '@angular/material/datepicker';
62
63
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
64
+ import * as i1$8 from 'ngx-dropzone';
65
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
66
  import * as i1$9 from '@angular/material/button-toggle';
66
67
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
68
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -72,6 +73,7 @@ import { MatTabsModule } from '@angular/material/tabs';
72
73
  import * as i2$2 from '@ngrx/effects';
73
74
  import { Actions, createEffect, ofType, EffectsModule } from '@ngrx/effects';
74
75
  import { valid as valid$1 } from 'geojson-validation';
76
+ import { queryDataGouvFr, queryGeonames, queryGeoadmin } from '@geospatial-sdk/geocoding';
75
77
  import { TemplatePortal } from '@angular/cdk/portal';
76
78
  import { parse as parse$3 } from 'marked';
77
79
  import * as i2$3 from '@angular/cdk/scrolling';
@@ -79,10 +81,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
81
  import Duration from 'duration-relativetimeformat';
80
82
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
83
  import { trigger, transition, animate, keyframes, style } from '@angular/animations';
82
- import * as Papa from 'papaparse';
83
- import { parse as parse$4 } from 'date-fns/parse';
84
- import { parseISO } from 'date-fns/parseISO';
85
- import { WFS, GeoJSON as GeoJSON$1 } from 'ol/format.js';
84
+ import * as duckdb from '@duckdb/duckdb-wasm';
85
+ import { Vector, DataType } from 'apache-arrow';
86
+ import WFS from 'ol/format/WFS';
87
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
88
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
89
  import * as i2$4 from '@angular/material/sort';
88
90
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +92,6 @@ import * as i1$b from '@angular/material/table';
90
92
  import { MatTableModule } from '@angular/material/table';
91
93
  import * as i3 from '@angular/material/paginator';
92
94
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
95
  import { Meta } from '@angular/platform-browser';
95
96
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
97
  import * as i3$1 from '@angular/material/radio';
@@ -674,6 +675,149 @@ function matchMimeType(format) {
674
675
  return format || null;
675
676
  }
676
677
 
678
+ const RoleValues = [
679
+ 'unspecified',
680
+ 'other',
681
+ 'author', // Party who authored the resource
682
+ 'collaborator', // party who assists with the generation of the resource other than the principal investigator
683
+ 'contributor', // party contributing to the resource
684
+ 'custodian', // Party that accepts accountability and responsibility for the data and ensures appropriate care and maintenance of the resource
685
+ 'distributor', // Party who distributes the resource
686
+ 'editor', // party who reviewed or modified the resource to improve the content
687
+ 'funder', // party providing monetary support for the resource
688
+ 'mediator', // a class of entity that mediates access to the resource and for whom the resource is intended or useful
689
+ 'originator', // Party who created the resource
690
+ 'owner', // Party that owns the resource
691
+ 'point_of_contact', // Party who can be contacted for acquiring knowledge about or acquisition of the resource
692
+ 'principal_investigator', // Key party responsible for gathering information and conducting research
693
+ 'processor', // Party who has processed the data in a manner such that the resource has been modified
694
+ 'publisher', // Party who published the resource
695
+ 'resource_provider', // Party that supplies the resource
696
+ 'rights_holder', // party owning or managing rights over the resource
697
+ 'sponsor', // party that sponsors the resource
698
+ 'stakeholder', // party who has an interest in the resource or the use of the resource
699
+ 'user', // Party who uses the resource
700
+ ];
701
+ const RoleLabels = new Map([
702
+ ['unspecified', marker('domain.contact.role.unspecified')],
703
+ ['other', marker('domain.contact.role.other')],
704
+ ['author', marker('domain.contact.role.author')],
705
+ ['collaborator', marker('domain.contact.role.collaborator')],
706
+ ['contributor', marker('domain.contact.role.contributor')],
707
+ ['custodian', marker('domain.contact.role.custodian')],
708
+ ['distributor', marker('domain.contact.role.distributor')],
709
+ ['editor', marker('domain.contact.role.editor')],
710
+ ['funder', marker('domain.contact.role.funder')],
711
+ ['mediator', marker('domain.contact.role.mediator')],
712
+ ['originator', marker('domain.contact.role.originator')],
713
+ ['owner', marker('domain.contact.role.owner')],
714
+ ['point_of_contact', marker('domain.contact.role.point_of_contact')],
715
+ [
716
+ 'principal_investigator',
717
+ marker('domain.contact.role.principal_investigator'),
718
+ ],
719
+ ['processor', marker('domain.contact.role.processor')],
720
+ ['publisher', marker('domain.contact.role.publisher')],
721
+ ['resource_provider', marker('domain.contact.role.resource_provider')],
722
+ ['rights_holder', marker('domain.contact.role.rights_holder')],
723
+ ['sponsor', marker('domain.contact.role.sponsor')],
724
+ ['stakeholder', marker('domain.contact.role.stakeholder')],
725
+ ['user', marker('domain.contact.role.user')],
726
+ ]);
727
+
728
+ marker('domain.record.updateFrequency.unknown');
729
+ marker('domain.record.updateFrequency.notPlanned');
730
+ marker('domain.record.updateFrequency.asNeeded');
731
+ marker('domain.record.updateFrequency.irregular');
732
+ marker('domain.record.updateFrequency.continual');
733
+ marker('domain.record.updateFrequency.periodic');
734
+ const updateFrequencyCodeValues = [
735
+ 'unknown',
736
+ 'notPlanned',
737
+ 'asNeeded',
738
+ 'irregular',
739
+ 'continual',
740
+ 'periodic',
741
+ 'daily',
742
+ 'weekly',
743
+ 'fortnightly',
744
+ 'semimonthly',
745
+ 'monthly',
746
+ 'quarterly',
747
+ 'biannually',
748
+ 'annually',
749
+ 'biennially',
750
+ ];
751
+ marker('domain.record.updateFrequency.day');
752
+ marker('domain.record.updateFrequency.week');
753
+ marker('domain.record.updateFrequency.month');
754
+ marker('domain.record.updateFrequency.year');
755
+ marker('domain.record.updateFrequency.daily');
756
+ marker('domain.record.updateFrequency.weekly');
757
+ marker('domain.record.updateFrequency.fortnightly');
758
+ marker('domain.record.updateFrequency.monthly');
759
+ marker('domain.record.updateFrequency.quarterly');
760
+ marker('domain.record.updateFrequency.biannually');
761
+ marker('domain.record.updateFrequency.annually');
762
+ marker('domain.record.updateFrequency.semimonthly');
763
+ marker('domain.record.updateFrequency.biennially');
764
+ marker('domain.record.status.completed');
765
+ marker('domain.record.status.ongoing');
766
+ marker('domain.record.status.under_development');
767
+ marker('domain.record.status.deprecated');
768
+ marker('domain.record.status.removed');
769
+ marker('domain.record.status.planned');
770
+ marker('domain.record.status.required');
771
+ const RecordStatusValues = [
772
+ 'completed',
773
+ 'ongoing',
774
+ 'under_development',
775
+ 'deprecated',
776
+ 'removed',
777
+ 'planned',
778
+ 'required',
779
+ ];
780
+ marker('record.metadata.api.accessServiceProtocol.wms');
781
+ marker('record.metadata.api.accessServiceProtocol.wfs');
782
+ marker('record.metadata.api.accessServiceProtocol.wps');
783
+ marker('record.metadata.api.accessServiceProtocol.wmts');
784
+ marker('record.metadata.api.accessServiceProtocol.esriRest');
785
+ marker('record.metadata.api.accessServiceProtocol.ogcFeatures');
786
+ marker('record.metadata.api.accessServiceProtocol.GPFDL');
787
+ marker('record.metadata.api.accessServiceProtocol.tms');
788
+ marker('record.metadata.api.accessServiceProtocol.maplibre-style');
789
+ marker('record.metadata.api.accessServiceProtocol.postgis');
790
+ marker('record.metadata.api.accessServiceProtocol.stac');
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');
801
+ // DS_AssociationTypeCode values of the ISO19115-3 codelist, any unrecognized value
802
+ // being mapped to 'crossReference'
803
+ const associationTypeValues = [
804
+ 'crossReference',
805
+ 'largerWorkCitation',
806
+ 'partOfSeamlessDatabase',
807
+ 'stereoMate',
808
+ 'isComposedOf',
809
+ 'collectiveTitle',
810
+ 'series',
811
+ 'dependency',
812
+ 'revisionOf',
813
+ ];
814
+
815
+ function getAssociationTypeFromCode(associationTypeCode) {
816
+ return associationTypeValues.includes(associationTypeCode)
817
+ ? associationTypeCode
818
+ : 'crossReference';
819
+ }
820
+
677
821
  function readGeometry(el) {
678
822
  const xmlDoc = createDocument(el);
679
823
  xmlDoc.root.attributes['xmlns'] = 'http://www.opengis.net/gml/3.2';
@@ -802,121 +946,6 @@ function getStatusFromStatusCode(statusCode) {
802
946
  }
803
947
  }
804
948
 
805
- const RoleValues = [
806
- 'unspecified',
807
- 'other',
808
- 'author', // Party who authored the resource
809
- 'collaborator', // party who assists with the generation of the resource other than the principal investigator
810
- 'contributor', // party contributing to the resource
811
- 'custodian', // Party that accepts accountability and responsibility for the data and ensures appropriate care and maintenance of the resource
812
- 'distributor', // Party who distributes the resource
813
- 'editor', // party who reviewed or modified the resource to improve the content
814
- 'funder', // party providing monetary support for the resource
815
- 'mediator', // a class of entity that mediates access to the resource and for whom the resource is intended or useful
816
- 'originator', // Party who created the resource
817
- 'owner', // Party that owns the resource
818
- 'point_of_contact', // Party who can be contacted for acquiring knowledge about or acquisition of the resource
819
- 'principal_investigator', // Key party responsible for gathering information and conducting research
820
- 'processor', // Party who has processed the data in a manner such that the resource has been modified
821
- 'publisher', // Party who published the resource
822
- 'resource_provider', // Party that supplies the resource
823
- 'rights_holder', // party owning or managing rights over the resource
824
- 'sponsor', // party that sponsors the resource
825
- 'stakeholder', // party who has an interest in the resource or the use of the resource
826
- 'user', // Party who uses the resource
827
- ];
828
- const RoleLabels = new Map([
829
- ['unspecified', marker('domain.contact.role.unspecified')],
830
- ['other', marker('domain.contact.role.other')],
831
- ['author', marker('domain.contact.role.author')],
832
- ['collaborator', marker('domain.contact.role.collaborator')],
833
- ['contributor', marker('domain.contact.role.contributor')],
834
- ['custodian', marker('domain.contact.role.custodian')],
835
- ['distributor', marker('domain.contact.role.distributor')],
836
- ['editor', marker('domain.contact.role.editor')],
837
- ['funder', marker('domain.contact.role.funder')],
838
- ['mediator', marker('domain.contact.role.mediator')],
839
- ['originator', marker('domain.contact.role.originator')],
840
- ['owner', marker('domain.contact.role.owner')],
841
- ['point_of_contact', marker('domain.contact.role.point_of_contact')],
842
- [
843
- 'principal_investigator',
844
- marker('domain.contact.role.principal_investigator'),
845
- ],
846
- ['processor', marker('domain.contact.role.processor')],
847
- ['publisher', marker('domain.contact.role.publisher')],
848
- ['resource_provider', marker('domain.contact.role.resource_provider')],
849
- ['rights_holder', marker('domain.contact.role.rights_holder')],
850
- ['sponsor', marker('domain.contact.role.sponsor')],
851
- ['stakeholder', marker('domain.contact.role.stakeholder')],
852
- ['user', marker('domain.contact.role.user')],
853
- ]);
854
-
855
- marker('domain.record.updateFrequency.unknown');
856
- marker('domain.record.updateFrequency.notPlanned');
857
- marker('domain.record.updateFrequency.asNeeded');
858
- marker('domain.record.updateFrequency.irregular');
859
- marker('domain.record.updateFrequency.continual');
860
- marker('domain.record.updateFrequency.periodic');
861
- const updateFrequencyCodeValues = [
862
- 'unknown',
863
- 'notPlanned',
864
- 'asNeeded',
865
- 'irregular',
866
- 'continual',
867
- 'periodic',
868
- 'daily',
869
- 'weekly',
870
- 'fortnightly',
871
- 'semimonthly',
872
- 'monthly',
873
- 'quarterly',
874
- 'biannually',
875
- 'annually',
876
- 'biennially',
877
- ];
878
- marker('domain.record.updateFrequency.day');
879
- marker('domain.record.updateFrequency.week');
880
- marker('domain.record.updateFrequency.month');
881
- marker('domain.record.updateFrequency.year');
882
- marker('domain.record.updateFrequency.daily');
883
- marker('domain.record.updateFrequency.weekly');
884
- marker('domain.record.updateFrequency.fortnightly');
885
- marker('domain.record.updateFrequency.monthly');
886
- marker('domain.record.updateFrequency.quarterly');
887
- marker('domain.record.updateFrequency.biannually');
888
- marker('domain.record.updateFrequency.annually');
889
- marker('domain.record.updateFrequency.semimonthly');
890
- marker('domain.record.updateFrequency.biennially');
891
- marker('domain.record.status.completed');
892
- marker('domain.record.status.ongoing');
893
- marker('domain.record.status.under_development');
894
- marker('domain.record.status.deprecated');
895
- marker('domain.record.status.removed');
896
- marker('domain.record.status.planned');
897
- marker('domain.record.status.required');
898
- const RecordStatusValues = [
899
- 'completed',
900
- 'ongoing',
901
- 'under_development',
902
- 'deprecated',
903
- 'removed',
904
- 'planned',
905
- 'required',
906
- ];
907
- marker('record.metadata.api.accessServiceProtocol.wms');
908
- marker('record.metadata.api.accessServiceProtocol.wfs');
909
- marker('record.metadata.api.accessServiceProtocol.wps');
910
- marker('record.metadata.api.accessServiceProtocol.wmts');
911
- marker('record.metadata.api.accessServiceProtocol.esriRest');
912
- marker('record.metadata.api.accessServiceProtocol.ogcFeatures');
913
- marker('record.metadata.api.accessServiceProtocol.GPFDL');
914
- marker('record.metadata.api.accessServiceProtocol.tms');
915
- marker('record.metadata.api.accessServiceProtocol.maplibre-style');
916
- marker('record.metadata.api.accessServiceProtocol.postgis');
917
- marker('record.metadata.api.accessServiceProtocol.stac');
918
- marker('record.metadata.api.accessServiceProtocol.other');
919
-
920
949
  function getUpdateFrequencyFromFrequencyCode(frequencyCode) {
921
950
  return frequencyCode &&
922
951
  updateFrequencyCodeValues.includes(frequencyCode)
@@ -1484,14 +1513,17 @@ function extractSourceRecords(liLineageEl) {
1484
1513
  function readSourceRecords$1(rootEl) {
1485
1514
  return extractSourceRecords(pipe(findNestedElement('gmd:dataQualityInfo', 'gmd:DQ_DataQuality', 'gmd:lineage', 'gmd:LI_Lineage'))(rootEl));
1486
1515
  }
1487
- function extractAssociatedRecords(containerName, aggregateName, readUuid) {
1516
+ function extractAssociatedRecords(containerName, aggregateName, readIdentifier) {
1488
1517
  return pipe(findChildrenElement(containerName, false), mapArray((el) => {
1489
1518
  const aggregateEl = findChildElement(aggregateName, false)(el);
1490
- const uuid = readUuid(aggregateEl);
1491
- const associationType = pipe(findNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'), readAttribute('codeListValue'))(aggregateEl);
1492
- if (!uuid || !associationType)
1519
+ const uniqueIdentifier = readIdentifier(aggregateEl);
1520
+ const associationTypeCode = pipe(findNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'), readAttribute('codeListValue'))(aggregateEl);
1521
+ if (!uniqueIdentifier || !associationTypeCode)
1493
1522
  return null;
1494
- return { uuid, associationType };
1523
+ return {
1524
+ uniqueIdentifier,
1525
+ associationType: getAssociationTypeFromCode(associationTypeCode),
1526
+ };
1495
1527
  }), filterArray((r) => r !== null));
1496
1528
  }
1497
1529
  function readAssociatedRecords$1(rootEl) {
@@ -2039,8 +2071,8 @@ function writeSourceRecords$1(record, rootEl) {
2039
2071
  }
2040
2072
  function writeAssociatedRecords$1(record, rootEl) {
2041
2073
  pipe(findOrCreateIdentification(), removeChildrenByName('gmd:aggregationInfo'), appendChildren(...record.associatedRecords
2042
- .filter((assoc) => assoc.uuid && assoc.associationType)
2043
- .map((assoc) => pipe(createNestedElement('gmd:aggregationInfo', 'gmd:MD_AggregateInformation'), appendChildren(pipe(createNestedElement('gmd:aggregateDataSetIdentifier', 'gmd:MD_Identifier', 'gmd:code'), writeCharacterString(assoc.uuid)), pipe(createNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'), writeAttribute('codeList', 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode'), writeAttribute('codeListValue', assoc.associationType)))))))(rootEl);
2074
+ .filter((assoc) => assoc.uniqueIdentifier && assoc.associationType)
2075
+ .map((assoc) => pipe(createNestedElement('gmd:aggregationInfo', 'gmd:MD_AggregateInformation'), appendChildren(pipe(createNestedElement('gmd:aggregateDataSetIdentifier', 'gmd:MD_Identifier', 'gmd:code'), writeCharacterString(assoc.uniqueIdentifier)), pipe(createNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'), writeAttribute('codeList', 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode'), writeAttribute('codeListValue', assoc.associationType)))))))(rootEl);
2044
2076
  }
2045
2077
  function getServiceEndpointProtocol(endpoint) {
2046
2078
  switch (endpoint.accessServiceProtocol.toLowerCase()) {
@@ -2752,8 +2784,8 @@ function writeSourceRecords(record, rootEl) {
2752
2784
  }
2753
2785
  function writeAssociatedRecords(record, rootEl) {
2754
2786
  pipe(findOrCreateIdentification(), removeChildrenByName('mri:associatedResource'), appendChildren(...record.associatedRecords
2755
- .filter((assoc) => assoc.uuid && assoc.associationType)
2756
- .map((assoc) => pipe(createNestedElement('mri:associatedResource', 'mri:MD_AssociatedResource'), appendChildren(pipe(createNestedElement('mri:associationType', 'mri:DS_AssociationTypeCode'), writeAttribute('codeList', 'http://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#DS_AssociationTypeCode'), writeAttribute('codeListValue', assoc.associationType)), pipe(createElement('mri:metadataReference'), writeAttribute('uuidref', assoc.uuid)))))))(rootEl);
2787
+ .filter((assoc) => assoc.uniqueIdentifier && assoc.associationType)
2788
+ .map((assoc) => pipe(createNestedElement('mri:associatedResource', 'mri:MD_AssociatedResource'), appendChildren(pipe(createNestedElement('mri:associationType', 'mri:DS_AssociationTypeCode'), writeAttribute('codeList', 'http://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#DS_AssociationTypeCode'), writeAttribute('codeListValue', assoc.associationType)), pipe(createElement('mri:metadataReference'), writeAttribute('uuidref', assoc.uniqueIdentifier)))))))(rootEl);
2757
2789
  }
2758
2790
 
2759
2791
  class Iso191153Converter extends Iso19139Converter {
@@ -18876,14 +18908,29 @@ class Gn4FieldMapper {
18876
18908
  featureTypes: selectField(source, 'featureTypes'),
18877
18909
  }, output),
18878
18910
  related: (output, source) => {
18879
- 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');
18880
18913
  const featureCatalogIdentifier = selectField(fcatSource, 'uuid');
18881
- const sourceOfLinks = getAsArray(selectField(selectField(source, 'related'), 'hassources'));
18914
+ const sourceOfLinks = getAsArray(selectField(related, 'hassources'));
18882
18915
  const sourceOfIdentifiers = sourceOfLinks
18883
18916
  .filter((link) => link['origin'] === 'catalog')
18884
18917
  .map((link) => {
18885
18918
  return selectField(selectField(link, '_source'), 'uuid');
18886
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));
18887
18934
  const extraValues = {};
18888
18935
  if (featureCatalogIdentifier) {
18889
18936
  extraValues.featureCatalogIdentifier = featureCatalogIdentifier;
@@ -18891,6 +18938,12 @@ class Gn4FieldMapper {
18891
18938
  if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
18892
18939
  extraValues.sourceOfIdentifiers = sourceOfIdentifiers;
18893
18940
  }
18941
+ if (associatedIdentifiers && associatedIdentifiers.length > 0) {
18942
+ extraValues.associatedIdentifiers = associatedIdentifiers;
18943
+ }
18944
+ if (siblings && siblings.length > 0) {
18945
+ extraValues.siblings = siblings;
18946
+ }
18894
18947
  return Object.keys(extraValues).length > 0
18895
18948
  ? this.addExtra(extraValues, output)
18896
18949
  : output;
@@ -19416,10 +19469,10 @@ class DateService {
19416
19469
  const dateObj = this.getDateObject(date);
19417
19470
  return { locale, dateObj };
19418
19471
  }
19419
- async getDateLocale() {
19472
+ async getDateFnsLocale() {
19420
19473
  const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE;
19421
19474
  const locales = await this.dateLocales;
19422
- return locales[lang];
19475
+ return locales[lang] ?? locales[DEFAULT_LANGUAGE];
19423
19476
  }
19424
19477
  formatDate(date, options) {
19425
19478
  const { locale, dateObj } = this.getLocaleAndDate(date);
@@ -19432,7 +19485,7 @@ class DateService {
19432
19485
  async formatRelativeDateTime(date) {
19433
19486
  const dateObj = this.getDateObject(date);
19434
19487
  const now = new Date();
19435
- const locale = await this.getDateLocale();
19488
+ const locale = await this.getDateFnsLocale();
19436
19489
  return formatDistance(dateObj, now, {
19437
19490
  addSuffix: true,
19438
19491
  locale: locale,
@@ -19467,6 +19520,18 @@ function propagateToDocumentOnly(event) {
19467
19520
  }, 0);
19468
19521
  }
19469
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
+
19470
19535
  function formatUserInfo(userInfo, displayCount = false) {
19471
19536
  const infos = (typeof userInfo === 'string' ? userInfo : '').split('|');
19472
19537
  const count = displayCount ? ` ${infos[3].split(' ')[1]}` : '';
@@ -19526,6 +19591,11 @@ function getGeometryFromGeoJSON(data) {
19526
19591
  }
19527
19592
  return null;
19528
19593
  }
19594
+ function isBoundingBox(value) {
19595
+ return (Array.isArray(value) &&
19596
+ value.length === 4 &&
19597
+ value.every((item) => typeof item === 'number'));
19598
+ }
19529
19599
  function getGeometryBoundingBox(geometry) {
19530
19600
  // use the bounding box if specified in the GeoJSON object
19531
19601
  if (geometry.bbox) {
@@ -20194,7 +20264,7 @@ function getLinkLabel(link) {
20194
20264
  const label = link.description || ('name' in link ? link.name : '');
20195
20265
  return format ? `${label} (${format})` : label;
20196
20266
  }
20197
- async function getLayers(url, serviceProtocol) {
20267
+ async function getLayers(url, serviceProtocol, deep = false) {
20198
20268
  switch (serviceProtocol) {
20199
20269
  case 'ogcFeatures': {
20200
20270
  const layers = await new OgcApiEndpoint(url).allCollections;
@@ -20202,7 +20272,10 @@ async function getLayers(url, serviceProtocol) {
20202
20272
  }
20203
20273
  case 'wfs': {
20204
20274
  const endpointWfs = await new WfsEndpoint(url).isReady();
20205
- const featureTypes = await endpointWfs.getFeatureTypes();
20275
+ const featureTypes = endpointWfs.getFeatureTypes();
20276
+ if (!deep) {
20277
+ return featureTypes;
20278
+ }
20206
20279
  const layers = (await Promise.allSettled(featureTypes.map((collection) => {
20207
20280
  return endpointWfs.getFeatureTypeFull(collection.name);
20208
20281
  })))
@@ -20212,12 +20285,14 @@ async function getLayers(url, serviceProtocol) {
20212
20285
  }
20213
20286
  case 'wms': {
20214
20287
  const endpointWms = await new WmsEndpoint(url).isReady();
20215
- const layers = (await endpointWms
20288
+ const layersSummary = endpointWms
20216
20289
  .getLayers()
20217
20290
  .flatMap(wmsLayerFlatten)
20218
- .filter((l) => l.name)).map((collection) => {
20219
- return endpointWms.getLayerByName(collection.name);
20220
- });
20291
+ .filter((l) => l.name);
20292
+ if (!deep) {
20293
+ return layersSummary;
20294
+ }
20295
+ const layers = layersSummary.map((collection) => endpointWms.getLayerByName(collection.name));
20221
20296
  return layers;
20222
20297
  }
20223
20298
  case 'wmts': {
@@ -20311,6 +20386,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20311
20386
  }]
20312
20387
  }] });
20313
20388
 
20389
+ class AutofocusDirective {
20390
+ constructor() {
20391
+ this.el = inject(ElementRef);
20392
+ this.injector = inject(Injector);
20393
+ }
20394
+ set autofocus(active) {
20395
+ if (!active)
20396
+ return;
20397
+ afterNextRender(() => this.el.nativeElement.focus(), {
20398
+ injector: this.injector,
20399
+ });
20400
+ }
20401
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20402
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.3.19", type: AutofocusDirective, isStandalone: true, selector: "[gnUiAutofocus]", inputs: { autofocus: ["gnUiAutofocus", "autofocus", booleanAttribute] }, ngImport: i0 }); }
20403
+ }
20404
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20405
+ type: Directive,
20406
+ args: [{
20407
+ selector: '[gnUiAutofocus]',
20408
+ standalone: true,
20409
+ }]
20410
+ }], propDecorators: { autofocus: [{
20411
+ type: Input,
20412
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20413
+ }] } });
20414
+
20314
20415
  class ImageFallbackDirective {
20315
20416
  constructor() {
20316
20417
  this.el = inject(ElementRef);
@@ -20338,7 +20439,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20338
20439
  }] } });
20339
20440
 
20340
20441
  var name = "geonetwork-ui";
20341
- var version = "2.10.2";
20442
+ var version = "2.11.0-dev.0ddf1324b";
20342
20443
  var engines = {
20343
20444
  node: ">=24"
20344
20445
  };
@@ -20359,6 +20460,7 @@ var peerDependencies = {
20359
20460
  "@angular/core": "19.x || 20.x || 21.x",
20360
20461
  "@angular/forms": "19.x || 20.x || 21.x",
20361
20462
  "@angular/material": "19.x || 20.x || 21.x",
20463
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20362
20464
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20363
20465
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20364
20466
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20377,11 +20479,12 @@ var peerDependencies = {
20377
20479
  };
20378
20480
  var dependencies = {
20379
20481
  "@biesbjerg/ngx-translate-extract-marker": "~1.0.0",
20380
- "@camptocamp/ogc-client": "1.3.1-dev.bb345f0",
20381
- "@geospatial-sdk/core": "0.0.5-dev.61",
20382
- "@geospatial-sdk/geocoding": "0.0.5-dev.61",
20383
- "@geospatial-sdk/legend": "0.0.5-dev.61",
20384
- "@geospatial-sdk/openlayers": "0.0.5-dev.61",
20482
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20483
+ "@duckdb/duckdb-wasm": "~1.32.0",
20484
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20485
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20486
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20487
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20385
20488
  "@ltd/j-toml": "~1.38.0",
20386
20489
  "@messageformat/core": "~3.4.0",
20387
20490
  "@ng-icons/core": "~32.5.0",
@@ -20389,7 +20492,7 @@ var dependencies = {
20389
20492
  "@ng-icons/material-icons": "~32.5.0",
20390
20493
  "@ng-icons/tabler-icons": "~32.4.0",
20391
20494
  "@rgrove/parse-xml": "4.2.0",
20392
- alasql: "~4.17.0",
20495
+ "apache-arrow": "~17.0.0",
20393
20496
  "chart.js": "~4.5.1",
20394
20497
  "chroma-js": "~3.2.0",
20395
20498
  "date-fns": "4.1.0",
@@ -20402,7 +20505,6 @@ var dependencies = {
20402
20505
  "ngx-dropzone": "~3.1.0",
20403
20506
  "ngx-translate-messageformat-compiler": "~7.2.0",
20404
20507
  ol: "~10.8.0",
20405
- papaparse: "~5.5.3",
20406
20508
  proj4: "~2.20.4",
20407
20509
  rdflib: "~2.3.5",
20408
20510
  semver: "~7.7.4",
@@ -20424,7 +20526,7 @@ var packageJson = {
20424
20526
 
20425
20527
  const GEONETWORK_UI_VERSION = packageJson.version;
20426
20528
  const GEONETWORK_UI_TAG_NAME = GEONETWORK_UI_VERSION.split('-')[1]?.startsWith('dev')
20427
- ? '2.10.x'
20529
+ ? 'main'
20428
20530
  : `v${packageJson.version}`;
20429
20531
 
20430
20532
  const ValidatorMapper = {
@@ -20600,8 +20702,8 @@ class ElasticsearchService {
20600
20702
  if (!payload.runtime_mappings)
20601
20703
  payload.runtime_mappings = {};
20602
20704
  payload.runtime_mappings[fieldName] = {
20603
- type: 'keyword',
20604
- script: this.runtimeFields[fieldName],
20705
+ type: this.runtimeFields[fieldName].type,
20706
+ script: this.runtimeFields[fieldName].script,
20605
20707
  };
20606
20708
  };
20607
20709
  const lookForField = (node) => {
@@ -20621,6 +20723,12 @@ class ElasticsearchService {
20621
20723
  (node[runtimeField] === 'asc' || node[runtimeField] === 'desc')) {
20622
20724
  addMapping(runtimeField);
20623
20725
  }
20726
+ if (runtimeField in node &&
20727
+ typeof node[runtimeField] === 'object' &&
20728
+ node[runtimeField] !== null &&
20729
+ ('gte' in node[runtimeField] || 'lte' in node[runtimeField])) {
20730
+ addMapping(runtimeField);
20731
+ }
20624
20732
  if ('query' in node &&
20625
20733
  typeof node.query === 'string' &&
20626
20734
  node.query.indexOf(runtimeField + ':') > -1) {
@@ -20637,8 +20745,8 @@ class ElasticsearchService {
20637
20745
  lookForField(payload.query);
20638
20746
  return payload;
20639
20747
  }
20640
- registerRuntimeField(fieldName, expression) {
20641
- this.runtimeFields[fieldName] = expression;
20748
+ registerRuntimeField(fieldName, expression, type = 'keyword') {
20749
+ this.runtimeFields[fieldName] = { script: expression, type };
20642
20750
  }
20643
20751
  getMetadataByIdsPayload(uuids) {
20644
20752
  return {
@@ -20647,6 +20755,7 @@ class ElasticsearchService {
20647
20755
  values: uuids,
20648
20756
  },
20649
20757
  },
20758
+ size: uuids.length,
20650
20759
  };
20651
20760
  }
20652
20761
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20697,10 +20806,28 @@ class ElasticsearchService {
20697
20806
  };
20698
20807
  }
20699
20808
  buildPayloadSort(sortBy) {
20700
- if (sortBy === null)
20809
+ if (!sortBy || sortBy.length === 0)
20701
20810
  return undefined;
20702
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy];
20703
- return fields.map((field) => ({ [field[1]]: field[0] }));
20811
+ const fields = Array.isArray(sortBy[0])
20812
+ ? sortBy
20813
+ : [sortBy];
20814
+ return fields.map((field) => {
20815
+ // Sort by nested array of dates only works with the explicit syntax
20816
+ if (field[1].endsWith('.date')) {
20817
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'));
20818
+ return {
20819
+ [field[1]]: {
20820
+ order: field[0],
20821
+ mode: field[0] === 'desc' ? 'max' : 'min',
20822
+ missing: '_last',
20823
+ nested: {
20824
+ path: nestedPath,
20825
+ },
20826
+ },
20827
+ };
20828
+ }
20829
+ return { [field[1]]: field[0] };
20830
+ });
20704
20831
  }
20705
20832
  injectLangInQueryStringFields(queryFieldsPriority) {
20706
20833
  const queryLang = this.getQueryLang();
@@ -20736,7 +20863,49 @@ class ElasticsearchService {
20736
20863
  isCurrentSearchLang() {
20737
20864
  return this.metadataLang === 'current';
20738
20865
  }
20739
- filtersToQuery(filters) {
20866
+ findSpatialFilterExtent(filters) {
20867
+ if (typeof filters === 'string') {
20868
+ return undefined;
20869
+ }
20870
+ return Object.values(filters).find(isBoundingBox);
20871
+ }
20872
+ // if a field registered min/max runtime fields, its dates form an interval
20873
+ // the record matches if that interval intersects the filter range
20874
+ buildDateRangeQuery(searchField, dateRange) {
20875
+ const minField = `${searchField}Min`;
20876
+ const maxField = `${searchField}Max`;
20877
+ if (minField in this.runtimeFields && maxField in this.runtimeFields) {
20878
+ const filter = [
20879
+ dateRange.start && {
20880
+ range: {
20881
+ [maxField]: {
20882
+ gte: formatDate(dateRange.start),
20883
+ format: 'yyyy-MM-dd',
20884
+ },
20885
+ },
20886
+ },
20887
+ dateRange.end && {
20888
+ range: {
20889
+ [minField]: {
20890
+ lte: formatDate(dateRange.end),
20891
+ format: 'yyyy-MM-dd',
20892
+ },
20893
+ },
20894
+ },
20895
+ ].filter(Boolean);
20896
+ return { bool: { filter } };
20897
+ }
20898
+ return {
20899
+ range: {
20900
+ [searchField]: {
20901
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
20902
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
20903
+ format: 'yyyy-MM-dd',
20904
+ },
20905
+ },
20906
+ };
20907
+ }
20908
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20740
20909
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20741
20910
  const makeQuery = (filter) => {
20742
20911
  if (typeof filter === 'string') {
@@ -20755,7 +20924,9 @@ class ElasticsearchService {
20755
20924
  ? filters
20756
20925
  : Object.keys(filters)
20757
20926
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20927
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20758
20928
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20929
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20759
20930
  .filter((fieldname) => filters[fieldname] &&
20760
20931
  JSON.stringify(filters[fieldname]) !== '{}')
20761
20932
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20763,31 +20934,26 @@ class ElasticsearchService {
20763
20934
  if (filters['gn-ui-crossFieldFilter']) {
20764
20935
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`;
20765
20936
  }
20766
- const queryRange = Object.entries(filters)
20767
- .filter(([, value]) => isDateRange(value))
20768
- .map(([searchField, dateRange]) => {
20769
- return {
20770
- searchField,
20771
- dateRange,
20772
- };
20773
- })[0];
20937
+ const queryRanges = Object.entries(filters).filter(([, value]) => isDateRange(value));
20774
20938
  const queryParts = [
20775
20939
  queryString && {
20776
20940
  query_string: {
20777
20941
  query: queryString,
20778
20942
  },
20779
20943
  },
20780
- queryRange &&
20781
- queryRange.dateRange && {
20782
- range: {
20783
- [queryRange.searchField]: {
20784
- ...(queryRange.dateRange.start && {
20785
- gte: formatDate(queryRange.dateRange.start),
20786
- }),
20787
- ...(queryRange.dateRange.end && {
20788
- lte: formatDate(queryRange.dateRange.end),
20789
- }),
20790
- format: 'yyyy-MM-dd',
20944
+ ...queryRanges.map(([searchField, dateRange]) => this.buildDateRangeQuery(searchField, dateRange)),
20945
+ spatialFilterExtent && {
20946
+ geo_shape: {
20947
+ geom: {
20948
+ shape: {
20949
+ type: 'envelope',
20950
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20951
+ coordinates: [
20952
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20953
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20954
+ ],
20955
+ },
20956
+ relation: 'intersects',
20791
20957
  },
20792
20958
  },
20793
20959
  },
@@ -20817,7 +20983,9 @@ class ElasticsearchService {
20817
20983
  },
20818
20984
  });
20819
20985
  }
20820
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20986
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20987
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20988
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20821
20989
  if (queryFilters) {
20822
20990
  filter.push(...queryFilters);
20823
20991
  }
@@ -20828,7 +20996,10 @@ class ElasticsearchService {
20828
20996
  },
20829
20997
  });
20830
20998
  }
20831
- if (geometry) {
20999
+ const boostGeometry = spatialFilterExtent
21000
+ ? bboxToPolygon(spatialFilterExtent)
21001
+ : geometry;
21002
+ if (boostGeometry) {
20832
21003
  // boosts applied using the filter geometry:
20833
21004
  // * records completely within the geometry receive a boost of 5
20834
21005
  // * records intersecting the geometry receive a boost of 2
@@ -20837,7 +21008,7 @@ class ElasticsearchService {
20837
21008
  should.push({
20838
21009
  geo_shape: {
20839
21010
  geom: {
20840
- shape: geometry,
21011
+ shape: boostGeometry,
20841
21012
  relation: 'within',
20842
21013
  },
20843
21014
  boost: 5.0,
@@ -20845,7 +21016,7 @@ class ElasticsearchService {
20845
21016
  }, {
20846
21017
  geo_shape: {
20847
21018
  geom: {
20848
- shape: geometry,
21019
+ shape: boostGeometry,
20849
21020
  relation: 'intersects',
20850
21021
  },
20851
21022
  boost: 2.0,
@@ -20854,7 +21025,7 @@ class ElasticsearchService {
20854
21025
  // this will boost the results variably depending on their distance from the given geometry
20855
21026
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20856
21027
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20857
- const bbox = getGeometryBoundingBox(geometry);
21028
+ const bbox = getGeometryBoundingBox(boostGeometry);
20858
21029
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20859
21030
  const northToCenter = new LineString([
20860
21031
  [center[0], bbox[3]],
@@ -21180,7 +21351,7 @@ class Gn4Repository {
21180
21351
  }
21181
21352
  getRecord(uniqueIdentifier) {
21182
21353
  return this.gn4SearchApi
21183
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21354
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21184
21355
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21185
21356
  }
21186
21357
  getMultipleRecords(uniqueIdentifiers) {
@@ -21234,19 +21405,28 @@ class Gn4Repository {
21234
21405
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21235
21406
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21236
21407
  }
21237
- getSources(record) {
21238
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21239
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21240
- return this.getMultipleRecords(sourcesIdentifiers);
21241
- }
21242
- return of(null);
21243
- }
21244
- getSourceOf(record) {
21245
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21246
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21247
- return this.getMultipleRecords(sourceOfIdentifiers);
21408
+ getLinkedRecords(record) {
21409
+ const siblings = (record.extras?.['siblings'] ?? []);
21410
+ const relations = [
21411
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21412
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21413
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21414
+ [
21415
+ 'associated',
21416
+ (record.extras?.['associatedIdentifiers'] ?? []),
21417
+ ],
21418
+ ];
21419
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21420
+ if (requested.length === 0) {
21421
+ return of([]);
21248
21422
  }
21249
- return of(null);
21423
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21424
+ record,
21425
+ relation,
21426
+ associationType: relation === 'sibling'
21427
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21428
+ : undefined,
21429
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21250
21430
  }
21251
21431
  aggregate(params) {
21252
21432
  // if aggregations are empty, return an empty object right away
@@ -22435,48 +22615,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22435
22615
  type: Injectable
22436
22616
  }] });
22437
22617
 
22438
- const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider');
22439
- class GeocodingService {
22440
- constructor() {
22441
- this.provider = inject(GEOCODING_PROVIDER);
22442
- }
22443
- query(text) {
22444
- let queryObservable;
22445
- switch (this.provider[0]) {
22446
- case 'geoadmin':
22447
- queryObservable = from(queryGeoadmin(text, this.provider[1]));
22448
- break;
22449
- case 'geonames':
22450
- queryObservable = from(queryGeonames(text, this.provider[1]));
22451
- break;
22452
- case 'data-gouv-fr':
22453
- queryObservable = from(queryDataGouvFr(text, this.provider[1]));
22454
- break;
22455
- default:
22456
- return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
22457
- }
22458
- return queryObservable.pipe(catchError((error) => throwError(error)));
22459
- }
22460
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22461
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
22462
- }
22463
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
22464
- type: Injectable,
22465
- args: [{
22466
- providedIn: 'root',
22467
- }]
22468
- }] });
22469
-
22470
22618
  class FeatureMapModule {
22471
22619
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
22472
22620
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, imports: [i1.StoreFeatureModule] }); }
22473
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [
22474
- MapFacade,
22475
- {
22476
- provide: GEOCODING_PROVIDER,
22477
- useValue: ['geonames', { maxRows: 5 }],
22478
- },
22479
- ], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22621
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, providers: [MapFacade], imports: [StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer)] }); }
22480
22622
  }
22481
22623
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureMapModule, decorators: [{
22482
22624
  type: NgModule,
@@ -22484,13 +22626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
22484
22626
  imports: [
22485
22627
  StoreModule.forFeature(MAP_FEATURE_KEY, mapReducer),
22486
22628
  ],
22487
- providers: [
22488
- MapFacade,
22489
- {
22490
- provide: GEOCODING_PROVIDER,
22491
- useValue: ['geonames', { maxRows: 5 }],
22492
- },
22493
- ],
22629
+ providers: [MapFacade],
22494
22630
  }]
22495
22631
  }] });
22496
22632
 
@@ -23225,6 +23361,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23225
23361
  'search_preset',
23226
23362
  'advanced_filters',
23227
23363
  'limit',
23364
+ 'spatial_extent_max_file_size',
23228
23365
  ], warnings, errors);
23229
23366
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23230
23367
  searchConfig =
@@ -23242,6 +23379,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23242
23379
  })),
23243
23380
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23244
23381
  LIMIT: parsedSearchSection.limit,
23382
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23245
23383
  };
23246
23384
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23247
23385
  metadataQualityConfig =
@@ -24299,7 +24437,7 @@ class AutocompleteComponent {
24299
24437
  this.enterBtnPosition = event.target['value'].length * 8 + 80;
24300
24438
  }
24301
24439
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24302
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24440
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AutocompleteComponent, isStandalone: true, selector: "gn-ui-autocomplete", inputs: { placeholder: "placeholder", enterButton: "enterButton", action: "action", value: "value", clearOnSelection: "clearOnSelection", preventCompleteOnSelection: "preventCompleteOnSelection", autoFocus: "autoFocus", minCharacterCount: "minCharacterCount", allowSubmit: "allowSubmit", forceTrackPosition: "forceTrackPosition", displayWithTemplate: "displayWithTemplate", displayWithFn: "displayWithFn" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", inputCleared: "inputCleared", isSearchActive: "isSearchActive" }, providers: [
24303
24441
  provideIcons({
24304
24442
  iconoirSearch,
24305
24443
  matClose,
@@ -24308,7 +24446,7 @@ class AutocompleteComponent {
24308
24446
  provideNgIconsConfig({
24309
24447
  size: '1.75rem',
24310
24448
  }),
24311
- ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24449
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: MatAutocompleteTrigger, descendants: true }, { propertyName: "autocomplete", first: true, predicate: MatAutocomplete, descendants: true }, { propertyName: "inputRef", first: true, predicate: ["searchInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"], dependencies: [{ kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i1$2.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i1$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i1$2.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24312
24450
  }
24313
24451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutocompleteComponent, decorators: [{
24314
24452
  type: Component,
@@ -24329,7 +24467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24329
24467
  provideNgIconsConfig({
24330
24468
  size: '1.75rem',
24331
24469
  }),
24332
- ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n {{ displayWithFnInternal(suggestion) }}\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24470
+ ], template: "<span class=\"w-full inline-block relative\">\n @if (!allowSubmit) {\n <div\n class=\"absolute inset-y-[--icon-padding] left-[--icon-padding] w-[--icon-width] pointer-events-none\"\n >\n <ng-icon name=\"iconoirSearch\" class=\"text-primary search\"></ng-icon>\n </div>\n }\n <div class=\"flex flex-row\">\n <input\n #searchInput\n type=\"text\"\n class=\"gn-ui-text-input\"\n (input)=\"handleInput($event)\"\n [placeholder]=\"placeholder\"\n [formControl]=\"control\"\n [matAutocomplete]=\"auto\"\n (keyup.enter)=\"handleSearch()\"\n [ngClass]=\"{\n 'text-primary': searchActive && enterButton,\n 'text-gray-900': !searchActive,\n 'px-[--icon-width]': !allowSubmit,\n }\"\n />\n @if (searchInput.value && displayEnterBtn) {\n <gn-ui-button\n type=\"gray\"\n extraClass=\"w-32 h-8 !opacity-100\"\n [ngStyle]=\"{ left: enterBtnPosition + 'px' }\"\n class=\"absolute\"\n disabled=\"true\"\n >\n <ng-icon\n name=\"iconoirLongArrowDownLeft\"\n class=\"!w-4 text-gray-900 font-bold\"\n ></ng-icon>\n <span translate class=\"text-bold text-gray-900 font-bold\">\n Enter to search\n </span>\n </gn-ui-button>\n }\n </div>\n @if (searchInput.value) {\n <gn-ui-button\n type=\"primary\"\n [extraClass]=\"getExtraClass()\"\n data-test=\"clear-btn\"\n aria-label=\"Clear\"\n (buttonClick)=\"clear()\"\n >\n <ng-icon class=\"text-white\" name=\"matClose\"></ng-icon>\n </gn-ui-button>\n }\n @if (allowSubmit) {\n <gn-ui-button\n type=\"light\"\n extraClass=\"border-0 border-l-[1px] border-gray-300 hover:border-gray-500 text-primary hover:text-primary-darkest focus:text-primary-darkest absolute inset-y-[--icon-padding] right-[--icon-padding]\"\n aria-label=\"Trigger search\"\n data-test=\"autocomplete-submit-btn\"\n (buttonClick)=\"handleSearch()\"\n >\n <ng-icon name=\"iconoirSearch\"></ng-icon>\n </gn-ui-button>\n }\n\n @if (error) {\n <gn-ui-popup-alert\n class=\"absolute mt-2 w-full top-[100%] left-0\"\n icon=\"matErrorOutlineOutline\"\n position=\"top\"\n type=\"warning\"\n >\n <span translate>search.autocomplete.error</span>\n {{ error }}\n </gn-ui-popup-alert>\n }\n</span>\n\n<mat-autocomplete\n #auto=\"matAutocomplete\"\n (optionSelected)=\"handleSelection($event)\"\n [displayWith]=\"displayWithFnInternal\"\n>\n @for (suggestion of suggestions$ | async; track suggestion) {\n <mat-option [value]=\"suggestion\" class=\"p-2 suggestion text-start\">\n @if (displayWithTemplate) {\n <ng-container\n *ngTemplateOutlet=\"\n displayWithTemplate;\n context: { $implicit: suggestion }\n \"\n ></ng-container>\n } @else {\n {{ displayWithFnInternal(suggestion) }}\n }\n </mat-option>\n }\n</mat-autocomplete>\n", styles: ["span{--icon-padding: var(--gn-ui-text-input-border-size, 2px);--icon-width: calc(var(--gn-ui-text-input-padding, .6em) * 2 + 1.25em)}span>*:not(gn-ui-popup-alert){font-size:var(--gn-ui-text-input-font-size, 1em)}input:placeholder-shown{text-overflow:ellipsis}gn-ui-button{--gn-ui-button-rounded: 0;--gn-ui-button-width: var(--icon-width);--gn-ui-button-padding: 0;font-size:1em}ng-icon.search{margin-top:.6rem;margin-left:.6rem}gn-ui-button:last-of-type{--gn-ui-button-rounded: 0 var(--gn-ui-text-input-rounded, .25em) var(--gn-ui-text-input-rounded, .25em) 0}.mat-mdc-option.suggestion.mat-mdc-option-active{background-color:var(--color-primary-lightest)}\n"] }]
24333
24471
  }], propDecorators: { placeholder: [{
24334
24472
  type: Input
24335
24473
  }], enterButton: [{
@@ -24367,6 +24505,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24367
24505
  }], inputRef: [{
24368
24506
  type: ViewChild,
24369
24507
  args: ['searchInput']
24508
+ }], displayWithTemplate: [{
24509
+ type: Input
24370
24510
  }], displayWithFn: [{
24371
24511
  type: Input
24372
24512
  }] } });
@@ -24499,12 +24639,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24499
24639
  type: Input
24500
24640
  }] } });
24501
24641
 
24642
+ function provideLocalizedDateAdapter() {
24643
+ return [
24644
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24645
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24646
+ {
24647
+ provide: DateAdapter,
24648
+ useFactory: () => {
24649
+ const dateService = inject(DateService);
24650
+ const adapter = new DateFnsAdapter();
24651
+ dateService
24652
+ .getDateFnsLocale()
24653
+ .then((locale) => adapter.setLocale(locale));
24654
+ return adapter;
24655
+ },
24656
+ },
24657
+ ];
24658
+ }
24659
+
24502
24660
  class DatePickerComponent {
24503
24661
  constructor() {
24504
- this.dateAdapter = inject(DateAdapter);
24505
- this.translate = inject(TranslateService);
24506
24662
  this.dateChange = new EventEmitter();
24507
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24508
24663
  }
24509
24664
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24510
24665
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DatePickerComponent, isStandalone: true, selector: "gn-ui-date-picker", inputs: { date: "date" }, outputs: { dateChange: "dateChange" }, providers: [
@@ -24512,30 +24667,19 @@ class DatePickerComponent {
24512
24667
  provideNgIconsConfig({
24513
24668
  size: '1.5rem',
24514
24669
  }),
24515
- {
24516
- provide: MAT_DATE_LOCALE,
24517
- useFactory: (locale) => locale,
24518
- },
24519
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24670
+ provideLocalizedDateAdapter(),
24671
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24520
24672
  }
24521
24673
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24522
24674
  type: Component,
24523
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24524
- MatNativeDateModule,
24525
- MatDatepickerModule,
24526
- ButtonComponent,
24527
- NgIconComponent,
24528
- ], providers: [
24675
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24529
24676
  provideIcons({ iconoirCalendar }),
24530
24677
  provideNgIconsConfig({
24531
24678
  size: '1.5rem',
24532
24679
  }),
24533
- {
24534
- provide: MAT_DATE_LOCALE,
24535
- useFactory: (locale) => locale,
24536
- },
24680
+ provideLocalizedDateAdapter(),
24537
24681
  ], template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24538
- }], ctorParameters: () => [], propDecorators: { date: [{
24682
+ }], propDecorators: { date: [{
24539
24683
  type: Input
24540
24684
  }], dateChange: [{
24541
24685
  type: Output
@@ -24543,58 +24687,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24543
24687
 
24544
24688
  class DateRangeDropdownComponent {
24545
24689
  constructor() {
24546
- this.overlayContainer = inject(OverlayContainer);
24690
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24691
+ this.dateAdapter = inject(DateAdapter);
24692
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24547
24693
  this.cdr = inject(ChangeDetectorRef);
24548
- this.startDateChange = new EventEmitter();
24549
- this.endDateChange = new EventEmitter();
24550
- this.isPickerDisplayed = false;
24694
+ this.dateRange = {};
24695
+ this.dateRangeChange = new EventEmitter();
24696
+ this.overlayPositions = [
24697
+ {
24698
+ originX: 'start',
24699
+ originY: 'bottom',
24700
+ overlayX: 'start',
24701
+ overlayY: 'top',
24702
+ offsetY: 8,
24703
+ },
24704
+ {
24705
+ originX: 'start',
24706
+ originY: 'top',
24707
+ overlayX: 'start',
24708
+ overlayY: 'bottom',
24709
+ offsetY: -8,
24710
+ },
24711
+ ];
24712
+ this.scrollStrategy = this.scrollStrategies.reposition();
24713
+ this.overlayOpen = false;
24714
+ this.expandedBound = 'start';
24715
+ this.invalidBounds = { start: false, end: false };
24716
+ // redraw when the lazily loaded UI locale reaches the adapter
24717
+ this.dateAdapter.localeChanges
24718
+ .pipe(takeUntilDestroyed())
24719
+ .subscribe(() => this.cdr.markForCheck());
24551
24720
  }
24552
- ngAfterViewChecked() {
24553
- this.checkPickerOverlay();
24721
+ get startDate() {
24722
+ return this.dateRange.start;
24554
24723
  }
24555
- checkPickerOverlay() {
24556
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24557
- setTimeout(() => {
24558
- this.isPickerDisplayed =
24559
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24560
- null;
24561
- this.cdr.detectChanges();
24562
- }, 200); // FIXME: find a better way to deal with animation delay
24724
+ get endDate() {
24725
+ return this.dateRange.end;
24726
+ }
24727
+ get selectedDatesCount() {
24728
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24729
+ }
24730
+ openOverlay() {
24731
+ this.expandedBound = 'start';
24732
+ this.overlayOpen = true;
24733
+ }
24734
+ closeOverlay() {
24735
+ this.overlayOpen = false;
24736
+ }
24737
+ toggleBound(bound) {
24738
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24739
+ }
24740
+ formatDate(date) {
24741
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24742
+ }
24743
+ onDateInput(bound, event) {
24744
+ const typedText = event.target.value.trim();
24745
+ const date = typedText
24746
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24747
+ : null;
24748
+ if (!this.isAcceptable(bound, typedText, date)) {
24749
+ // keep the text as typed and flag it; the filter stays on its last value
24750
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24751
+ return;
24752
+ }
24753
+ if (bound === 'start')
24754
+ this.setStartDate(date);
24755
+ else
24756
+ this.setEndDate(date);
24757
+ }
24758
+ normalizeDateInput(bound, event) {
24759
+ if (this.invalidBounds[bound])
24760
+ return;
24761
+ const date = bound === 'start' ? this.startDate : this.endDate;
24762
+ const input = event.target;
24763
+ input.value = date ? this.formatDate(date) : '';
24764
+ }
24765
+ isAcceptable(bound, typedText, date) {
24766
+ if (!typedText)
24767
+ return true;
24768
+ if (!date || !this.dateAdapter.isValid(date))
24769
+ return false;
24770
+ return bound === 'start'
24771
+ ? !this.endDate || date <= this.endDate
24772
+ : !this.startDate || date >= this.startDate;
24773
+ }
24774
+ setStartDate(date) {
24775
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24776
+ this.applyDateRange({ ...this.dateRange, start: date });
24777
+ this.expandedBound = 'end';
24778
+ }
24779
+ setEndDate(date) {
24780
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24781
+ this.applyDateRange({ ...this.dateRange, end: date });
24782
+ this.expandedBound = null;
24783
+ }
24784
+ clearDates(event) {
24785
+ this.invalidBounds = { start: false, end: false };
24786
+ this.expandedBound = 'start';
24787
+ this.applyDateRange({});
24788
+ propagateToDocumentOnly(event);
24789
+ }
24790
+ applyDateRange(dateRange) {
24791
+ this.dateRange = {
24792
+ ...(dateRange.start && { start: dateRange.start }),
24793
+ ...(dateRange.end && { end: dateRange.end }),
24794
+ };
24795
+ this.dateRangeChange.emit(this.dateRange);
24563
24796
  }
24564
24797
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24565
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", startDate: "startDate", endDate: "endDate" }, outputs: { startDateChange: "startDateChange", endDateChange: "endDateChange" }, providers: [
24798
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", dateRange: "dateRange" }, outputs: { dateRangeChange: "dateRangeChange" }, providers: [
24566
24799
  provideIcons({
24567
- matExpandMore,
24800
+ iconoirCalendar,
24801
+ matClose,
24568
24802
  matExpandLess,
24803
+ matExpandMore,
24569
24804
  }),
24570
- ], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"], dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }] }); }
24805
+ provideNgIconsConfig({
24806
+ size: '1.5rem',
24807
+ }),
24808
+ provideLocalizedDateAdapter(),
24809
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: AutofocusDirective, selector: "[gnUiAutofocus]", inputs: ["gnUiAutofocus"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24571
24810
  }
24572
24811
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24573
24812
  type: Component,
24574
24813
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24575
- NgIconComponent,
24576
- MatNativeDateModule,
24577
- MatDatepickerModule,
24814
+ AutofocusDirective,
24578
24815
  ButtonComponent,
24816
+ MatDatepickerModule,
24817
+ NgIcon,
24818
+ NgTemplateOutlet,
24819
+ OverlayModule,
24820
+ TranslateDirective,
24821
+ TranslatePipe,
24579
24822
  ], providers: [
24580
24823
  provideIcons({
24581
- matExpandMore,
24824
+ iconoirCalendar,
24825
+ matClose,
24582
24826
  matExpandLess,
24827
+ matExpandMore,
24583
24828
  }),
24584
- ], 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"] }]
24585
- }], propDecorators: { title: [{
24586
- type: Input
24587
- }], startDate: [{
24829
+ provideNgIconsConfig({
24830
+ size: '1.5rem',
24831
+ }),
24832
+ provideLocalizedDateAdapter(),
24833
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"] }]
24834
+ }], ctorParameters: () => [], propDecorators: { title: [{
24588
24835
  type: Input
24589
- }], endDate: [{
24836
+ }], dateRange: [{
24590
24837
  type: Input
24591
- }], startDateChange: [{
24592
- type: Output
24593
- }], endDateChange: [{
24838
+ }], dateRangeChange: [{
24594
24839
  type: Output
24595
- }], picker: [{
24840
+ }], overlayOrigin: [{
24596
24841
  type: ViewChild,
24597
- args: ['picker']
24842
+ args: ['overlayOrigin']
24598
24843
  }] } });
24599
24844
 
24600
24845
  class DateRangePickerComponent {
@@ -24608,20 +24853,17 @@ class DateRangePickerComponent {
24608
24853
  provideNgIconsConfig({
24609
24854
  size: '1.5rem',
24610
24855
  }),
24611
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24856
+ provideLocalizedDateAdapter(),
24857
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24612
24858
  }
24613
24859
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24614
24860
  type: Component,
24615
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24616
- MatNativeDateModule,
24617
- MatDatepickerModule,
24618
- ButtonComponent,
24619
- NgIconComponent,
24620
- ], providers: [
24861
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24621
24862
  provideIcons({ iconoirCalendar }),
24622
24863
  provideNgIconsConfig({
24623
24864
  size: '1.5rem',
24624
24865
  }),
24866
+ provideLocalizedDateAdapter(),
24625
24867
  ], template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24626
24868
  }], propDecorators: { startDate: [{
24627
24869
  type: Input
@@ -24639,28 +24881,68 @@ class DragAndDropFileInputComponent {
24639
24881
  constructor() {
24640
24882
  this.placeholder = placeholder;
24641
24883
  this.accept = '*';
24884
+ this.maxFileSizeMb = null;
24885
+ this.icon = null;
24886
+ this.dropzoneBackgroundColor = null;
24887
+ this.textClass = '';
24888
+ this.extraClass = '';
24889
+ this.showFileName = true;
24642
24890
  this.fileChange = new EventEmitter();
24891
+ this.errorChange = new EventEmitter();
24643
24892
  this.selectedFile = null;
24644
24893
  }
24645
24894
  get fileName() {
24646
24895
  return this.selectedFile && this.selectedFile.name;
24647
24896
  }
24897
+ get maxFileSizeBytes() {
24898
+ return typeof this.maxFileSizeMb === 'number'
24899
+ ? megabytesToBytes(this.maxFileSizeMb)
24900
+ : null;
24901
+ }
24648
24902
  selectFile(event) {
24903
+ if (event.rejectedFiles?.length) {
24904
+ const reason = event.rejectedFiles[0].reason;
24905
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24906
+ return;
24907
+ }
24649
24908
  this.selectedFile = event.addedFiles[0];
24650
24909
  this.fileChange.emit(this.selectedFile);
24651
24910
  }
24911
+ openFileSelector() {
24912
+ this.dropzone.showFileSelector();
24913
+ }
24914
+ clear() {
24915
+ this.selectedFile = null;
24916
+ }
24652
24917
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24653
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept" }, outputs: { fileChange: "fileChange" }, ngImport: i0, template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$7.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }] }); }
24918
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept", maxFileSizeMb: "maxFileSizeMb", icon: "icon", dropzoneBackgroundColor: "dropzoneBackgroundColor", textClass: "textClass", extraClass: "extraClass", showFileName: "showFileName" }, outputs: { fileChange: "fileChange", errorChange: "errorChange" }, viewQueries: [{ propertyName: "dropzone", first: true, predicate: NgxDropzoneComponent, descendants: true }], ngImport: i0, template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$8.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
24654
24919
  }
24655
24920
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24656
24921
  type: Component,
24657
- args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule], template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"] }]
24922
+ args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule, NgIcon], template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"] }]
24658
24923
  }], propDecorators: { placeholder: [{
24659
24924
  type: Input
24660
24925
  }], accept: [{
24661
24926
  type: Input
24927
+ }], maxFileSizeMb: [{
24928
+ type: Input
24929
+ }], icon: [{
24930
+ type: Input
24931
+ }], dropzoneBackgroundColor: [{
24932
+ type: Input
24933
+ }], textClass: [{
24934
+ type: Input
24935
+ }], extraClass: [{
24936
+ type: Input
24937
+ }], showFileName: [{
24938
+ type: Input
24662
24939
  }], fileChange: [{
24663
24940
  type: Output
24941
+ }], errorChange: [{
24942
+ type: Output
24943
+ }], dropzone: [{
24944
+ type: ViewChild,
24945
+ args: [NgxDropzoneComponent]
24664
24946
  }] } });
24665
24947
 
24666
24948
  class DropdownMultiselectComponent {
@@ -24709,7 +24991,7 @@ class DropdownMultiselectComponent {
24709
24991
  }
24710
24992
  setFocus() {
24711
24993
  setTimeout(() => {
24712
- this.searchFieldInput.nativeElement.focus();
24994
+ this.searchFieldInput?.nativeElement.focus();
24713
24995
  }, 0);
24714
24996
  }
24715
24997
  openOverlay() {
@@ -24815,7 +25097,10 @@ class DropdownMultiselectComponent {
24815
25097
  matExpandMore,
24816
25098
  matExpandLess,
24817
25099
  }),
24818
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25100
+ provideNgIconsConfig({
25101
+ size: '1.5rem',
25102
+ }),
25103
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-1 flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24819
25104
  }
24820
25105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24821
25106
  type: Component,
@@ -24825,7 +25110,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24825
25110
  matExpandMore,
24826
25111
  matExpandLess,
24827
25112
  }),
24828
- ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
25113
+ provideNgIconsConfig({
25114
+ size: '1.5rem',
25115
+ }),
25116
+ ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-1 flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
24829
25117
  }], propDecorators: { title: [{
24830
25118
  type: Input
24831
25119
  }], choices: [{
@@ -24997,7 +25285,7 @@ class DropdownSelectorComponent {
24997
25285
  provideNgIconsConfig({
24998
25286
  size: '1.5em',
24999
25287
  }),
25000
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25288
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25001
25289
  }
25002
25290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25003
25291
  type: Component,
@@ -25737,6 +26025,175 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25737
26025
  type: Output
25738
26026
  }] } });
25739
26027
 
26028
+ marker('search.filters.spatialExtent.import');
26029
+ marker('search.filters.spatialExtent.helpText');
26030
+ marker('search.filters.spatialExtent.error.title');
26031
+ const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile');
26032
+ const LABEL_FROM_FILE_DELETE = marker('search.filters.spatialExtent.bboxFromFileDelete');
26033
+ const LABEL_INITIAL = marker('search.filters.spatialExtent.bboxInitial');
26034
+ const LABEL_INITIAL_DELETE = marker('search.filters.spatialExtent.bboxInitialDelete');
26035
+ class SpatialExtentDropdownComponent {
26036
+ constructor() {
26037
+ this.cd = inject(ChangeDetectorRef);
26038
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26039
+ this.maxFileSizeMb = null;
26040
+ this.bboxChange = new EventEmitter();
26041
+ this.errorChange = new EventEmitter();
26042
+ this.bbox = null;
26043
+ this.fileName = '';
26044
+ this.overlayPositions = [
26045
+ {
26046
+ originX: 'start',
26047
+ originY: 'bottom',
26048
+ overlayX: 'start',
26049
+ overlayY: 'top',
26050
+ offsetY: 8,
26051
+ },
26052
+ {
26053
+ originX: 'start',
26054
+ originY: 'top',
26055
+ overlayX: 'start',
26056
+ overlayY: 'bottom',
26057
+ offsetY: -8,
26058
+ },
26059
+ ];
26060
+ this.scrollStrategy = this.scrollStrategies.reposition();
26061
+ this.overlayOpen = false;
26062
+ this.overlayMinWidth = 'none';
26063
+ this.errorKey = null;
26064
+ }
26065
+ set initialBbox(value) {
26066
+ if (!this.bbox && value) {
26067
+ this.bbox = value;
26068
+ }
26069
+ }
26070
+ get hasSelection() {
26071
+ return !!this.bbox;
26072
+ }
26073
+ get selectionLabelKey() {
26074
+ return this.fileName ? LABEL_FROM_FILE : LABEL_INITIAL;
26075
+ }
26076
+ get selectionDeleteLabelKey() {
26077
+ return this.fileName ? LABEL_FROM_FILE_DELETE : LABEL_INITIAL_DELETE;
26078
+ }
26079
+ get selectionLabelParams() {
26080
+ return { fileName: this.fileName };
26081
+ }
26082
+ openOverlay() {
26083
+ this.overlayMinWidth =
26084
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26085
+ .width + 'px';
26086
+ this.overlayOpen = true;
26087
+ }
26088
+ closeOverlay() {
26089
+ this.overlayOpen = false;
26090
+ }
26091
+ toggleOverlay() {
26092
+ if (this.overlayOpen) {
26093
+ this.closeOverlay();
26094
+ }
26095
+ else {
26096
+ this.openOverlay();
26097
+ }
26098
+ }
26099
+ async handleFileSelected(file) {
26100
+ this.errorKey = null;
26101
+ let content;
26102
+ try {
26103
+ content = await readFileAsText(file);
26104
+ const parsed = JSON.parse(content);
26105
+ const geometry = getGeometryFromGeoJSON(parsed);
26106
+ if (!geometry) {
26107
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26108
+ return;
26109
+ }
26110
+ const bbox = getGeometryBoundingBox(geometry);
26111
+ this.bbox = bbox;
26112
+ this.fileName = file.name;
26113
+ this.bboxChange.emit(bbox);
26114
+ this.cd.markForCheck();
26115
+ }
26116
+ catch {
26117
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26118
+ return;
26119
+ }
26120
+ }
26121
+ handleFileError(error) {
26122
+ if (error === 'file-too-large') {
26123
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26124
+ maxSize: this.maxFileSizeMb,
26125
+ });
26126
+ }
26127
+ else {
26128
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26129
+ }
26130
+ }
26131
+ setError(errorKey, params) {
26132
+ this.errorKey = errorKey;
26133
+ this.errorChange.emit({ key: errorKey, params });
26134
+ this.cd.markForCheck();
26135
+ }
26136
+ removeSelection(event) {
26137
+ this.bbox = null;
26138
+ this.fileName = '';
26139
+ this.errorKey = null;
26140
+ this.fileInput?.clear();
26141
+ this.bboxChange.emit(null);
26142
+ propagateToDocumentOnly(event);
26143
+ }
26144
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26145
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SpatialExtentDropdownComponent, isStandalone: true, selector: "gn-ui-spatial-extent-dropdown", inputs: { title: "title", maxFileSizeMb: "maxFileSizeMb", initialBbox: "initialBbox" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26146
+ provideIcons({
26147
+ iconoirCheckCircle,
26148
+ iconoirImport,
26149
+ iconoirSquareDashed,
26150
+ iconoirTrash,
26151
+ matClose,
26152
+ matExpandLess,
26153
+ matExpandMore,
26154
+ }),
26155
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "fileInput", first: true, predicate: DragAndDropFileInputComponent, descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26156
+ }
26157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26158
+ type: Component,
26159
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26160
+ ButtonComponent,
26161
+ NgIcon,
26162
+ OverlayModule,
26163
+ TranslatePipe,
26164
+ DragAndDropFileInputComponent,
26165
+ ], providers: [
26166
+ provideIcons({
26167
+ iconoirCheckCircle,
26168
+ iconoirImport,
26169
+ iconoirSquareDashed,
26170
+ iconoirTrash,
26171
+ matClose,
26172
+ matExpandLess,
26173
+ matExpandMore,
26174
+ }),
26175
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"selectionDeleteLabelKey | translate: selectionLabelParams\"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ selectionLabelKey | translate: selectionLabelParams }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ selectionDeleteLabelKey | translate: selectionLabelParams }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n" }]
26176
+ }], propDecorators: { title: [{
26177
+ type: Input
26178
+ }], maxFileSizeMb: [{
26179
+ type: Input
26180
+ }], initialBbox: [{
26181
+ type: Input
26182
+ }], bboxChange: [{
26183
+ type: Output
26184
+ }], errorChange: [{
26185
+ type: Output
26186
+ }], overlayOrigin: [{
26187
+ type: ViewChild,
26188
+ args: ['overlayOrigin']
26189
+ }], overlay: [{
26190
+ type: ViewChild,
26191
+ args: [CdkConnectedOverlay]
26192
+ }], fileInput: [{
26193
+ type: ViewChild,
26194
+ args: [DragAndDropFileInputComponent]
26195
+ }] } });
26196
+
25740
26197
  class CellPopinComponent {
25741
26198
  constructor() {
25742
26199
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25813,7 +26270,7 @@ class CellPopinComponent {
25813
26270
  this.isOpen = false;
25814
26271
  }
25815
26272
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25816
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
26273
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
25817
26274
  }
25818
26275
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25819
26276
  type: Component,
@@ -27435,6 +27892,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
27435
27892
  }]
27436
27893
  }] });
27437
27894
 
27895
+ const GEOCODING_PROVIDER = new InjectionToken('geocoding-provider', {
27896
+ providedIn: 'root',
27897
+ factory: () => ['geonames', { maxRows: 5 }],
27898
+ });
27899
+ class GeocodingService {
27900
+ constructor() {
27901
+ this.provider = inject(GEOCODING_PROVIDER);
27902
+ }
27903
+ query(text) {
27904
+ let queryObservable;
27905
+ switch (this.provider[0]) {
27906
+ case 'geoadmin':
27907
+ queryObservable = from(queryGeoadmin(text, this.provider[1]));
27908
+ break;
27909
+ case 'geonames':
27910
+ queryObservable = from(queryGeonames(text, this.provider[1]));
27911
+ break;
27912
+ case 'data-gouv-fr':
27913
+ queryObservable = from(queryDataGouvFr(text, this.provider[1]));
27914
+ break;
27915
+ default:
27916
+ return throwError(() => new Error(`Unsupported geocoding provider: ${this.provider[0]}`));
27917
+ }
27918
+ return queryObservable.pipe(catchError((error) => throwError(error)));
27919
+ }
27920
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
27921
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, providedIn: 'root' }); }
27922
+ }
27923
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingService, decorators: [{
27924
+ type: Injectable,
27925
+ args: [{
27926
+ providedIn: 'root',
27927
+ }]
27928
+ }] });
27929
+
27930
+ class LocationSearchComponent {
27931
+ constructor() {
27932
+ this.geocodingService = inject(GeocodingService);
27933
+ this.placeholder = '';
27934
+ this.resultSelected = new EventEmitter();
27935
+ this.displayWithFn = (item) => item.label;
27936
+ this.searchAction = (text) => this.geocodingService.query(text);
27937
+ }
27938
+ handleItemSelected(item) {
27939
+ this.resultSelected.emit(item);
27940
+ }
27941
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
27942
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: LocationSearchComponent, isStandalone: true, selector: "gn-ui-location-search", inputs: { placeholder: "placeholder", displayWithTemplate: "displayWithTemplate" }, outputs: { resultSelected: "resultSelected" }, ngImport: i0, template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
27943
+ }
27944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LocationSearchComponent, decorators: [{
27945
+ type: Component,
27946
+ args: [{ selector: 'gn-ui-location-search', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [AutocompleteComponent], template: "<gn-ui-autocomplete\n [action]=\"searchAction\"\n [displayWithFn]=\"displayWithFn\"\n [placeholder]=\"placeholder\"\n [displayWithTemplate]=\"displayWithTemplate\"\n (itemSelected)=\"handleItemSelected($event)\"\n>\n</gn-ui-autocomplete>\n" }]
27947
+ }], propDecorators: { placeholder: [{
27948
+ type: Input
27949
+ }], displayWithTemplate: [{
27950
+ type: Input
27951
+ }], resultSelected: [{
27952
+ type: Output
27953
+ }] } });
27954
+
27438
27955
  // expects the replacement key ${uuid}
27439
27956
  const RECORD_DATASET_URL_TOKEN = new InjectionToken('record-dataset-url-token');
27440
27957
  const RECORD_SERVICE_URL_TOKEN = new InjectionToken('record-service-url-token');
@@ -27903,6 +28420,45 @@ class DateRangeSearchField extends SimpleSearchField {
27903
28420
  return 'dateRange';
27904
28421
  }
27905
28422
  }
28423
+ // treats a resource's creation/revision dates as an interval (its min and max), so a record matches
28424
+ // when that interval intersects the filter range, not just when a single date falls inside it
28425
+ class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
28426
+ constructor(injector, order = 'desc') {
28427
+ super('resourceCreationRevisionDate', injector, order);
28428
+ const dateSourceFields = [
28429
+ 'creationDateForResource',
28430
+ 'revisionDateForResource',
28431
+ ];
28432
+ const collectSortedDates = dateSourceFields
28433
+ .map((field) => `if (doc.containsKey('${field}')) {
28434
+ for (def date : doc['${field}']) { dates.add(date.millis); }
28435
+ }`)
28436
+ .join('\n');
28437
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMin', `def dates = [];
28438
+ ${collectSortedDates}
28439
+ if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`, 'date');
28440
+ this.esService.registerRuntimeField('resourceCreationRevisionDateMax', `def dates = [];
28441
+ ${collectSortedDates}
28442
+ if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`, 'date');
28443
+ }
28444
+ }
28445
+ class BoundingBoxSearchField extends SimpleSearchField {
28446
+ getAvailableValues() {
28447
+ return of([]);
28448
+ }
28449
+ getFiltersForValues(values) {
28450
+ return of({
28451
+ [this.esFieldName]: values.map(Number),
28452
+ });
28453
+ }
28454
+ getValuesForFilter(filters) {
28455
+ const filter = filters[this.esFieldName];
28456
+ return of(isBoundingBox(filter) ? filter : []);
28457
+ }
28458
+ getType() {
28459
+ return 'spatialExtent';
28460
+ }
28461
+ }
27906
28462
  marker('search.filters.availableServices.view');
27907
28463
  marker('search.filters.availableServices.download');
27908
28464
  class AvailableServicesField extends SimpleSearchField {
@@ -28047,6 +28603,8 @@ marker('search.filters.producerOrg');
28047
28603
  marker('search.filters.publisherOrg');
28048
28604
  marker('search.filters.user');
28049
28605
  marker('search.filters.changeDate');
28606
+ marker('search.filters.resourceCreationRevisionDate');
28607
+ marker('search.filters.spatialExtent');
28050
28608
  class FieldsService {
28051
28609
  constructor() {
28052
28610
  this.injector = inject(Injector);
@@ -28069,7 +28627,9 @@ class FieldsService {
28069
28627
  publisherOrg: new MultilingualSearchField('distributorOrgForResourceObject', this.injector, 'asc', 'key'),
28070
28628
  user: new UserSearchField(this.injector),
28071
28629
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28630
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(this.injector, 'desc'),
28072
28631
  availableServices: new AvailableServicesField(this.injector),
28632
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28073
28633
  };
28074
28634
  }
28075
28635
  get supportedFields() {
@@ -29332,7 +29892,7 @@ class ContactPillComponent {
29332
29892
  this.overlayOpen = false;
29333
29893
  }
29334
29894
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29335
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29895
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29336
29896
  }
29337
29897
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29338
29898
  type: Component,
@@ -30058,7 +30618,7 @@ class ServiceCapabilitiesComponent {
30058
30618
  if (this.apiLinks.length > 0 &&
30059
30619
  this.apiLinks[0].accessServiceProtocol !== 'ogcFeatures') {
30060
30620
  this.loading = true;
30061
- this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol);
30621
+ this.availableLayers = await getLayers(this.apiLinks[0].url.href, this.apiLinks[0].accessServiceProtocol, true);
30062
30622
  this.loading = false;
30063
30623
  this.cdr.detectChanges();
30064
30624
  this.filteredLayers = this.availableLayers;
@@ -31254,19 +31814,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31254
31814
  type: Output
31255
31815
  }] } });
31256
31816
 
31817
+ class NotificationsService {
31818
+ constructor() {
31819
+ this.notifications$ = new BehaviorSubject([]);
31820
+ }
31821
+ showNotification(content, timeoutMs, error) {
31822
+ error && console.error(error);
31823
+ const id = Math.floor(Math.random() * 1000000);
31824
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31825
+ if (typeof timeoutMs !== 'undefined') {
31826
+ setTimeout(() => {
31827
+ this.removeNotificationById(id);
31828
+ }, timeoutMs);
31829
+ }
31830
+ return id;
31831
+ }
31832
+ removeNotificationById(id) {
31833
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31834
+ }
31835
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31836
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31837
+ }
31838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31839
+ type: Injectable,
31840
+ args: [{
31841
+ providedIn: 'root',
31842
+ }]
31843
+ }] });
31844
+
31845
+ class NotificationsContainerComponent {
31846
+ constructor() {
31847
+ this.notificationsService = inject(NotificationsService);
31848
+ }
31849
+ trackById(index, notification) {
31850
+ return notification.id;
31851
+ }
31852
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31853
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31854
+ trigger('enterExit', [
31855
+ transition(':enter', [
31856
+ animate('150ms', keyframes([
31857
+ style({ transform: 'scale(1)', opacity: 0 }),
31858
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31859
+ style({ transform: 'scale(1)', opacity: 1 }),
31860
+ ])),
31861
+ ]),
31862
+ transition(':leave', [
31863
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31864
+ ]),
31865
+ ]),
31866
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31867
+ }
31868
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31869
+ type: Component,
31870
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31871
+ trigger('enterExit', [
31872
+ transition(':enter', [
31873
+ animate('150ms', keyframes([
31874
+ style({ transform: 'scale(1)', opacity: 0 }),
31875
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31876
+ style({ transform: 'scale(1)', opacity: 1 }),
31877
+ ])),
31878
+ ]),
31879
+ transition(':leave', [
31880
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31881
+ ]),
31882
+ ]),
31883
+ ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31884
+ }] });
31885
+
31257
31886
  class FilterDropdownComponent {
31258
31887
  constructor() {
31259
31888
  this.searchFacade = inject(SearchFacade);
31260
31889
  this.searchService = inject(SearchService);
31261
31890
  this.fieldsService = inject(FieldsService);
31891
+ this.notificationsService = inject(NotificationsService);
31892
+ this.translateService = inject(TranslateService);
31893
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31262
31894
  this.selected$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$1((fieldValues) => fieldValues[this.fieldName]), filter$1((selected) => !!selected), startWith$1([]), catchError(() => of([])));
31263
- this.selectedDateRange$ = this.selected$.pipe(map$1((selectedDateRange) => selectedDateRange));
31895
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31896
+ this.selectedBoundingBox$ = this.selected$.pipe(map$1((selected) => Array.isArray(selected) && selected.length > 0
31897
+ ? selected
31898
+ : null));
31899
+ this.spatialExtentErrorNotificationId = null;
31264
31900
  }
31265
31901
  onSelectedValues(values) {
31266
31902
  this.fieldsService
31267
31903
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31268
31904
  .subscribe((filters) => this.searchService.updateFilters(filters));
31269
31905
  }
31906
+ onBboxChange(bbox) {
31907
+ this.fieldsService
31908
+ .buildFiltersFromFieldValues({
31909
+ [this.fieldName]: bbox,
31910
+ })
31911
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31912
+ this.clearSpatialExtentErrorNotification();
31913
+ }
31914
+ onSpatialExtentError(error) {
31915
+ this.clearSpatialExtentErrorNotification();
31916
+ this.spatialExtentErrorNotificationId =
31917
+ this.notificationsService.showNotification({
31918
+ type: 'error',
31919
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31920
+ text: this.translateService.instant(error.key, error.params),
31921
+ });
31922
+ }
31923
+ clearSpatialExtentErrorNotification() {
31924
+ if (this.spatialExtentErrorNotificationId === null)
31925
+ return;
31926
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31927
+ this.spatialExtentErrorNotificationId = null;
31928
+ }
31270
31929
  ngOnInit() {
31271
31930
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31272
31931
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31274,27 +31933,15 @@ class FilterDropdownComponent {
31274
31933
  value: v.value.toString(), // converting to string for the dropdown
31275
31934
  }))), catchError(() => of([])));
31276
31935
  }
31277
- onStartDateChange(start) {
31278
- if (!start)
31279
- return;
31280
- this.dateRange = { ...this.dateRange, start };
31281
- }
31282
- onEndDateChange(end) {
31283
- if (!end)
31284
- return;
31285
- this.dateRange = { ...this.dateRange, end };
31286
- if (this.dateRange.start && this.dateRange.end) {
31287
- this.fieldsService
31288
- .buildFiltersFromFieldValues({
31289
- [this.fieldName]: this.dateRange,
31290
- })
31291
- .subscribe((filters) => {
31292
- return this.searchService.updateFilters(filters);
31293
- });
31294
- }
31936
+ onDateRangeChange(dateRange) {
31937
+ this.fieldsService
31938
+ .buildFiltersFromFieldValues({
31939
+ [this.fieldName]: dateRange,
31940
+ })
31941
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31295
31942
  }
31296
31943
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31297
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31944
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb", "initialBbox"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31298
31945
  }
31299
31946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31300
31947
  type: Component,
@@ -31302,7 +31949,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31302
31949
  CommonModule,
31303
31950
  DateRangeDropdownComponent,
31304
31951
  DropdownMultiselectComponent,
31305
- ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31952
+ SpatialExtentDropdownComponent,
31953
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31306
31954
  }], propDecorators: { fieldName: [{
31307
31955
  type: Input
31308
31956
  }], title: [{
@@ -31363,7 +32011,7 @@ class FuzzySearchComponent {
31363
32011
  this.isSearchActive.emit(event);
31364
32012
  }
31365
32013
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31366
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
32014
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: FuzzySearchComponent, isStandalone: true, selector: "gn-ui-fuzzy-search", inputs: { autoFocus: "autoFocus", forceTrackPosition: "forceTrackPosition", enterButton: "enterButton", placeholder: "placeholder" }, outputs: { itemSelected: "itemSelected", inputSubmitted: "inputSubmitted", isSearchActive: "isSearchActive" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: AutocompleteComponent, descendants: true }], ngImport: i0, template: "<gn-ui-autocomplete\n [placeholder]=\"placeholder ?? ('search.field.any.placeholder' | translate)\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n (inputSubmitted)=\"handleInputSubmission($event)\"\n (inputCleared)=\"handleInputCleared()\"\n (isSearchActive)=\"handleSearchActive($event)\"\n [value]=\"searchInputValue$ | async\"\n [preventCompleteOnSelection]=\"true\"\n [autoFocus]=\"autoFocus\"\n [allowSubmit]=\"true\"\n [forceTrackPosition]=\"forceTrackPosition\"\n [enterButton]=\"enterButton\"\n></gn-ui-autocomplete>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31367
32015
  }
31368
32016
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FuzzySearchComponent, decorators: [{
31369
32017
  type: Component,
@@ -31772,11 +32420,7 @@ const SortByEnum = {
31772
32420
  RELEVANCY: ['desc', '_score'],
31773
32421
  QUALITY_SCORE: ['desc', 'qualityScore'],
31774
32422
  CHANGE_DATE: ['desc', 'changeDate'],
31775
- RESOURCE_DATES: [
31776
- ['desc', 'revisionDateForResource'],
31777
- ['desc', 'publicationDateForResource'],
31778
- ['desc', 'creationDateForResource'],
31779
- ],
32423
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
31780
32424
  };
31781
32425
 
31782
32426
  class SortByComponent {
@@ -31790,7 +32434,7 @@ class SortByComponent {
31790
32434
  },
31791
32435
  {
31792
32436
  label: marker('results.sortBy.dateStamp'),
31793
- value: SortByEnum.RESOURCE_DATES,
32437
+ value: SortByEnum.RESOURCE_DATE,
31794
32438
  },
31795
32439
  {
31796
32440
  label: marker('results.sortBy.popularity'),
@@ -31842,74 +32486,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31842
32486
  args: ['gnUiSearchStateContainer']
31843
32487
  }] } });
31844
32488
 
31845
- class NotificationsService {
31846
- constructor() {
31847
- this.notifications$ = new BehaviorSubject([]);
31848
- }
31849
- showNotification(content, timeoutMs, error) {
31850
- error && console.error(error);
31851
- const id = Math.floor(Math.random() * 1000000);
31852
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31853
- if (typeof timeoutMs === 'undefined')
31854
- return;
31855
- setTimeout(() => {
31856
- this.removeNotificationById(id);
31857
- }, timeoutMs);
31858
- }
31859
- removeNotificationById(id) {
31860
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31861
- }
31862
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31863
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31864
- }
31865
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31866
- type: Injectable,
31867
- args: [{
31868
- providedIn: 'root',
31869
- }]
31870
- }] });
31871
-
31872
- class NotificationsContainerComponent {
31873
- constructor() {
31874
- this.notificationsService = inject(NotificationsService);
31875
- }
31876
- trackById(index, notification) {
31877
- return notification.id;
31878
- }
31879
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31880
- 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: [
31881
- trigger('enterExit', [
31882
- transition(':enter', [
31883
- animate('150ms', keyframes([
31884
- style({ transform: 'scale(1)', opacity: 0 }),
31885
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31886
- style({ transform: 'scale(1)', opacity: 1 }),
31887
- ])),
31888
- ]),
31889
- transition(':leave', [
31890
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31891
- ]),
31892
- ]),
31893
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31894
- }
31895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31896
- type: Component,
31897
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31898
- trigger('enterExit', [
31899
- transition(':enter', [
31900
- animate('150ms', keyframes([
31901
- style({ transform: 'scale(1)', opacity: 0 }),
31902
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31903
- style({ transform: 'scale(1)', opacity: 1 }),
31904
- ])),
31905
- ]),
31906
- transition(':leave', [
31907
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31908
- ]),
31909
- ]),
31910
- ], 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" }]
31911
- }] });
31912
-
31913
32489
  class ResultsTableContainerComponent {
31914
32490
  constructor() {
31915
32491
  this.searchFacade = inject(SearchFacade);
@@ -31994,12 +32570,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31994
32570
 
31995
32571
  marker('search.filters.summaryLabel.user');
31996
32572
  marker('search.filters.summaryLabel.changeDate');
32573
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate');
32574
+ const OPEN_BOUND = '…';
31997
32575
  class SearchFiltersSummaryItemComponent {
31998
32576
  constructor() {
31999
32577
  this.searchFacade = inject(SearchFacade);
32000
32578
  this.searchService = inject(SearchService);
32001
32579
  this.fieldsService = inject(FieldsService);
32002
- this.datePipe = inject(DatePipe);
32580
+ this.dateService = inject(DateService);
32003
32581
  this.translate = inject(TranslateService);
32004
32582
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32005
32583
  ? fieldValues[this.fieldName]
@@ -32026,9 +32604,10 @@ class SearchFiltersSummaryItemComponent {
32026
32604
  getReadableValues(fieldValues) {
32027
32605
  return fieldValues.map((value) => {
32028
32606
  if (this.fieldType === 'dateRange') {
32607
+ const { start, end } = value;
32029
32608
  return {
32030
32609
  value,
32031
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32610
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32032
32611
  };
32033
32612
  }
32034
32613
  else if (this.fieldName === 'user') {
@@ -32039,6 +32618,15 @@ class SearchFiltersSummaryItemComponent {
32039
32618
  }
32040
32619
  });
32041
32620
  }
32621
+ formatBound(date) {
32622
+ return date
32623
+ ? this.dateService.formatDate(date, {
32624
+ day: '2-digit',
32625
+ month: '2-digit',
32626
+ year: 'numeric',
32627
+ })
32628
+ : OPEN_BOUND;
32629
+ }
32042
32630
  async removeFilterValue(fieldValue) {
32043
32631
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32044
32632
  const updatedFieldValues = currentFieldValues
@@ -32051,11 +32639,11 @@ class SearchFiltersSummaryItemComponent {
32051
32639
  .subscribe((filters) => this.searchService.updateFilters(filters));
32052
32640
  }
32053
32641
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32054
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, providers: [DatePipe], ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32642
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32055
32643
  }
32056
32644
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32057
32645
  type: Component,
32058
- args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], providers: [DatePipe], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32646
+ args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32059
32647
  }], propDecorators: { fieldName: [{
32060
32648
  type: Input
32061
32649
  }] } });
@@ -32299,11 +32887,11 @@ class AddLayerFromFileComponent {
32299
32887
  }, 5000);
32300
32888
  }
32301
32889
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32302
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept"], outputs: ["fileChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32890
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32303
32891
  }
32304
32892
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32305
32893
  type: Component,
32306
- args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32894
+ args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32307
32895
  }] });
32308
32896
 
32309
32897
  class LayersPanelComponent {
@@ -32364,95 +32952,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32364
32952
  ], template: "<div\n class=\"bg-white rounded shadow shadow-lg relative h-full w-[400px] overflow-hidden\"\n>\n <div class=\"p-3 border-b border-gray-300 flex items-center\">\n <ng-icon class=\"mr-2\" name=\"matLayersOutline\"></ng-icon>\n <span translate>map.layers.list</span>\n </div>\n <div class=\"flex flex-col px-4 divide-y divide-y-gray-50\">\n @for (layer of layers$ | async; track layer; let index = $index) {\n <div class=\"flex flex-row py-3\">\n <ng-icon class=\"-ml-2 mr-2 shrink-0\" name=\"matChevronRight\"></ng-icon>\n <span class=\"mr-2 grow\">{{ layer.label }}</span>\n <a\n href\n class=\"underline text-sm text-red-700 hover:text-red-900\"\n (click)=\"deleteLayer(index); $event.preventDefault()\"\n >\n delete\n </a>\n </div>\n }\n </div>\n <gn-ui-expandable-panel-button [titleTemplate]=\"addLayerTitle\">\n <mat-tab-group animationDuration=\"200ms\" class=\"h-full\">\n <mat-tab [label]=\"'map.add.layer.catalog' | translate\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-catalog></gn-ui-add-layer-from-catalog>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wms' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wms></gn-ui-add-layer-from-wms>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.wfs' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-wfs></gn-ui-add-layer-from-wfs>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.ogc.api' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-ogc-api\n [ogcUrl]=\"ogcUrl\"\n (layerAdded)=\"addLayer($event)\"\n ></gn-ui-add-layer-from-ogc-api>\n </div>\n </mat-tab>\n <mat-tab [label]=\"'map.add.layer.file' | translate\" bodyClass=\"h-full\">\n <div class=\"p-3\">\n <gn-ui-add-layer-from-file></gn-ui-add-layer-from-file>\n </div>\n </mat-tab>\n </mat-tab-group>\n </gn-ui-expandable-panel-button>\n</div>\n\n<ng-template #addLayerTitle>\n <ng-icon class=\"mr-4\" name=\"matAddCircleOutlineOutline\"> </ng-icon>\n <span translate>map.add.layer</span>\n</ng-template>\n" }]
32365
32953
  }] });
32366
32954
 
32367
- class GeocodingComponent {
32368
- constructor() {
32369
- this.mapFacade = inject(MapFacade);
32370
- this.geocodingService = inject(GeocodingService);
32371
- this.searchText = '';
32372
- this.results = [];
32373
- this.searchTextChanged = new Subject();
32374
- this.destroy$ = new Subject();
32375
- this.errorMessage = null;
32376
- this.searchTextChanged
32377
- .pipe(debounceTime(300), switchMap$1((searchText) => {
32378
- return from(this.geocodingService.query(searchText)).pipe(catchError$1((error) => {
32379
- this.errorMessage =
32380
- 'An error occurred while searching. Please try again.';
32381
- console.error(error);
32382
- return [];
32383
- }));
32384
- }), takeUntil(this.destroy$))
32385
- .subscribe((results) => {
32386
- this.results = results;
32387
- });
32388
- }
32389
- ngOnDestroy() {
32390
- this.destroy$.next();
32391
- this.destroy$.complete();
32392
- }
32393
- onSearchChange(searchText) {
32394
- if (!searchText) {
32395
- this.clearSearch();
32396
- return;
32397
- }
32398
- else {
32399
- this.searchTextChanged.next(searchText);
32400
- }
32401
- }
32402
- clearSearch() {
32403
- this.searchText = '';
32404
- this.results = [];
32405
- this.errorMessage = null;
32406
- }
32407
- async zoomToLocation(result) {
32408
- const geometry = result.geom;
32409
- if (geometry.type === 'Point') {
32410
- await this.zoomToPoint(geometry.coordinates);
32411
- }
32412
- else if (geometry.type === 'Polygon') {
32413
- await this.zoomToPolygon(geometry.coordinates);
32414
- }
32415
- else {
32416
- console.error(`Unsupported geometry type: ${geometry.type}`);
32417
- }
32418
- }
32419
- async zoomToPoint(pointCoords) {
32420
- const context = await firstValueFrom(this.mapFacade.context$);
32421
- const view = {
32422
- center: pointCoords,
32423
- zoom: 10,
32424
- };
32425
- this.mapFacade.applyContext({
32426
- ...context,
32427
- view,
32428
- });
32429
- }
32430
- async zoomToPolygon(polygonCoords) {
32431
- const context = await firstValueFrom(this.mapFacade.context$);
32432
- const view = {
32433
- geometry: {
32434
- type: 'Polygon',
32435
- coordinates: polygonCoords,
32436
- },
32437
- };
32438
- this.mapFacade.applyContext({
32439
- ...context,
32440
- view,
32441
- });
32442
- }
32443
- onEnterPress() {
32444
- if (this.results && this.results.length > 0) {
32445
- this.zoomToLocation(this.results[0]);
32446
- }
32447
- }
32448
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32449
- 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" }] }); }
32450
- }
32451
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GeocodingComponent, decorators: [{
32452
- type: Component,
32453
- 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" }]
32454
- }], ctorParameters: () => [] });
32455
-
32456
32955
  /*
32457
32956
  Metadata actions
32458
32957
  */
@@ -32468,8 +32967,7 @@ const closeMetadata = createAction('[Metadata view] close');
32468
32967
  Related actions
32469
32968
  */
32470
32969
  const setRelated = createAction('[Metadata view] Set related records', props());
32471
- const setSources = createAction('[Metadata view] Set sources', props());
32472
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32970
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32473
32971
  /*
32474
32972
  ChartConfig actions
32475
32973
  */
@@ -32501,9 +32999,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32501
32999
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32502
33000
  setChartConfig: setChartConfig,
32503
33001
  setIncompleteMetadata: setIncompleteMetadata,
32504
- setRelated: setRelated,
32505
- setSourceOf: setSourceOf,
32506
- setSources: setSources
33002
+ setLinkedRecords: setLinkedRecords,
33003
+ setRelated: setRelated
32507
33004
  });
32508
33005
 
32509
33006
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32546,12 +33043,9 @@ on(loadFullMetadata, (state) => ({
32546
33043
  on(setRelated, (state, { related }) => ({
32547
33044
  ...state,
32548
33045
  related,
32549
- })), on(setSources, (state, { sources }) => ({
32550
- ...state,
32551
- sources,
32552
- })), on(setSourceOf, (state, { sourceOf }) => ({
33046
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32553
33047
  ...state,
32554
- sourceOf,
33048
+ linkedRecords,
32555
33049
  })),
32556
33050
  /*
32557
33051
  ChartConfig reducers
@@ -32613,8 +33107,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32613
33107
  Related selectors
32614
33108
  */
32615
33109
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32616
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32617
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33110
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32618
33111
  /*
32619
33112
  Metadata selectors
32620
33113
  */
@@ -32715,212 +33208,10 @@ function parseHeaders(httpHeaders) {
32715
33208
  return result;
32716
33209
  }
32717
33210
 
32718
- async function inferDatasetType(url, typeHint) {
32719
- const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32720
- const fileExtension = fileExtensionMatches && fileExtensionMatches.length
32721
- ? fileExtensionMatches[1].toLowerCase()
32722
- : null;
32723
- // 1. type hint
32724
- if (typeHint)
32725
- return Promise.resolve(typeHint);
32726
- // 2. content-type header
32727
- const headers = await fetchHeaders(url);
32728
- if ('supportedType' in headers)
32729
- return headers.supportedType;
32730
- // 3. file extension from url
32731
- else if (SupportedTypes.indexOf(fileExtension) > -1)
32732
- return fileExtension;
32733
- // no type inferred or hinted
32734
- if ('mimeType' in headers)
32735
- throw FetchError.unsupportedType(headers.mimeType);
32736
- else
32737
- throw FetchError.unknownType();
33211
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33212
+ function fieldToSql(name) {
33213
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32738
33214
  }
32739
- function fetchHeaders(url) {
32740
- return sharedFetch(url, 'HEAD')
32741
- .catch((error) => {
32742
- throw FetchError.corsOrNetwork(error.message);
32743
- })
32744
- .then((response) => {
32745
- if (!response.ok) {
32746
- throw FetchError.http(response.status);
32747
- }
32748
- return parseHeaders(response.headers);
32749
- });
32750
- }
32751
- function fetchDataAsText(url, cacheActive) {
32752
- const fetchFactory = () => sharedFetch(url)
32753
- .catch((error) => {
32754
- throw FetchError.corsOrNetwork(error.message);
32755
- })
32756
- .then(async (response) => {
32757
- if (!response.ok) {
32758
- const clonedResponse = response.clone();
32759
- throw FetchError.http(response.status, await clonedResponse.text());
32760
- }
32761
- const clonedResponse = response.clone();
32762
- return clonedResponse.text();
32763
- });
32764
- return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
32765
- }
32766
- function fetchDataAsArrayBuffer(url, cacheActive) {
32767
- const fetchFactory = () => sharedFetch(url)
32768
- .catch((error) => {
32769
- throw FetchError.corsOrNetwork(error.message);
32770
- })
32771
- .then(async (response) => {
32772
- if (!response.ok) {
32773
- throw FetchError.http(response.status, await response.text());
32774
- }
32775
- // convert to a numeric array so that we can store the response in cache
32776
- return Array.from(new Uint8Array(await response.arrayBuffer()));
32777
- });
32778
- return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
32779
- return new Uint8Array(array).buffer;
32780
- });
32781
- }
32782
- function tryParseDate(input) {
32783
- if (typeof input !== 'string')
32784
- return null;
32785
- function tryIso(value) {
32786
- const parsed = parseISO(value);
32787
- return isNaN(parsed.getDate()) ? null : parsed;
32788
- }
32789
- function tryFormat(value, format) {
32790
- const parsed = parse$4(value, format, new Date());
32791
- return isNaN(parsed.getDate()) ? null : parsed;
32792
- }
32793
- return (tryIso(input) ||
32794
- tryFormat(input, 'dd/MM/yyyy') ||
32795
- tryFormat(input, 'dd.MM.yyyy') ||
32796
- tryFormat(input, 'MM/dd/yyyy') ||
32797
- null);
32798
- }
32799
- function tryParseNumber(input) {
32800
- if (isNaN(input))
32801
- return null;
32802
- const parsed = parseFloat(input);
32803
- return isNaN(parsed) ? null : parsed;
32804
- }
32805
- function jsonToGeojsonFeature(object) {
32806
- const { id, properties } = Object.keys(object)
32807
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32808
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32809
- ? {
32810
- ...prev,
32811
- id: object[curr],
32812
- }
32813
- : {
32814
- ...prev,
32815
- properties: { ...prev.properties, [curr]: object[curr] },
32816
- }, { id: undefined, properties: {} });
32817
- return {
32818
- type: 'Feature',
32819
- geometry: null,
32820
- properties,
32821
- ...(id !== undefined && { id }),
32822
- };
32823
- }
32824
- function mutateProperties(items, mutators) {
32825
- const mutatorKeys = Object.keys(mutators);
32826
- for (let i = 0, ii = items.length; i < ii; i++) {
32827
- const item = items[i];
32828
- for (const mutatorField of mutatorKeys) {
32829
- if (!(mutatorField in item.properties))
32830
- continue;
32831
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32832
- }
32833
- }
32834
- return items;
32835
- }
32836
- const SAMPLE_SIZE = 20;
32837
- /**
32838
- * This will infer field types from a list of data items and cast the values accordingly
32839
- * @param items
32840
- * @param inferTypes
32841
- */
32842
- function processItemProperties(items, inferTypes = false) {
32843
- const foundFields = {};
32844
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32845
- const item = items[i];
32846
- const fields = Object.keys(item.properties);
32847
- for (const field of fields) {
32848
- if (!(field in foundFields)) {
32849
- foundFields[field] = {
32850
- label: field,
32851
- name: field,
32852
- type: null,
32853
- };
32854
- }
32855
- const value = item.properties[field];
32856
- const info = foundFields[field];
32857
- if (value === undefined || value === '' || value === null)
32858
- continue;
32859
- if (!inferTypes) {
32860
- if (info.type === null && typeof value === 'number') {
32861
- info.type = 'number';
32862
- }
32863
- else if (info.type === 'number' && typeof value !== 'number') {
32864
- info.type = 'string';
32865
- }
32866
- continue;
32867
- }
32868
- const parsedNumber = tryParseNumber(value);
32869
- if (info.type === null && parsedNumber !== null) {
32870
- info.type = 'number';
32871
- continue;
32872
- }
32873
- else if (info.type === 'number' && parsedNumber === null) {
32874
- info.type = 'string';
32875
- continue;
32876
- }
32877
- const parsedDate = tryParseDate(value);
32878
- if (info.type === null && parsedDate !== null) {
32879
- info.type = 'date';
32880
- }
32881
- else if (info.type === 'date' && parsedDate === null) {
32882
- info.type = 'string';
32883
- }
32884
- }
32885
- }
32886
- const properties = [];
32887
- const mutators = {};
32888
- for (const field in foundFields) {
32889
- const info = foundFields[field];
32890
- if (info.type === 'number') {
32891
- mutators[field] = tryParseNumber;
32892
- }
32893
- else if (info.type === 'date') {
32894
- mutators[field] = tryParseDate;
32895
- }
32896
- properties.push({ ...info, type: info.type || 'string' });
32897
- }
32898
- if (inferTypes) {
32899
- mutateProperties(items, mutators);
32900
- }
32901
- return { items, properties };
32902
- }
32903
- /**
32904
- * This creates a Proxy that allows reading and writing to the data item properties
32905
- * as if it was a simple array of JSON objects
32906
- * @param items
32907
- */
32908
- function getJsonDataItemsProxy(items) {
32909
- return new Proxy(items, {
32910
- get(target, p) {
32911
- if (typeof p === 'string' &&
32912
- !Number.isNaN(parseInt(p)) &&
32913
- target[p]?.properties) {
32914
- return target[p].properties;
32915
- }
32916
- return target[p];
32917
- },
32918
- set() {
32919
- throw new Error('This object is read-only');
32920
- },
32921
- });
32922
- }
32923
-
32924
33215
  function filterToSql(filter) {
32925
33216
  const operator = filter[0];
32926
33217
  const args = filter.slice(1);
@@ -32935,10 +33226,10 @@ function filterToSql(filter) {
32935
33226
  case '=':
32936
33227
  case '!=':
32937
33228
  case 'like':
32938
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33229
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32939
33230
  case 'in': {
32940
33231
  const values = args.slice(1);
32941
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33232
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32942
33233
  }
32943
33234
  case 'and':
32944
33235
  case 'or': {
@@ -32957,17 +33248,18 @@ function aggregationToSql(aggregation) {
32957
33248
  const field = aggregation[1];
32958
33249
  switch (operation) {
32959
33250
  case 'average':
32960
- return `AVG([${field}]) as [average(${field})]`;
33251
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
32961
33252
  case 'sum':
32962
33253
  case 'max':
32963
33254
  case 'min':
32964
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33255
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
32965
33256
  case 'count':
32966
- return 'COUNT(*) as [count()]';
33257
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
32967
33258
  }
32968
33259
  }
32969
33260
  /**
32970
33261
  * Leave arguments at null if not used
33262
+ * @param tableName
32971
33263
  * @param selected
32972
33264
  * @param filter
32973
33265
  * @param sort
@@ -32976,22 +33268,25 @@ function aggregationToSql(aggregation) {
32976
33268
  * @param groupBy
32977
33269
  * @param aggregations
32978
33270
  */
32979
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33271
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
32980
33272
  let sqlSelect = 'SELECT *';
32981
- const sqlFrom = ' FROM ?';
33273
+ const sqlFrom = ` FROM ${tableName}`;
32982
33274
  let sqlOrderBy = '';
32983
33275
  let sqlWhere = '';
32984
33276
  let sqlLimit = '';
32985
33277
  let sqlGroupBy = '';
32986
33278
  if (selected !== null) {
32987
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33279
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33280
+ }
33281
+ if (geometryColumn !== null) {
33282
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
32988
33283
  }
32989
33284
  if (filter !== null) {
32990
33285
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
32991
33286
  }
32992
33287
  if (sort?.length) {
32993
33288
  sqlOrderBy = ` ORDER BY ${sort
32994
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33289
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
32995
33290
  .join(', ')}`;
32996
33291
  }
32997
33292
  if (startIndex !== null && count !== null) {
@@ -33001,17 +33296,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33001
33296
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33002
33297
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33003
33298
  const sqlGroupByFields = groupedByDistinct
33004
- .map((group) => `[${group[1]}]`)
33299
+ .map((group) => fieldToSql(group[1]))
33005
33300
  .join(', ');
33006
33301
  const sqlGroupBySelect = groupedByDistinct
33007
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33302
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33008
33303
  .join(', ');
33009
33304
  if (sqlGroupByFields && sqlGroupBySelect) {
33010
33305
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33011
33306
  sqlSelect += `, ${sqlGroupBySelect}`;
33012
33307
  }
33013
33308
  }
33014
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33309
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33310
+ }
33311
+
33312
+ function arrowTableToDataItems(table) {
33313
+ const fields = table.schema.fields;
33314
+ return table.toArray().map((row) => {
33315
+ const rowJson = row.toJSON();
33316
+ const feature = {
33317
+ type: 'Feature',
33318
+ geometry: null,
33319
+ properties: {},
33320
+ };
33321
+ const keys = Object.keys(rowJson);
33322
+ for (let i = 0; i < keys.length; i++) {
33323
+ const key = keys[i];
33324
+ const dataType = fields[i].type;
33325
+ let value = rowJson[key];
33326
+ // this might happen if we get an array inside a field
33327
+ if (value instanceof Vector) {
33328
+ value = Array.from(value);
33329
+ }
33330
+ // cast bigints to ints
33331
+ if (typeof value === 'bigint') {
33332
+ value = Number(value);
33333
+ }
33334
+ // rename columns with empty name
33335
+ if (!key) {
33336
+ feature.properties['unknown'] = value;
33337
+ continue;
33338
+ }
33339
+ // assign properties that look like an id to the geojson `id` field
33340
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33341
+ (typeof value == 'string' || typeof value === 'number')) {
33342
+ feature.id = value;
33343
+ }
33344
+ // if a date is in timestamp (number) format, convert it to native
33345
+ if (typeof value === 'number' &&
33346
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33347
+ value = new Date(value);
33348
+ }
33349
+ // if a binary field (most likely a geometry column): skip
33350
+ if (DataType.isBinary(dataType)) {
33351
+ continue;
33352
+ }
33353
+ // geometry column
33354
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33355
+ feature.geometry = JSON.parse(value);
33356
+ continue;
33357
+ }
33358
+ feature.properties[key] = value;
33359
+ }
33360
+ return feature;
33361
+ });
33362
+ }
33363
+
33364
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33365
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33366
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33367
+ const typesMapping = {
33368
+ INTEGER: 'number',
33369
+ SMALLINT: 'number',
33370
+ TINYINT: 'number',
33371
+ BIGINT: 'number',
33372
+ HUGEINT: 'number',
33373
+ UINTEGER: 'number',
33374
+ USMALLINT: 'number',
33375
+ UTINYINT: 'number',
33376
+ UBIGINT: 'number',
33377
+ UHUGEINT: 'number',
33378
+ DOUBLE: 'number',
33379
+ BIGNUM: 'number',
33380
+ DECIMAL: 'number',
33381
+ NUMERIC: 'number',
33382
+ FLOAT: 'number',
33383
+ REAL: 'number',
33384
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33385
+ TIMESTAMP: 'date',
33386
+ DATE: 'date',
33387
+ CHAR: 'string',
33388
+ VARCHAR: 'string',
33389
+ TEXT: 'string',
33390
+ UUID: 'string',
33391
+ BLOB: 'string',
33392
+ BIT: 'string',
33393
+ INTERVAL: 'string',
33394
+ LIST: 'string',
33395
+ BOOLEAN: 'boolean',
33396
+ };
33397
+ class Engine {
33398
+ async makeInit() {
33399
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33400
+ let worker_url = bundle.mainWorker;
33401
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33402
+ if (worker_url.startsWith('https://')) {
33403
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33404
+ type: 'text/javascript',
33405
+ }));
33406
+ }
33407
+ const worker = new Worker(worker_url);
33408
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33409
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33410
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33411
+ // setup duckdb options
33412
+ const conn = await this.db.connect();
33413
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33414
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33415
+ await conn.query(`SET TimeZone = '${timezone}';`);
33416
+ await conn.query(`SET threads = 1;`);
33417
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33418
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33419
+ conn.close();
33420
+ return this;
33421
+ }
33422
+ isReady() {
33423
+ if (!this.init_) {
33424
+ this.init_ = this.makeInit();
33425
+ }
33426
+ return this.init_;
33427
+ }
33428
+ /**
33429
+ * Returns information about a dataset once it's loaded:
33430
+ * - a list of properties description
33431
+ * - the name of the dataset geometry column (null if no geometry present)
33432
+ * @param datasetId name of the table under which the dataset will be stored
33433
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33434
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33435
+ */
33436
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33437
+ const conn = await this.db.connect();
33438
+ let results;
33439
+ // either we want to recreate the table, or we keep it if it already exists
33440
+ const safeLoadQuery = forceReload
33441
+ ? `DROP TABLE IF EXISTS ${datasetId};
33442
+ ${loadQuery}`
33443
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33444
+ // create the table
33445
+ try {
33446
+ results = await conn.query(safeLoadQuery);
33447
+ }
33448
+ catch (e) {
33449
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33450
+ }
33451
+ // read rows count
33452
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33453
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33454
+ // read columns
33455
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33456
+ let geometryColumn = null;
33457
+ const properties = results
33458
+ .toArray()
33459
+ .map((row) => {
33460
+ const rowObj = row.toJSON();
33461
+ if (rowObj['data_type'] === 'GEOMETRY') {
33462
+ // the geometry is not part of the properties
33463
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33464
+ // all get discarded
33465
+ geometryColumn = rowObj['column_name'];
33466
+ return null;
33467
+ }
33468
+ return {
33469
+ name: rowObj['column_name'],
33470
+ label: rowObj['column_name'],
33471
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33472
+ };
33473
+ })
33474
+ .filter((prop) => prop !== null);
33475
+ conn.close();
33476
+ return {
33477
+ properties,
33478
+ geometryColumn,
33479
+ rowsCount: Number(recordsCount),
33480
+ };
33481
+ }
33482
+ // register a Uint8 buffer using a handle in the duckdb instance
33483
+ async registerData(name, buffer) {
33484
+ return this.db.registerFileBuffer(name, buffer);
33485
+ }
33486
+ /**
33487
+ * @param query duckdb-specific query for fetching items
33488
+ */
33489
+ async queryItems(query) {
33490
+ const conn = await this.db.connect();
33491
+ const results = await conn.query(query);
33492
+ conn.close();
33493
+ return arrowTableToDataItems(results);
33494
+ }
33495
+ close() {
33496
+ this.db?.terminate();
33497
+ }
33498
+ }
33499
+ let engine = null;
33500
+ async function getEngine() {
33501
+ if (!engine) {
33502
+ engine = new Engine();
33503
+ }
33504
+ return engine.isReady();
33015
33505
  }
33016
33506
 
33017
33507
  class BaseReader {
@@ -33024,10 +33514,18 @@ class BaseReader {
33024
33514
  this.sort = null;
33025
33515
  this.startIndex = null;
33026
33516
  this.count = null;
33517
+ this.loadPromise_ = Promise.resolve();
33518
+ this.cacheEnabled = false;
33519
+ }
33520
+ enableCache(enabled) {
33521
+ this.cacheEnabled = enabled;
33027
33522
  }
33028
33523
  load() {
33029
33524
  throw new Error('not implemented');
33030
33525
  }
33526
+ get isLoaded() {
33527
+ return this.loadPromise_;
33528
+ }
33031
33529
  get properties() {
33032
33530
  throw new Error('not implemented');
33033
33531
  }
@@ -33076,178 +33574,237 @@ class BaseReader {
33076
33574
  }
33077
33575
  }
33078
33576
 
33079
- class BaseCacheReader extends BaseReader {
33080
- constructor(url, cacheActive = true) {
33081
- super(url);
33082
- this.url = url;
33083
- this.cacheActive = cacheActive;
33084
- }
33085
- setCacheActive(value) {
33086
- this.cacheActive = value;
33087
- }
33088
- }
33089
-
33090
- class BaseFileReader extends BaseCacheReader {
33091
- getData() {
33577
+ /**
33578
+ * This reader handles file formats supported natively by DuckDB
33579
+ */
33580
+ class BaseFileReader extends BaseReader {
33581
+ // a table id should not exceed 63 chars
33582
+ generateDatasetId() {
33583
+ // generate a hash out of the url
33584
+ let hash = 0;
33585
+ for (const char of this.url) {
33586
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33587
+ hash = hash >>> 0; // make it unsigned
33588
+ }
33589
+ return `datafetcher_${hash.toString(16)}`;
33590
+ }
33591
+ async getLoadQuery() {
33092
33592
  throw new Error('not implemented');
33093
33593
  }
33094
- load() {
33095
- this.parseResult_ = this.getData();
33594
+ async load() {
33595
+ this.datasetId = this.generateDatasetId();
33596
+ this.loadPromise_ = getEngine()
33597
+ .then((engine) => {
33598
+ this.engine = engine;
33599
+ return this.getLoadQuery();
33600
+ })
33601
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33602
+ .then((datasetInfo) => {
33603
+ this.properties_ = datasetInfo.properties;
33604
+ this.geometryColumn = datasetInfo.geometryColumn;
33605
+ this.rowsCount = datasetInfo.rowsCount;
33606
+ // returns void for the loaded promise
33607
+ });
33096
33608
  }
33097
33609
  get properties() {
33098
- return this.parseResult_.then((result) => result.properties);
33610
+ return this.isLoaded.then(() => this.properties_);
33099
33611
  }
33100
33612
  get info() {
33101
- return this.parseResult_.then((result) => ({
33102
- itemsCount: result.items.length,
33613
+ return this.isLoaded.then(() => ({
33614
+ itemsCount: this.rowsCount,
33615
+ hasGeometry: !!this.geometryColumn,
33103
33616
  }));
33104
33617
  }
33105
33618
  async read() {
33106
- const items = (await this.parseResult_).items;
33107
- // no query defined: return the full results as is
33108
- if (this.groupedBy == null &&
33109
- this.aggregations == null &&
33110
- this.selected == null &&
33111
- this.sort == null &&
33112
- this.filter == null &&
33113
- this.startIndex == null &&
33114
- this.count == null) {
33115
- return items;
33116
- }
33117
- const jsonItems = getJsonDataItemsProxy(items);
33118
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33119
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33120
- return result.map(jsonToGeojsonFeature);
33121
- }
33122
- }
33123
-
33124
- function parseCsv(text) {
33125
- // first parse the header to guess the delimiter
33126
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33127
- let delimiter;
33128
- try {
33129
- const header = text.split('\n')[0];
33130
- const result = Papa.parse(header, {
33131
- header: false,
33132
- });
33133
- delimiter = result.meta.delimiter;
33134
- }
33135
- catch (e) {
33136
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33137
- }
33138
- const parsed = Papa.parse(text, {
33139
- header: true,
33140
- skipEmptyLines: true,
33141
- delimiter,
33142
- });
33143
- if (parsed.errors.length) {
33144
- throw new Error('CSV parsing failed for the following reasons:\n' +
33145
- parsed.errors
33146
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33147
- .join('\n'));
33619
+ await this.isLoaded;
33620
+ // if only certain fields are selected, omit the geometry
33621
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33622
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33623
+ return this.engine.queryItems(query);
33148
33624
  }
33149
- const items = parsed.data.map(jsonToGeojsonFeature);
33150
- return processItemProperties(items, true);
33151
33625
  }
33626
+
33152
33627
  class CsvReader extends BaseFileReader {
33153
- getData() {
33154
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33628
+ async getLoadQuery() {
33629
+ // first we get a list of columns hich have a detected type of VARCHAR
33630
+ // then we make sure that for those columns, we don't cast an empty string to null; instead we want to keep the empty string
33631
+ return `
33632
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33633
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33634
+ force_not_null = getvariable('strColumns'),
33635
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33636
+ );`;
33155
33637
  }
33156
33638
  }
33157
33639
 
33158
- /**
33159
- * This parser only supports arrays of simple flat objects with properties
33160
- * @param text
33161
- */
33162
- function parseJson(text) {
33163
- const parsed = JSON.parse(text);
33164
- if (!Array.isArray(parsed)) {
33165
- throw new Error('Could not parse JSON, expected an array at root level');
33166
- }
33167
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33168
- }
33169
33640
  class JsonReader extends BaseFileReader {
33170
- getData() {
33171
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33641
+ async getLoadQuery() {
33642
+ return `
33643
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33644
+ maximum_object_size = 536870912 -- 500MB
33645
+ );`;
33172
33646
  }
33173
33647
  }
33174
33648
 
33175
- /**
33176
- * This parser supports both Geojson Feature collections or arrays
33177
- * of Features
33178
- * @param text
33179
- */
33180
- function parseGeojson(text) {
33181
- const parsed = JSON.parse(text);
33182
- const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33183
- if (!Array.isArray(features)) {
33184
- throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33185
- }
33186
- return processItemProperties(features);
33187
- }
33188
33649
  class GeojsonReader extends BaseFileReader {
33189
- getData() {
33190
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33650
+ async getLoadQuery() {
33651
+ return `
33652
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33653
+ allowed_drivers = ['GeoJSON']
33654
+ );`;
33191
33655
  }
33192
33656
  }
33193
33657
 
33658
+ async function inferDatasetType(url, typeHint) {
33659
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33660
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33661
+ ? fileExtensionMatches[1].toLowerCase()
33662
+ : null;
33663
+ // 1. type hint
33664
+ if (typeHint)
33665
+ return Promise.resolve(typeHint);
33666
+ // 2. content-type header
33667
+ const headers = await fetchHeaders(url);
33668
+ if ('supportedType' in headers)
33669
+ return headers.supportedType;
33670
+ // 3. file extension from url
33671
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33672
+ return fileExtension;
33673
+ // no type inferred or hinted
33674
+ if ('mimeType' in headers)
33675
+ throw FetchError.unsupportedType(headers.mimeType);
33676
+ else
33677
+ throw FetchError.unknownType();
33678
+ }
33679
+ function fetchHeaders(url) {
33680
+ return sharedFetch(url, 'HEAD')
33681
+ .catch((error) => {
33682
+ throw FetchError.corsOrNetwork(error.message);
33683
+ })
33684
+ .then((response) => {
33685
+ if (!response.ok) {
33686
+ throw FetchError.http(response.status);
33687
+ }
33688
+ return parseHeaders(response.headers);
33689
+ });
33690
+ }
33691
+ function fetchDataAsText(url, cacheActive) {
33692
+ const fetchFactory = () => sharedFetch(url)
33693
+ .catch((error) => {
33694
+ throw FetchError.corsOrNetwork(error.message);
33695
+ })
33696
+ .then(async (response) => {
33697
+ if (!response.ok) {
33698
+ const clonedResponse = response.clone();
33699
+ throw FetchError.http(response.status, await clonedResponse.text());
33700
+ }
33701
+ const clonedResponse = response.clone();
33702
+ return clonedResponse.text();
33703
+ });
33704
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33705
+ }
33706
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33707
+ const fetchFactory = () => sharedFetch(url)
33708
+ .catch((error) => {
33709
+ throw FetchError.corsOrNetwork(error.message);
33710
+ })
33711
+ .then(async (response) => {
33712
+ if (!response.ok) {
33713
+ throw FetchError.http(response.status, await response.text());
33714
+ }
33715
+ // convert to a numeric array so that we can store the response in cache
33716
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33717
+ });
33718
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33719
+ return new Uint8Array(array).buffer;
33720
+ });
33721
+ }
33194
33722
  /**
33195
- * This will read the first sheet of the excel workbook and expect the first
33196
- * line to contain the properties names
33197
- * @param buffer
33723
+ * This creates a Proxy that allows reading and writing to the data item properties
33724
+ * as if it was a simple array of JSON objects
33725
+ * @param items
33198
33726
  */
33199
- function parseExcel(buffer) {
33200
- return import('xlsx').then(({ read, utils }) => {
33201
- const workbook = read(buffer);
33202
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33203
- let json = utils.sheet_to_json(sheet);
33204
- if (!json.length) {
33205
- json = [];
33206
- }
33207
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33727
+ function getJsonDataItemsProxy(items) {
33728
+ return new Proxy(items, {
33729
+ get(target, p) {
33730
+ if (typeof p === 'string' &&
33731
+ !Number.isNaN(parseInt(p)) &&
33732
+ target[p]?.properties) {
33733
+ return target[p].properties;
33734
+ }
33735
+ return target[p];
33736
+ },
33737
+ set() {
33738
+ throw new Error('This object is read-only');
33739
+ },
33208
33740
  });
33209
33741
  }
33742
+
33210
33743
  class ExcelReader extends BaseFileReader {
33211
- getData() {
33212
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33744
+ async getLoadQuery() {
33745
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33746
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33747
+ const bufferHandle = `B${this.datasetId}`;
33748
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33749
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33750
+ const isXls = Array.from(magicNumber)
33751
+ .map((n) => n.toString(16).toUpperCase())
33752
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33753
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33754
+ if (isXls) {
33755
+ buffer = await import('xlsx').then(({ read, utils }) => {
33756
+ const workbook = read(buffer);
33757
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33758
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33759
+ });
33760
+ }
33761
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33762
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33763
+ return `
33764
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33213
33765
  }
33214
33766
  }
33215
33767
 
33216
- function parseGml(text, namespace, version) {
33217
- const splittedNamespace = namespace.split(':');
33218
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33768
+ class GmlReader extends BaseFileReader {
33769
+ async getLoadQuery() {
33770
+ return `
33771
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33772
+ allowed_drivers = ['GML']
33773
+ );`;
33774
+ }
33775
+ }
33776
+
33777
+ const formatGeojson = new GeoJSON$1();
33778
+ function parseGeojson(text) {
33779
+ const parsed = JSON.parse(text);
33780
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33781
+ if (!Array.isArray(features)) {
33782
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33783
+ }
33784
+ return features;
33785
+ }
33786
+ function parseGml(text, featureType, version) {
33787
+ const parts = featureType.split(':');
33788
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33219
33789
  const match = regex.exec(text);
33220
33790
  if (match && match.length >= 2) {
33221
- const wf = new WFS({
33791
+ const wfs = new WFS({
33222
33792
  featureNS: match[1],
33223
- featureType: splittedNamespace[1],
33793
+ featureType: parts[1],
33224
33794
  version: version,
33225
33795
  });
33226
33796
  let features;
33227
33797
  try {
33228
- features = wf.readFeatures(text);
33798
+ features = wfs.readFeatures(text);
33229
33799
  }
33230
33800
  catch (e) {
33231
- throw Error("Couldn't parse WFS with GML features");
33801
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33232
33802
  }
33233
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33234
- return processItemProperties(geojsonItem.features, true);
33803
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33804
+ return geojsonItem.features;
33235
33805
  }
33236
33806
  throw Error("Couldn't retrieve namespace url");
33237
33807
  }
33238
- class GmlReader extends BaseFileReader {
33239
- constructor(url, namespace, version, cacheActive = true) {
33240
- super(url);
33241
- this.url = url;
33242
- this.namespace = namespace;
33243
- this.version = version;
33244
- this.cacheActive = cacheActive;
33245
- }
33246
- getData() {
33247
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33248
- }
33249
- }
33250
-
33251
33808
  async function getWfsEndpoint(wfsUrl) {
33252
33809
  try {
33253
33810
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33275,21 +33832,65 @@ async function getWfsEndpoint(wfsUrl) {
33275
33832
  }
33276
33833
  }
33277
33834
  }
33278
- class WfsReader extends BaseCacheReader {
33279
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33280
- super(url, cacheActive);
33281
- this.endpoint = wfsEndpoint;
33282
- this.featureTypeName = featureTypeName;
33283
- this.version = this.endpoint.getVersion();
33835
+ class WfsReader extends BaseReader {
33836
+ constructor(url, featureTypeName) {
33837
+ super(url);
33838
+ this.endpoint = getWfsEndpoint(url);
33839
+ this.featureType = this.endpoint
33840
+ .then((endpoint) => {
33841
+ const featureTypes = endpoint.getFeatureTypes();
33842
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33843
+ ? featureTypes[0].name
33844
+ : featureTypeName);
33845
+ })
33846
+ .then((featureType) => {
33847
+ if (!featureType) {
33848
+ throw new Error('wfs.featuretype.notfound');
33849
+ }
33850
+ return featureType;
33851
+ });
33852
+ }
33853
+ get backupReader() {
33854
+ if (this.backupReader_) {
33855
+ return this.backupReader_;
33856
+ }
33857
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33858
+ let reader;
33859
+ if (endpoint.supportsJson(featureType.name)) {
33860
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33861
+ asJson: true,
33862
+ outputCrs: 'EPSG:4326',
33863
+ }));
33864
+ }
33865
+ else {
33866
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33867
+ (featureType.defaultCrs === 'EPSG:4326' ||
33868
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33869
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33870
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33871
+ outputCrs: 'EPSG:4326',
33872
+ }));
33873
+ }
33874
+ }
33875
+ reader.enableCache(this.cacheEnabled);
33876
+ reader.load();
33877
+ return reader;
33878
+ });
33879
+ return this.backupReader_;
33284
33880
  }
33285
33881
  get properties() {
33286
- return this.endpoint
33287
- .getFeatureTypeFull(this.featureTypeName)
33288
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33882
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33289
33883
  const originalType = featureType.properties[prop];
33290
- const type = originalType === 'float' || originalType === 'integer'
33291
- ? 'number'
33292
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33884
+ let type;
33885
+ if (originalType === 'float' || originalType === 'integer') {
33886
+ type = 'number';
33887
+ }
33888
+ else if (originalType === 'boolean') {
33889
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33890
+ }
33891
+ else {
33892
+ type = originalType;
33893
+ }
33293
33894
  return {
33294
33895
  name: prop,
33295
33896
  label: prop,
@@ -33298,83 +33899,63 @@ class WfsReader extends BaseCacheReader {
33298
33899
  }));
33299
33900
  }
33300
33901
  get info() {
33301
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33902
+ return this.featureType.then((result) => ({
33302
33903
  itemsCount: result.objectCount,
33904
+ hasGeometry: !!result.geometryName,
33303
33905
  }));
33304
33906
  }
33305
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33306
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33307
- const featureTypes = wfsEndpoint.getFeatureTypes();
33308
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33309
- ? featureTypes[0].name
33310
- : featureTypeName);
33311
- if (!featureType) {
33312
- throw new Error('wfs.featuretype.notfound');
33313
- }
33314
- if (wfsEndpoint.supportsStartIndex()) {
33315
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33316
- }
33317
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33318
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33319
- asJson: true,
33320
- outputCrs: 'EPSG:4326',
33321
- }));
33322
- }
33323
- else {
33324
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33325
- (featureType.defaultCrs === 'EPSG:4326' ||
33326
- featureType.otherCrs?.includes('EPSG:4326'))) {
33327
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33328
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33329
- outputCrs: 'EPSG:4326',
33330
- }), featureType.name, wfsEndpoint.getVersion());
33331
- }
33332
- throw new Error('wfs.geojsongml.notsupported');
33333
- }
33907
+ load() {
33908
+ // Nothing to load for Wfs
33334
33909
  }
33335
- async getData(aggregation, groupedBy) {
33336
- if (aggregation || groupedBy) {
33337
- return { items: await this.getQueryData() };
33910
+ async read() {
33911
+ const endpoint = await this.endpoint;
33912
+ const featureType = await this.featureType;
33913
+ // if we can't use the WFS protocol we fall back to the backup reader
33914
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33915
+ const backupReader = await this.backupReader;
33916
+ backupReader.selectAll();
33917
+ if (this.selected) {
33918
+ backupReader.select(...this.selected);
33919
+ }
33920
+ if (this.aggregations) {
33921
+ backupReader.aggregate(...this.aggregations);
33922
+ }
33923
+ if (this.groupedBy) {
33924
+ backupReader.groupBy(...this.groupedBy);
33925
+ }
33926
+ if (this.sort) {
33927
+ backupReader.orderBy(...this.sort);
33928
+ }
33929
+ if (this.startIndex !== null && this.count !== null) {
33930
+ backupReader.limit(this.startIndex, this.count);
33931
+ }
33932
+ return backupReader.read();
33338
33933
  }
33339
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33934
+ const asJson = endpoint.supportsJson(featureType.name);
33340
33935
  const attributes = this.selected ?? undefined;
33341
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33936
+ let sortBy = null;
33937
+ if (this.sort) {
33938
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33939
+ sortBy = Array.isArray(this.sort[0])
33940
+ ? this.sort.map(mapSort)
33941
+ : mapSort(this.sort);
33942
+ }
33943
+ const url = endpoint.getFeatureUrl(featureType.name, {
33342
33944
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33343
33945
  ...(this.count !== null && { maxFeatures: this.count }),
33344
33946
  asJson,
33345
33947
  outputCrs: 'EPSG:4326',
33346
33948
  attributes,
33347
- // sortBy: this.sort // TODO: no sort in ogc-client?
33949
+ sortBy,
33348
33950
  });
33349
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33350
- const finalUrl = new URL(url);
33351
- const sorts = this.sort
33352
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33353
- .join(',');
33354
- // Direct update on string url to prevent encoding of +A and +D
33355
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33356
- }
33357
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33951
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33358
33952
  ? parseGeojson(text)
33359
- : parseGml(text, this.featureTypeName, this.version));
33360
- }
33361
- async getQueryData() {
33362
- const items = (await this.getData()).items;
33363
- const jsonItems = getJsonDataItemsProxy(items);
33364
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33365
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33366
- return result.map(jsonToGeojsonFeature);
33367
- }
33368
- load() {
33369
- // Nothing to load for Wfs
33370
- }
33371
- async read() {
33372
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33953
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33373
33954
  }
33374
33955
  }
33375
33956
 
33376
- async function openDataset(url, typeHint, options, cacheActive) {
33377
- const fileType = await inferDatasetType(url, typeHint);
33957
+ async function openDataset(url, options) {
33958
+ const fileType = await inferDatasetType(url, options?.typeHint);
33378
33959
  let reader;
33379
33960
  try {
33380
33961
  switch (fileType) {
@@ -33391,18 +33972,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33391
33972
  reader = new ExcelReader(url);
33392
33973
  break;
33393
33974
  case 'gml':
33394
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33975
+ reader = new GmlReader(url);
33395
33976
  break;
33396
33977
  case 'wfs':
33397
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33978
+ reader = new WfsReader(url, options?.wfsFeatureType);
33398
33979
  break;
33399
33980
  }
33400
- reader.setCacheActive(cacheActive);
33981
+ reader.enableCache(options?.enableCache ?? true);
33401
33982
  reader.load();
33402
33983
  return reader;
33403
33984
  }
33404
33985
  catch (e) {
33405
- //WfsReader may already raise a FetchError
33986
+ // WfsReader may already raise a FetchError
33406
33987
  if (e instanceof FetchError)
33407
33988
  throw e;
33408
33989
  else
@@ -33413,13 +33994,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33413
33994
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33414
33995
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33415
33996
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33416
- * File type is determined liked so:
33997
+ * File type is determined like so:
33417
33998
  * 1. if a type hint is given, use it
33418
33999
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33419
34000
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33420
34001
  */
33421
- async function readDataset(url, typeHint, options, cacheActive = true) {
33422
- const reader = await openDataset(url, typeHint, options, cacheActive);
34002
+ async function readDataset(url, options) {
34003
+ const reader = await openDataset(url, options);
33423
34004
  try {
33424
34005
  return await reader.read();
33425
34006
  }
@@ -33650,9 +34231,11 @@ class DataService {
33650
34231
  getDataset(link, cacheActive) {
33651
34232
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33652
34233
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33653
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34234
+ return from(openDataset(wfsUrlEndpoint, {
34235
+ typeHint: 'wfs',
33654
34236
  wfsFeatureType: link.name,
33655
- }, cacheActive));
34237
+ enableCache: cacheActive,
34238
+ }));
33656
34239
  }
33657
34240
  else if (link.type === 'download') {
33658
34241
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33660,12 +34243,15 @@ class DataService {
33660
34243
  const supportedType = SupportedTypes.indexOf(format) > -1
33661
34244
  ? format
33662
34245
  : undefined;
33663
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34246
+ return from(openDataset(linkProxifiedUrl, {
34247
+ typeHint: supportedType,
34248
+ enableCache: cacheActive,
34249
+ })).pipe();
33664
34250
  }
33665
34251
  else if (link.type === 'service' &&
33666
34252
  link.accessServiceProtocol === 'esriRest') {
33667
34253
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33668
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34254
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33669
34255
  }
33670
34256
  else if (link.type === 'service' &&
33671
34257
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33679,7 +34265,10 @@ class DataService {
33679
34265
  }
33680
34266
  const urlWithoutLimit = new URL(geojsonUrl);
33681
34267
  urlWithoutLimit.searchParams.delete('limit');
33682
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34268
+ return openDataset(urlWithoutLimit.toString(), {
34269
+ typeHint: 'geojson',
34270
+ enableCache: cacheActive,
34271
+ });
33683
34272
  }));
33684
34273
  }
33685
34274
  return throwError(() => 'protocol not supported');
@@ -33931,20 +34520,31 @@ class DataTableComponent {
33931
34520
  this.eltRef = inject(ElementRef);
33932
34521
  this.cdr = inject(ChangeDetectorRef);
33933
34522
  this.translateService = inject(TranslateService);
33934
- this._featureAttributes = [];
34523
+ this.columnsFromFeatureCatalog = null;
34524
+ this.columnsFromDataset = [];
33935
34525
  this.selected = new EventEmitter();
33936
- this.properties$ = new BehaviorSubject(null);
33937
34526
  this.loading$ = new BehaviorSubject(false);
33938
34527
  this.error = null;
33939
34528
  }
33940
34529
  set featureAttributes(value) {
33941
- this._featureAttributes = value;
33942
- this.properties$.next(value.map((attr) => attr.value));
34530
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34531
+ name: attrs.value,
34532
+ label: attrs.label,
34533
+ }));
33943
34534
  }
33944
34535
  set dataset(value) {
33945
34536
  this.dataset_ = value;
33946
34537
  this.dataset_.load();
33947
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34538
+ this.dataset_.info.then((info) => {
34539
+ this.count = info.itemsCount;
34540
+ this.cdr.detectChanges();
34541
+ });
34542
+ }
34543
+ get columns() {
34544
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34545
+ }
34546
+ get columnNames() {
34547
+ return this.columns.map((c) => c.name);
33948
34548
  }
33949
34549
  ngOnInit() {
33950
34550
  this.dataSource = new DataTableDataSource();
@@ -33979,11 +34579,12 @@ class DataTableComponent {
33979
34579
  }
33980
34580
  async readData() {
33981
34581
  this.loading$.next(true);
33982
- // wait for properties to be read
33983
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
33984
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
33985
- this.dataset_.select(...propsWithoutGeom);
33986
34582
  try {
34583
+ // wait for properties to be read
34584
+ if (!this.columnsFromFeatureCatalog) {
34585
+ this.columnsFromDataset = await this.dataset_.properties;
34586
+ }
34587
+ this.dataset_.select(...this.columnNames);
33987
34588
  await this.dataSource.showData(this.dataset_.read());
33988
34589
  this.error = null;
33989
34590
  }
@@ -34013,7 +34614,7 @@ class DataTableComponent {
34013
34614
  }
34014
34615
  }
34015
34616
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34016
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34617
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34017
34618
  }
34018
34619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34019
34620
  type: Component,
@@ -34025,10 +34626,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34025
34626
  CommonModule,
34026
34627
  LoadingMaskComponent,
34027
34628
  PopupAlertComponent,
34028
- LetDirective,
34029
34629
  TranslatePipe,
34030
34630
  TranslateDirective,
34031
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34631
+ ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34032
34632
  }], propDecorators: { featureAttributes: [{
34033
34633
  type: Input
34034
34634
  }], dataset: [{
@@ -34131,7 +34731,7 @@ class ChartViewComponent {
34131
34731
  }), shareReplay$1(1));
34132
34732
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34133
34733
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34134
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34734
+ .filter((prop) => prop.type === 'number')
34135
34735
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34136
34736
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34137
34737
  const newProp = choices[0]?.value || '';
@@ -34158,6 +34758,7 @@ class ChartViewComponent {
34158
34758
  this.aggregation$,
34159
34759
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34160
34760
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34761
+ this.loading = true;
34161
34762
  return dataset
34162
34763
  .groupBy(['distinct', xProp])
34163
34764
  .aggregate(fieldAgg)
@@ -34399,6 +35000,7 @@ class GeoTableViewComponent {
34399
35000
  }));
34400
35001
  }
34401
35002
  async initMapContext() {
35003
+ this.dataset.load();
34402
35004
  this.dataset.selectAll();
34403
35005
  return {
34404
35006
  layers: [
@@ -34572,8 +35174,7 @@ class MdViewFacade {
34572
35174
  }));
34573
35175
  this.error$ = this.store.pipe(select(getMetadataError));
34574
35176
  this.related$ = this.store.pipe(select(getRelated));
34575
- this.sources$ = this.store.pipe(select(getSources));
34576
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35177
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34577
35178
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34578
35179
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34579
35180
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34692,12 +35293,9 @@ class MdViewEffects {
34692
35293
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34693
35294
  return setRelated({ related });
34694
35295
  }), catchError(() => of(setRelated({ related: null })))));
34695
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34696
- return setSources({ sources });
34697
- }), catchError(() => of(setSources({ sources: null })))));
34698
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34699
- return setSourceOf({ sourceOf });
34700
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
35296
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35297
+ return setLinkedRecords({ linkedRecords });
35298
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34701
35299
  /*
34702
35300
  UserFeedback effects
34703
35301
  */
@@ -36431,6 +37029,11 @@ const DEFAULT_CONFIGURATION = {
36431
37029
  },
36432
37030
  hidden: '${record.kind == "service"}',
36433
37031
  },
37032
+ {
37033
+ model: 'associatedRecords',
37034
+ formFieldConfig: {},
37035
+ hidden: '${record.kind == "service"}',
37036
+ },
36434
37037
  ],
36435
37038
  },
36436
37039
  // Section: Geographical coverage
@@ -37393,7 +37996,7 @@ class GenericKeywordsComponent {
37393
37996
  this.deletedKeyword.emit(keyword);
37394
37997
  }
37395
37998
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37396
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37999
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GenericKeywordsComponent, isStandalone: true, selector: "gn-ui-generic-keywords", inputs: { keywords: "keywords", keywordTypes: "keywordTypes", placeholder: "placeholder", allowSubmit: "allowSubmit" }, outputs: { changedKeywords: "changedKeywords", addedKeyword: "addedKeyword", deletedKeyword: "deletedKeyword" }, ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n <gn-ui-autocomplete\n [placeholder]=\"placeholder\"\n [displayWithFn]=\"displayWithFn\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleItemSelection($event)\"\n [preventCompleteOnSelection]=\"false\"\n [minCharacterCount]=\"1\"\n [allowSubmit]=\"false\"\n [clearOnSelection]=\"true\"\n ></gn-ui-autocomplete>\n <div class=\"flex gap-2 flex-wrap\">\n @for (keyword of keywords; track keyword) {\n <gn-ui-keyword-badge\n [keyword]=\"keyword\"\n [editable]=\"true\"\n (keywordRemove)=\"removeKeyword($event)\"\n ></gn-ui-keyword-badge>\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: KeywordBadgeComponent, selector: "gn-ui-keyword-badge", inputs: ["keyword", "editable"], outputs: ["keywordClick", "keywordRemove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37397
38000
  }
37398
38001
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GenericKeywordsComponent, decorators: [{
37399
38002
  type: Component,
@@ -37568,7 +38171,7 @@ class FormFieldTemporalExtentsComponent {
37568
38171
  provideNgIconsConfig({
37569
38172
  size: '1.5rem',
37570
38173
  }),
37571
- ], 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 }); }
38174
+ ], 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 }); }
37572
38175
  }
37573
38176
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldTemporalExtentsComponent, decorators: [{
37574
38177
  type: Component,
@@ -37585,7 +38188,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37585
38188
  provideNgIconsConfig({
37586
38189
  size: '1.5rem',
37587
38190
  }),
37588
- ], 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"] }]
38191
+ ], 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"] }]
37589
38192
  }], propDecorators: { value: [{
37590
38193
  type: Input
37591
38194
  }], valueChange: [{
@@ -37801,6 +38404,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
37801
38404
  ], 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" }]
37802
38405
  }] });
37803
38406
 
38407
+ class FormFieldAssociatedRecordsComponent {
38408
+ constructor() {
38409
+ this.valueChange = new EventEmitter();
38410
+ }
38411
+ get list() {
38412
+ return this.value ?? [];
38413
+ }
38414
+ get first() {
38415
+ return this.list[0];
38416
+ }
38417
+ get rest() {
38418
+ return this.list.slice(1);
38419
+ }
38420
+ get hasParentLink() {
38421
+ return this.list.length > 0;
38422
+ }
38423
+ get uniqueIdentifier() {
38424
+ return this.first?.uniqueIdentifier ?? '';
38425
+ }
38426
+ get selectedType() {
38427
+ return this.first?.associationType;
38428
+ }
38429
+ get choices() {
38430
+ return associationTypeValues.map((value) => ({
38431
+ value,
38432
+ label: `domain.record.associationType.${value}`,
38433
+ }));
38434
+ }
38435
+ onToggle(checked) {
38436
+ this.valueChange.emit(checked
38437
+ ? [
38438
+ {
38439
+ uniqueIdentifier: '',
38440
+ associationType: associationTypeValues[0],
38441
+ },
38442
+ ...this.rest,
38443
+ ]
38444
+ : this.rest);
38445
+ }
38446
+ onUniqueIdentifierChange(uniqueIdentifier) {
38447
+ this.valueChange.emit(uniqueIdentifier
38448
+ ? [{ ...this.first, uniqueIdentifier }, ...this.rest]
38449
+ : this.rest);
38450
+ }
38451
+ onTypeChange(associationType) {
38452
+ this.valueChange.emit([
38453
+ { ...this.first, associationType: associationType },
38454
+ ...this.rest,
38455
+ ]);
38456
+ }
38457
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38458
+ 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 }); }
38459
+ }
38460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldAssociatedRecordsComponent, decorators: [{
38461
+ type: Component,
38462
+ args: [{ selector: 'gn-ui-form-field-associated-records', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
38463
+ CheckToggleComponent,
38464
+ TextInputComponent,
38465
+ DropdownSelectorComponent,
38466
+ FormFieldWrapperComponent,
38467
+ TranslatePipe,
38468
+ ], 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" }]
38469
+ }], propDecorators: { value: [{
38470
+ type: Input
38471
+ }], valueChange: [{
38472
+ type: Output
38473
+ }] } });
38474
+
37804
38475
  class ContactCardComponent {
37805
38476
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
37806
38477
  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 }); }
@@ -37917,7 +38588,7 @@ class FormFieldContactsForResourceComponent {
37917
38588
  provideNgIconsConfig({
37918
38589
  size: '1.5rem',
37919
38590
  }),
37920
- ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38591
+ ], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <div class=\"flex flex-row flex-wrap gap-2\" data-test=\"rolesToPick\">\n @for (role of rolesToPick; track role) {\n <gn-ui-button type=\"gray\" (buttonClick)=\"addRoleToDisplay(role)\">\n <ng-icon name=\"iconoirPlus\" class=\"text-primary\"></ng-icon>\n &nbsp;\n <span translate>{{ roleToLabel(role) }}</span>\n </gn-ui-button>\n }\n </div>\n @if (value.length === 0) {\n <div\n class=\"p-4 border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contactsForResource.noContact\n </div>\n }\n @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {\n <div class=\"mt-8\" data-test=\"displayedRoles\">\n @for (\n role of roleSectionsToDisplay;\n track role;\n let index = $index;\n let isLast = $last\n ) {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex flex-row justify-between\">\n <span class=\"font-bold text-base\" translate>{{\n roleToLabel(role)\n }}</span>\n </div>\n @if (role !== 'unspecified' && role !== 'other') {\n <gn-ui-autocomplete\n [placeholder]=\"\n 'editor.record.form.field.contactsForResource.placeholder'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event, role)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n [allowSubmit]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (contactsForRessourceByRole.get(role); as contacts) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event, role)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact>\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n </ng-template>\n }\n @if (!isLast) {\n <hr class=\"border-t-[#D6D3D1] mt-4 mb-6\" />\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--gn-ui-button-padding: 8px 8px;--gn-ui-button-rounded: 8px}\n"], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
37921
38592
  }
37922
38593
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsForResourceComponent, decorators: [{
37923
38594
  type: Component,
@@ -38017,7 +38688,7 @@ class FormFieldContactsComponent {
38017
38688
  this.subscription.unsubscribe();
38018
38689
  }
38019
38690
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38020
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38691
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FormFieldContactsComponent, isStandalone: true, selector: "gn-ui-form-field-contacts", inputs: { value: "value", modelSpecifier: "modelSpecifier" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col gap-3\">\n <gn-ui-autocomplete\n [placeholder]=\"'editor.record.form.field.contacts.placeholder' | translate\"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"addContact($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"1\"\n [clearOnSelection]=\"true\"\n >\n </gn-ui-autocomplete>\n\n @if (contacts.length > 0) {\n <gn-ui-sortable-list\n [items]=\"contacts\"\n (itemsOrderChange)=\"handleContactsChanged($event)\"\n [elementTemplate]=\"contactTemplate\"\n ></gn-ui-sortable-list>\n <ng-template #contactTemplate let-contact let-index=\"index\">\n @if (modelSpecifier === 'contact:editableDetails') {\n <gn-ui-contact-details-form\n [contact]=\"contact\"\n (contactChange)=\"handleContactChanged($event, index)\"\n ></gn-ui-contact-details-form>\n } @else {\n <gn-ui-contact-card [contact]=\"contact\"></gn-ui-contact-card>\n }\n </ng-template>\n } @else {\n <div\n class=\"p-4 text-sm border border-primary bg-primary-lightest rounded-lg\"\n translate\n >\n editor.record.form.field.contacts.noContact\n </div>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ContactCardComponent, selector: "gn-ui-contact-card", inputs: ["contact"] }, { kind: "component", type: ContactDetailsFormComponent, selector: "gn-ui-contact-details-form", inputs: ["contact"], outputs: ["contactChange"] }, { kind: "component", type: SortableListComponent, selector: "gn-ui-sortable-list", inputs: ["elementTemplate", "items"], outputs: ["itemsOrderChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38021
38692
  }
38022
38693
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldContactsComponent, decorators: [{
38023
38694
  type: Component,
@@ -38370,7 +39041,7 @@ class OnlineServiceResourceInputComponent {
38370
39041
  provideNgIconsConfig({
38371
39042
  size: '1.5em',
38372
39043
  }),
38373
- ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39044
+ ], ngImport: i0, template: "<div class=\"flex flex-row justify-between\">\n <h3 class=\"text-[16px] font-bold text-main mb-[12px]\" translate>\n editor.record.form.field.onlineResource.edit.protocol\n </h3>\n @if (protocolHint) {\n <ng-icon\n name=\"matHelpOutline\"\n size=\"16\"\n class=\"m-2\"\n [matTooltip]=\"protocolHint\"\n matTooltipPosition=\"above\"\n ></ng-icon>\n }\n</div>\n<div class=\"flex flex-row items-center gap-4 h-[48px]\">\n <mat-radio-group\n aria-labelledby=\"example-radio-group-label\"\n class=\"flex flex-row gap-[8px]\"\n [(ngModel)]=\"_service.accessServiceProtocol\"\n [disabled]=\"disabled\"\n (change)=\"resetAllFormFields()\"\n >\n @for (protocolOption of availableProtocolOptions; track protocolOption) {\n <mat-radio-button [value]=\"protocolOption.value\">\n {{ protocolOption.label | translate }}\n </mat-radio-button>\n }\n </mat-radio-group>\n</div>\n\n<div class=\"flex flex-col gap-4\">\n <gn-ui-url-input\n class=\"w-full\"\n (uploadClick)=\"handleUploadClick($event)\"\n (valueChange)=\"handleUrlValueChange($event)\"\n [disabled]=\"disabled\"\n [value]=\"_service.url?.toString()\"\n [showValidateButton]=\"activeLayerSuggestion\"\n [resetUrlOnChange]=\"resetUrlOnChange\"\n >\n @if (activeLayerSuggestion) {\n <ng-content>\n <ng-icon name=\"iconoirRefresh\"></ng-icon>\n </ng-content>\n }\n </gn-ui-url-input>\n\n @if (loading) {\n <div class=\"flex justify-center w-full py-4\">\n <gn-ui-spinning-loader></gn-ui-spinning-loader>\n </div>\n }\n @if (errorMessage) {\n <p class=\"text-sm text-red-500 pl-4\" translate>\n editor.record.form.field.onlineResource.edit.identifier.error\n </p>\n }\n\n @if (\n !activeLayerSuggestion ||\n (_service.url && errorMessage) ||\n (modifyMode && (layers$ | async).length === 0)\n ) {\n <gn-ui-text-input\n class=\"grow border-b border-gray-300 pb-4\"\n [(value)]=\"_service.identifierInService\"\n [placeholder]=\"getIdentifierPlaceholder() | translate\"\n data-cy=\"identifier-in-service\"\n [disabled]=\"disabled\"\n ></gn-ui-text-input>\n }\n @if (activeLayerSuggestion && (layers$ | async)?.length > 0) {\n <gn-ui-autocomplete\n class=\"border-b border-gray-300 pb-4\"\n [placeholder]=\"\n 'editor.record.form.field.onlineResource.edit.identifier.select.label'\n | translate\n \"\n [action]=\"autoCompleteAction\"\n (itemSelected)=\"handleSelectValue($event)\"\n [displayWithFn]=\"displayWithFn\"\n [minCharacterCount]=\"0\"\n [clearOnSelection]=\"false\"\n >\n </gn-ui-autocomplete>\n }\n @if (\n !modifyMode &&\n ((activeLayerSuggestion && (layers$ | async)) ||\n !activeLayerSuggestion ||\n (_service.url && errorMessage))\n ) {\n <gn-ui-button\n (buttonClick)=\"submit()\"\n [disabled]=\"disabled || !_service.identifierInService\"\n type=\"primary\"\n >\n <span class=\"text-white font-bold\" translate>\n editor.record.form.field.onlineResource.edit.identifier.submit</span\n >\n </gn-ui-button>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: AutocompleteComponent, selector: "gn-ui-autocomplete", inputs: ["placeholder", "enterButton", "action", "value", "clearOnSelection", "preventCompleteOnSelection", "autoFocus", "minCharacterCount", "allowSubmit", "forceTrackPosition", "displayWithTemplate", "displayWithFn"], outputs: ["itemSelected", "inputSubmitted", "inputCleared", "isSearchActive"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: SpinningLoaderComponent, selector: "gn-ui-spinning-loader" }, { kind: "component", type: TextInputComponent, selector: "gn-ui-text-input", inputs: ["value", "extraClass", "placeholder", "required", "disabled", "type"], outputs: ["valueChange"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: UrlInputComponent, selector: "gn-ui-url-input", inputs: ["value", "extraClass", "placeholder", "disabled", "showValidateButton", "resetUrlOnChange"], outputs: ["valueChange", "uploadClick"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
38374
39045
  }
38375
39046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OnlineServiceResourceInputComponent, decorators: [{
38376
39047
  type: Component,
@@ -39186,6 +39857,9 @@ class FormFieldComponent {
39186
39857
  get valueAsUpdateFrequency() {
39187
39858
  return this.value;
39188
39859
  }
39860
+ get valueAsAssociatedRecords() {
39861
+ return this.value;
39862
+ }
39189
39863
  get valueAsTemporalExtents() {
39190
39864
  return this.value;
39191
39865
  }
@@ -39225,7 +39899,7 @@ class FormFieldComponent {
39225
39899
  }
39226
39900
  }
39227
39901
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
39228
- 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 }); }
39902
+ 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 }); }
39229
39903
  }
39230
39904
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FormFieldComponent, decorators: [{
39231
39905
  type: Component,
@@ -39237,6 +39911,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39237
39911
  FormFieldLicenseComponent,
39238
39912
  FormFieldDateComponent,
39239
39913
  FormFieldUpdateFrequencyComponent,
39914
+ FormFieldAssociatedRecordsComponent,
39240
39915
  FormFieldTemporalExtentsComponent,
39241
39916
  FormFieldSimpleComponent,
39242
39917
  FormFieldRichComponent,
@@ -39254,7 +39929,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
39254
39929
  FormFieldTopicsComponent,
39255
39930
  TextFieldModule,
39256
39931
  NgIconComponent,
39257
- ], 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"] }]
39932
+ ], 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"] }]
39258
39933
  }], propDecorators: { uniqueIdentifier: [{
39259
39934
  type: Input
39260
39935
  }], recordKind: [{
@@ -39839,7 +40514,7 @@ class RouterService {
39839
40514
  return ROUTER_ROUTE_ORGANIZATION;
39840
40515
  }
39841
40516
  getDefaultSort() {
39842
- return SortByEnum.RESOURCE_DATES;
40517
+ return SortByEnum.RESOURCE_DATE;
39843
40518
  }
39844
40519
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39845
40520
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RouterService, providedIn: 'root' }); }
@@ -39899,7 +40574,7 @@ function flattenQueryParams(params) {
39899
40574
  const start = flattened[key].start;
39900
40575
  const end = flattened[key].end;
39901
40576
  flattened[key] = [
39902
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40577
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
39903
40578
  ];
39904
40579
  }
39905
40580
  }
@@ -40256,5 +40931,5 @@ const CHART_TYPE_VALUES = [
40256
40931
  * Generated bundle index. Do not edit.
40257
40932
  */
40258
40933
 
40259
- 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_BASEMAP_LAYER, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, 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, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40934
+ export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AutofocusDirective, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, BoundingBoxSearchField, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_BASEMAP_LAYER, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEOCODING_PROVIDER, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingService, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LocationSearchComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceCreationRevisionDateSearchField, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpatialExtentDropdownComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isBoundingBox, isConfigLoaded, isDateRange, isFileExtensionValid, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideLocalizedDateAdapter, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readFileAsText, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40260
40935
  //# sourceMappingURL=geonetwork-ui.mjs.map