geonetwork-ui 2.11.0-dev.a83f0f51a → 2.11.0-dev.bc1a6d4e2

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 (77) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1244 -677
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +219 -78
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +48 -7
  8. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  9. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  10. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +13 -1
  11. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  12. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  13. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  14. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  15. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  16. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  17. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  18. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  19. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  20. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  21. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  22. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  23. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +11 -4
  24. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  25. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  26. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  27. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  28. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  29. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  30. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  31. package/src/libs/ui/inputs/src/index.ts +2 -0
  32. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  33. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  34. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  35. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  36. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  37. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  38. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  39. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  40. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  41. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  42. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  43. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  44. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
  45. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  46. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  47. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  48. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  49. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  50. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  51. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  52. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  53. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  54. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  55. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  56. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  57. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  58. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  59. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  60. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  61. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  62. package/src/libs/util/shared/src/index.ts +1 -0
  63. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  64. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  65. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  66. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  67. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  68. package/tailwind.base.css +5 -0
  69. package/translations/de.json +15 -2
  70. package/translations/en.json +14 -1
  71. package/translations/es.json +13 -0
  72. package/translations/fr.json +15 -2
  73. package/translations/it.json +13 -0
  74. package/translations/nl.json +13 -0
  75. package/translations/pt.json +13 -0
  76. package/translations/sk.json +13 -0
  77. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -6,9 +6,9 @@ import GeoJSON from 'ol/format/GeoJSON.js';
6
6
  import { parse as parse$1 } from 'ol/xml.js';
7
7
  import { format } from 'date-fns/format';
8
8
  import { Namespace, Literal, lit, parse as parse$2, sym, BlankNode, graph } from 'rdflib';
9
- import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, combineLatest, from, exhaustMap, throwError, forkJoin, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
9
+ import { lastValueFrom, fromEvent, startWith, map as map$2, shareReplay, filter, pairwise, of, switchMap, Subject, forkJoin, combineLatest, from, exhaustMap, throwError, takeLast, firstValueFrom, merge, BehaviorSubject, timer, ReplaySubject, Subscription, first, distinctUntilChanged as distinctUntilChanged$1, animationFrameScheduler, debounceTime as debounceTime$1, Observable, buffer, tap as tap$2, combineLatestWith, take as take$1, catchError as catchError$1, takeUntil, EMPTY, mergeMap as mergeMap$1, withLatestFrom as withLatestFrom$1 } from 'rxjs';
10
10
  import * as i0 from '@angular/core';
11
- import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, HostListener, Input, Directive, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, afterNextRender, viewChildren } from '@angular/core';
11
+ import { InjectionToken, inject, Injectable, NgModule, Injector, provideAppInitializer, makeEnvironmentProviders, ElementRef, afterNextRender, booleanAttribute, Input, Directive, HostListener, Renderer2, DestroyRef, EventEmitter, Output, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, ChangeDetectorRef, HostBinding, ViewContainerRef, TemplateRef, ViewChildren, ContentChild, input, output, signal, viewChild, computed, ContentChildren, NgZone, ComponentFactoryResolver, viewChildren } from '@angular/core';
12
12
  import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpEventType } from '@angular/common/http';
13
13
  import * as i4 from '@ngx-translate/core';
14
14
  import { TranslateLoader, TranslateCompiler, TranslateDefaultParser, TranslateParser, TranslateService, provideTranslateService, TranslateDirective, TranslatePipe, TranslateModule } from '@ngx-translate/core';
@@ -18,7 +18,7 @@ import { map as map$1, shareReplay as shareReplay$1, catchError, tap as tap$1, f
18
18
  import { lt, valid, coerce, satisfies, ltr } from 'semver';
19
19
  import chroma from 'chroma-js';
20
20
  import * as i1$1 from '@angular/common';
21
- import { Location, CommonModule, NgClass, NgTemplateOutlet, DatePipe, AsyncPipe } from '@angular/common';
21
+ import { Location, CommonModule, NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common';
22
22
  import { formatDistance } from 'date-fns/formatDistance';
23
23
  import { Scroll, NavigationEnd, Router, RouteReuseStrategy } from '@angular/router';
24
24
  import { WmtsEndpoint, WmsEndpoint, WfsEndpoint, OgcApiEndpoint, sharedFetch, useCache, StacEndpoint, TmsEndpoint } from '@camptocamp/ogc-client';
@@ -41,10 +41,10 @@ import * as TOML from '@ltd/j-toml';
41
41
  import { Style, Stroke, Fill, Circle } from 'ol/style.js';
42
42
  import CircleStyle from 'ol/style/Circle.js';
43
43
  import EmblaCarousel from 'embla-carousel';
44
- import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirImport, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirCheckCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
44
+ import { iconoirNavArrowLeft, iconoirNavArrowRight, iconoirLongArrowDownLeft, iconoirSearch, iconoirCalendar, iconoirArrowUp, iconoirLink, iconoirFramePlusIn, iconoirCloudUpload, iconoirTrash, iconoirSquareDashed, iconoirImport, iconoirCheckCircle, iconoirReduce, iconoirNavArrowUp, iconoirNavArrowDown, iconoirExpand, iconoirSettings, iconoirDownload, iconoirPlus, iconoirBin, iconoirMediaImageXmark, iconoirMediaImage, iconoirAppWindow, iconoirCode, iconoirDatabase, iconoirAppleWallet, iconoirBank, iconoirList, iconoirTranslate, iconoirLock, iconoirUser, iconoirArrowLeft, iconoirLightBulbOn, iconoirBadgeCheck, iconoirSystemShut, iconoirCircle, iconoirAttachment, iconoirRefresh } from '@ng-icons/iconoir';
45
45
  import { MatButtonModule } from '@angular/material/button';
46
- import * as i1$8 from '@angular/cdk/overlay';
47
- import { OverlayContainer, ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
46
+ import * as i1$7 from '@angular/cdk/overlay';
47
+ import { ScrollStrategyOptions, OverlayModule, CdkConnectedOverlay, ScrollDispatcher, Overlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
48
  import * as i1$3 from '@angular/forms';
49
49
  import { UntypedFormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
50
50
  import * as i1$2 from '@angular/material/autocomplete';
@@ -52,16 +52,18 @@ import { MatAutocompleteModule, MatAutocompleteTrigger, MatAutocomplete } from '
52
52
  import tippy from 'tippy.js';
53
53
  import * as i2 from '@angular/material/progress-spinner';
54
54
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
55
- import { matClose, matContentCopy, matExpandLess, matExpandMore, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
55
+ import { matClose, matContentCopy, matExpandMore, matExpandLess, matSearch, matStarBorder, matStar, matRemove, matAdd, matChevronRight, matChevronLeft, matArrowForwardIos, matArrowBackIos, matMoodBad, matQuestionMark, matFace, matZoomOutMap, matOpenInNew, matPersonOutline, matMailOutline, matWarningAmber, matCheck, matCheckCircleOutline, matWarning, matMoreVert } from '@ng-icons/material-icons/baseline';
56
56
  import * as i1$4 from '@angular/material/checkbox';
57
57
  import { MatCheckboxModule } from '@angular/material/checkbox';
58
58
  import * as i1$5 from '@angular/material/tooltip';
59
59
  import { MatTooltipModule } from '@angular/material/tooltip';
60
- import { DateAdapter, MatNativeDateModule, MAT_DATE_LOCALE } from '@angular/material/core';
60
+ import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, DateAdapter } from '@angular/material/core';
61
+ import { MAT_DATE_FNS_FORMATS, DateFnsAdapter } from '@angular/material-date-fns-adapter';
62
+ import { enUS } from 'date-fns/locale/en-US';
61
63
  import * as i1$6 from '@angular/material/datepicker';
62
64
  import { MatDatepickerModule } from '@angular/material/datepicker';
63
- import * as i1$7 from 'ngx-dropzone';
64
- import { NgxDropzoneModule } from 'ngx-dropzone';
65
+ import * as i1$8 from 'ngx-dropzone';
66
+ import { NgxDropzoneModule, NgxDropzoneComponent } from 'ngx-dropzone';
65
67
  import * as i1$9 from '@angular/material/button-toggle';
66
68
  import { MatButtonToggleModule } from '@angular/material/button-toggle';
67
69
  import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
@@ -79,10 +81,10 @@ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
79
81
  import Duration from 'duration-relativetimeformat';
80
82
  import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
81
83
  import { trigger, transition, animate, keyframes, style } from '@angular/animations';
82
- import * as Papa from 'papaparse';
83
- import { parse as parse$4 } from 'date-fns/parse';
84
- import { parseISO } from 'date-fns/parseISO';
85
- import { WFS, GeoJSON as GeoJSON$1 } from 'ol/format.js';
84
+ import * as duckdb from '@duckdb/duckdb-wasm';
85
+ import { Vector, DataType } from 'apache-arrow';
86
+ import WFS from 'ol/format/WFS';
87
+ import GeoJSON$1 from 'ol/format/GeoJSON';
86
88
  import { Chart, BarController, BarElement, CategoryScale, LinearScale, LineController, LineElement, PointElement, PieController, ArcElement, ScatterController, Tooltip, Colors, Legend } from 'chart.js';
87
89
  import * as i2$4 from '@angular/material/sort';
88
90
  import { MatSortModule, MatSort } from '@angular/material/sort';
@@ -90,7 +92,6 @@ import * as i1$b from '@angular/material/table';
90
92
  import { MatTableModule } from '@angular/material/table';
91
93
  import * as i3 from '@angular/material/paginator';
92
94
  import { MatPaginatorIntl, MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
93
- import { LetDirective } from '@ngrx/component';
94
95
  import { Meta } from '@angular/platform-browser';
95
96
  import { tablerFolderOpen } from '@ng-icons/tabler-icons';
96
97
  import * as i3$1 from '@angular/material/radio';
@@ -18907,14 +18908,29 @@ class Gn4FieldMapper {
18907
18908
  featureTypes: selectField(source, 'featureTypes'),
18908
18909
  }, output),
18909
18910
  related: (output, source) => {
18910
- const fcatSource = selectField(getFirstValue(selectField(selectField(source, 'related'), 'fcats')) ?? {}, '_source');
18911
+ const related = selectField(source, 'related');
18912
+ const fcatSource = selectField(getFirstValue(selectField(related, 'fcats')) ?? {}, '_source');
18911
18913
  const featureCatalogIdentifier = selectField(fcatSource, 'uuid');
18912
- const sourceOfLinks = getAsArray(selectField(selectField(source, 'related'), 'hassources'));
18914
+ const sourceOfLinks = getAsArray(selectField(related, 'hassources'));
18913
18915
  const sourceOfIdentifiers = sourceOfLinks
18914
18916
  .filter((link) => link['origin'] === 'catalog')
18915
18917
  .map((link) => {
18916
18918
  return selectField(selectField(link, '_source'), 'uuid');
18917
18919
  });
18920
+ const siblingLinks = getAsArray(selectField(related, 'siblings'));
18921
+ const siblings = siblingLinks
18922
+ .filter((link) => link['origin'] === 'catalog')
18923
+ .map((link) => ({
18924
+ uniqueIdentifier: selectField(selectField(link, '_source'), 'uuid'),
18925
+ associationType: getAssociationTypeFromCode(selectField(selectField(link, 'properties'), 'associationType')),
18926
+ }));
18927
+ const associatedLinks = getAsArray(selectField(related, 'associated'));
18928
+ const associatedIdentifiers = associatedLinks
18929
+ .filter((link) => link['origin'] === 'catalog')
18930
+ .map((link) => {
18931
+ return selectField(selectField(link, '_source'), 'uuid');
18932
+ })
18933
+ .filter((uuid) => !siblings.some((sibling) => sibling.uniqueIdentifier === uuid));
18918
18934
  const extraValues = {};
18919
18935
  if (featureCatalogIdentifier) {
18920
18936
  extraValues.featureCatalogIdentifier = featureCatalogIdentifier;
@@ -18922,6 +18938,12 @@ class Gn4FieldMapper {
18922
18938
  if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
18923
18939
  extraValues.sourceOfIdentifiers = sourceOfIdentifiers;
18924
18940
  }
18941
+ if (associatedIdentifiers && associatedIdentifiers.length > 0) {
18942
+ extraValues.associatedIdentifiers = associatedIdentifiers;
18943
+ }
18944
+ if (siblings && siblings.length > 0) {
18945
+ extraValues.siblings = siblings;
18946
+ }
18925
18947
  return Object.keys(extraValues).length > 0
18926
18948
  ? this.addExtra(extraValues, output)
18927
18949
  : output;
@@ -19447,10 +19469,10 @@ class DateService {
19447
19469
  const dateObj = this.getDateObject(date);
19448
19470
  return { locale, dateObj };
19449
19471
  }
19450
- async getDateLocale() {
19472
+ async getDateFnsLocale() {
19451
19473
  const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE;
19452
19474
  const locales = await this.dateLocales;
19453
- return locales[lang];
19475
+ return locales[lang] ?? locales[DEFAULT_LANGUAGE];
19454
19476
  }
19455
19477
  formatDate(date, options) {
19456
19478
  const { locale, dateObj } = this.getLocaleAndDate(date);
@@ -19463,7 +19485,7 @@ class DateService {
19463
19485
  async formatRelativeDateTime(date) {
19464
19486
  const dateObj = this.getDateObject(date);
19465
19487
  const now = new Date();
19466
- const locale = await this.getDateLocale();
19488
+ const locale = await this.getDateFnsLocale();
19467
19489
  return formatDistance(dateObj, now, {
19468
19490
  addSuffix: true,
19469
19491
  locale: locale,
@@ -19498,6 +19520,18 @@ function propagateToDocumentOnly(event) {
19498
19520
  }, 0);
19499
19521
  }
19500
19522
 
19523
+ function isFileExtensionValid(fileName, acceptedExtensions) {
19524
+ return acceptedExtensions.some((ext) => fileName.toLowerCase().endsWith(ext));
19525
+ }
19526
+ function readFileAsText(file) {
19527
+ return new Promise((resolve, reject) => {
19528
+ const reader = new FileReader();
19529
+ reader.onload = () => resolve(reader.result);
19530
+ reader.onerror = () => reject(reader.error);
19531
+ reader.readAsText(file);
19532
+ });
19533
+ }
19534
+
19501
19535
  function formatUserInfo(userInfo, displayCount = false) {
19502
19536
  const infos = (typeof userInfo === 'string' ? userInfo : '').split('|');
19503
19537
  const count = displayCount ? ` ${infos[3].split(' ')[1]}` : '';
@@ -19557,6 +19591,11 @@ function getGeometryFromGeoJSON(data) {
19557
19591
  }
19558
19592
  return null;
19559
19593
  }
19594
+ function isBoundingBox(value) {
19595
+ return (Array.isArray(value) &&
19596
+ value.length === 4 &&
19597
+ value.every((item) => typeof item === 'number'));
19598
+ }
19560
19599
  function getGeometryBoundingBox(geometry) {
19561
19600
  // use the bounding box if specified in the GeoJSON object
19562
19601
  if (geometry.bbox) {
@@ -20342,6 +20381,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20342
20381
  }]
20343
20382
  }] });
20344
20383
 
20384
+ class AutofocusDirective {
20385
+ constructor() {
20386
+ this.el = inject(ElementRef);
20387
+ this.injector = inject(Injector);
20388
+ }
20389
+ set autofocus(active) {
20390
+ if (!active)
20391
+ return;
20392
+ afterNextRender(() => this.el.nativeElement.focus(), {
20393
+ injector: this.injector,
20394
+ });
20395
+ }
20396
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20397
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.3.19", type: AutofocusDirective, isStandalone: true, selector: "[gnUiAutofocus]", inputs: { autofocus: ["gnUiAutofocus", "autofocus", booleanAttribute] }, ngImport: i0 }); }
20398
+ }
20399
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20400
+ type: Directive,
20401
+ args: [{
20402
+ selector: '[gnUiAutofocus]',
20403
+ standalone: true,
20404
+ }]
20405
+ }], propDecorators: { autofocus: [{
20406
+ type: Input,
20407
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20408
+ }] } });
20409
+
20345
20410
  class ImageFallbackDirective {
20346
20411
  constructor() {
20347
20412
  this.el = inject(ElementRef);
@@ -20369,7 +20434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20369
20434
  }] } });
20370
20435
 
20371
20436
  var name = "geonetwork-ui";
20372
- var version = "2.11.0-dev.a83f0f51a";
20437
+ var version = "2.11.0-dev.bc1a6d4e2";
20373
20438
  var engines = {
20374
20439
  node: ">=24"
20375
20440
  };
@@ -20390,6 +20455,7 @@ var peerDependencies = {
20390
20455
  "@angular/core": "19.x || 20.x || 21.x",
20391
20456
  "@angular/forms": "19.x || 20.x || 21.x",
20392
20457
  "@angular/material": "19.x || 20.x || 21.x",
20458
+ "@angular/material-date-fns-adapter": "19.x || 20.x",
20393
20459
  "@angular/platform-browser": "19.x || 20.x || 21.x",
20394
20460
  "@angular/platform-browser-dynamic": "19.x || 20.x || 21.x",
20395
20461
  "@angular/router": "19.x || 20.x || 21.x",
@@ -20399,20 +20465,21 @@ var peerDependencies = {
20399
20465
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20466
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20467
  "@ngrx/operators": "19.x || 20.x || 21.x",
20402
- "@ngx-translate/core": "16.x",
20403
- "@ngx-translate/http-loader": "16.x",
20404
- "flag-icons": "^7.3.2",
20468
+ "@ngx-translate/core": "17.x",
20469
+ "@ngx-translate/http-loader": "17.x",
20470
+ "flag-icons": "~7.5.0",
20405
20471
  rxjs: "7.x",
20406
20472
  "zone.js": "*",
20407
20473
  tailwindcss: "3.x"
20408
20474
  };
20409
20475
  var dependencies = {
20410
20476
  "@biesbjerg/ngx-translate-extract-marker": "~1.0.0",
20411
- "@camptocamp/ogc-client": "1.3.1-dev.bb345f0",
20412
- "@geospatial-sdk/core": "0.0.5-dev.61",
20413
- "@geospatial-sdk/geocoding": "0.0.5-dev.61",
20414
- "@geospatial-sdk/legend": "0.0.5-dev.61",
20415
- "@geospatial-sdk/openlayers": "0.0.5-dev.61",
20477
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20478
+ "@duckdb/duckdb-wasm": "~1.32.0",
20479
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20480
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20481
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20482
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20483
  "@ltd/j-toml": "~1.38.0",
20417
20484
  "@messageformat/core": "~3.4.0",
20418
20485
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20487,7 @@ var dependencies = {
20420
20487
  "@ng-icons/material-icons": "~32.5.0",
20421
20488
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20489
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20490
+ "apache-arrow": "~17.0.0",
20424
20491
  "chart.js": "~4.5.1",
20425
20492
  "chroma-js": "~3.2.0",
20426
20493
  "date-fns": "4.1.0",
@@ -20433,7 +20500,6 @@ var dependencies = {
20433
20500
  "ngx-dropzone": "~3.1.0",
20434
20501
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20502
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20503
  proj4: "~2.20.4",
20438
20504
  rdflib: "~2.3.5",
20439
20505
  semver: "~7.7.4",
@@ -20678,6 +20744,7 @@ class ElasticsearchService {
20678
20744
  values: uuids,
20679
20745
  },
20680
20746
  },
20747
+ size: uuids.length,
20681
20748
  };
20682
20749
  }
20683
20750
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20785,7 +20852,13 @@ class ElasticsearchService {
20785
20852
  isCurrentSearchLang() {
20786
20853
  return this.metadataLang === 'current';
20787
20854
  }
20788
- filtersToQuery(filters) {
20855
+ findSpatialFilterExtent(filters) {
20856
+ if (typeof filters === 'string') {
20857
+ return undefined;
20858
+ }
20859
+ return Object.values(filters).find(isBoundingBox);
20860
+ }
20861
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20789
20862
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20790
20863
  const makeQuery = (filter) => {
20791
20864
  if (typeof filter === 'string') {
@@ -20804,7 +20877,9 @@ class ElasticsearchService {
20804
20877
  ? filters
20805
20878
  : Object.keys(filters)
20806
20879
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20880
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20807
20881
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20882
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20808
20883
  .filter((fieldname) => filters[fieldname] &&
20809
20884
  JSON.stringify(filters[fieldname]) !== '{}')
20810
20885
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20840,6 +20915,21 @@ class ElasticsearchService {
20840
20915
  },
20841
20916
  },
20842
20917
  },
20918
+ spatialFilterExtent && {
20919
+ geo_shape: {
20920
+ geom: {
20921
+ shape: {
20922
+ type: 'envelope',
20923
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20924
+ coordinates: [
20925
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20926
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20927
+ ],
20928
+ },
20929
+ relation: 'intersects',
20930
+ },
20931
+ },
20932
+ },
20843
20933
  ].filter(Boolean);
20844
20934
  return queryParts.length > 0 ? queryParts : undefined;
20845
20935
  }
@@ -20866,7 +20956,9 @@ class ElasticsearchService {
20866
20956
  },
20867
20957
  });
20868
20958
  }
20869
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20959
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20960
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20961
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20870
20962
  if (queryFilters) {
20871
20963
  filter.push(...queryFilters);
20872
20964
  }
@@ -20877,7 +20969,10 @@ class ElasticsearchService {
20877
20969
  },
20878
20970
  });
20879
20971
  }
20880
- if (geometry) {
20972
+ const boostGeometry = spatialFilterExtent
20973
+ ? bboxToPolygon(spatialFilterExtent)
20974
+ : geometry;
20975
+ if (boostGeometry) {
20881
20976
  // boosts applied using the filter geometry:
20882
20977
  // * records completely within the geometry receive a boost of 5
20883
20978
  // * records intersecting the geometry receive a boost of 2
@@ -20886,7 +20981,7 @@ class ElasticsearchService {
20886
20981
  should.push({
20887
20982
  geo_shape: {
20888
20983
  geom: {
20889
- shape: geometry,
20984
+ shape: boostGeometry,
20890
20985
  relation: 'within',
20891
20986
  },
20892
20987
  boost: 5.0,
@@ -20894,7 +20989,7 @@ class ElasticsearchService {
20894
20989
  }, {
20895
20990
  geo_shape: {
20896
20991
  geom: {
20897
- shape: geometry,
20992
+ shape: boostGeometry,
20898
20993
  relation: 'intersects',
20899
20994
  },
20900
20995
  boost: 2.0,
@@ -20903,7 +20998,7 @@ class ElasticsearchService {
20903
20998
  // this will boost the results variably depending on their distance from the given geometry
20904
20999
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20905
21000
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20906
- const bbox = getGeometryBoundingBox(geometry);
21001
+ const bbox = getGeometryBoundingBox(boostGeometry);
20907
21002
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20908
21003
  const northToCenter = new LineString([
20909
21004
  [center[0], bbox[3]],
@@ -21229,7 +21324,7 @@ class Gn4Repository {
21229
21324
  }
21230
21325
  getRecord(uniqueIdentifier) {
21231
21326
  return this.gn4SearchApi
21232
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21327
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21233
21328
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21234
21329
  }
21235
21330
  getMultipleRecords(uniqueIdentifiers) {
@@ -21283,19 +21378,28 @@ class Gn4Repository {
21283
21378
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21284
21379
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21285
21380
  }
21286
- getSources(record) {
21287
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'];
21288
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
21289
- return this.getMultipleRecords(sourcesIdentifiers);
21290
- }
21291
- return of(null);
21292
- }
21293
- getSourceOf(record) {
21294
- const sourceOfIdentifiers = record.extras?.['sourceOfIdentifiers'];
21295
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
21296
- return this.getMultipleRecords(sourceOfIdentifiers);
21381
+ getLinkedRecords(record) {
21382
+ const siblings = (record.extras?.['siblings'] ?? []);
21383
+ const relations = [
21384
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21385
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21386
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21387
+ [
21388
+ 'associated',
21389
+ (record.extras?.['associatedIdentifiers'] ?? []),
21390
+ ],
21391
+ ];
21392
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21393
+ if (requested.length === 0) {
21394
+ return of([]);
21297
21395
  }
21298
- return of(null);
21396
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21397
+ record,
21398
+ relation,
21399
+ associationType: relation === 'sibling'
21400
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21401
+ : undefined,
21402
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21299
21403
  }
21300
21404
  aggregate(params) {
21301
21405
  // if aggregations are empty, return an empty object right away
@@ -22556,9 +22660,10 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22556
22660
  });
22557
22661
 
22558
22662
  const DEFAULT_BASEMAP_LAYER = {
22559
- type: 'xyz',
22560
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
22561
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
22663
+ type: 'maplibre-style',
22664
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22665
+ clickable: false,
22666
+ hoverable: false,
22562
22667
  };
22563
22668
  const DEFAULT_VIEW = {
22564
22669
  center: [0, 15],
@@ -23273,6 +23378,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23273
23378
  'search_preset',
23274
23379
  'advanced_filters',
23275
23380
  'limit',
23381
+ 'spatial_extent_max_file_size',
23276
23382
  ], warnings, errors);
23277
23383
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23278
23384
  searchConfig =
@@ -23290,6 +23396,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23290
23396
  })),
23291
23397
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23292
23398
  LIMIT: parsedSearchSection.limit,
23399
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23293
23400
  };
23294
23401
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23295
23402
  metadataQualityConfig =
@@ -24547,12 +24654,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24547
24654
  type: Input
24548
24655
  }] } });
24549
24656
 
24657
+ function provideLocalizedDateAdapter() {
24658
+ return [
24659
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24660
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24661
+ {
24662
+ provide: DateAdapter,
24663
+ useFactory: () => {
24664
+ const dateService = inject(DateService);
24665
+ const adapter = new DateFnsAdapter();
24666
+ dateService
24667
+ .getDateFnsLocale()
24668
+ .then((locale) => adapter.setLocale(locale));
24669
+ return adapter;
24670
+ },
24671
+ },
24672
+ ];
24673
+ }
24674
+
24550
24675
  class DatePickerComponent {
24551
24676
  constructor() {
24552
- this.dateAdapter = inject(DateAdapter);
24553
- this.translate = inject(TranslateService);
24554
24677
  this.dateChange = new EventEmitter();
24555
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24556
24678
  }
24557
24679
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24558
24680
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DatePickerComponent, isStandalone: true, selector: "gn-ui-date-picker", inputs: { date: "date" }, outputs: { dateChange: "dateChange" }, providers: [
@@ -24560,30 +24682,19 @@ class DatePickerComponent {
24560
24682
  provideNgIconsConfig({
24561
24683
  size: '1.5rem',
24562
24684
  }),
24563
- {
24564
- provide: MAT_DATE_LOCALE,
24565
- useFactory: (locale) => locale,
24566
- },
24567
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24685
+ provideLocalizedDateAdapter(),
24686
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$6.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24568
24687
  }
24569
24688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24570
24689
  type: Component,
24571
- args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24572
- MatNativeDateModule,
24573
- MatDatepickerModule,
24574
- ButtonComponent,
24575
- NgIconComponent,
24576
- ], providers: [
24690
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24577
24691
  provideIcons({ iconoirCalendar }),
24578
24692
  provideNgIconsConfig({
24579
24693
  size: '1.5rem',
24580
24694
  }),
24581
- {
24582
- provide: MAT_DATE_LOCALE,
24583
- useFactory: (locale) => locale,
24584
- },
24695
+ provideLocalizedDateAdapter(),
24585
24696
  ], template: "<span class=\"w-full inline-block relative\">\n <input\n class=\"gn-ui-text-input pr-[var(--text-padding)]\"\n [matDatepicker]=\"picker\"\n [value]=\"date\"\n (dateChange)=\"dateChange.emit($event.value)\"\n />\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n data-cy=\"date-picker-button\"\n extraClass=\"h-full\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-datepicker #picker></mat-datepicker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24586
- }], ctorParameters: () => [], propDecorators: { date: [{
24697
+ }], propDecorators: { date: [{
24587
24698
  type: Input
24588
24699
  }], dateChange: [{
24589
24700
  type: Output
@@ -24591,58 +24702,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24591
24702
 
24592
24703
  class DateRangeDropdownComponent {
24593
24704
  constructor() {
24594
- this.overlayContainer = inject(OverlayContainer);
24705
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24706
+ this.dateAdapter = inject(DateAdapter);
24707
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24595
24708
  this.cdr = inject(ChangeDetectorRef);
24596
- this.startDateChange = new EventEmitter();
24597
- this.endDateChange = new EventEmitter();
24598
- this.isPickerDisplayed = false;
24709
+ this.dateRange = {};
24710
+ this.dateRangeChange = new EventEmitter();
24711
+ this.overlayPositions = [
24712
+ {
24713
+ originX: 'start',
24714
+ originY: 'bottom',
24715
+ overlayX: 'start',
24716
+ overlayY: 'top',
24717
+ offsetY: 8,
24718
+ },
24719
+ {
24720
+ originX: 'start',
24721
+ originY: 'top',
24722
+ overlayX: 'start',
24723
+ overlayY: 'bottom',
24724
+ offsetY: -8,
24725
+ },
24726
+ ];
24727
+ this.scrollStrategy = this.scrollStrategies.reposition();
24728
+ this.overlayOpen = false;
24729
+ this.expandedBound = 'start';
24730
+ this.invalidBounds = { start: false, end: false };
24731
+ // redraw when the lazily loaded UI locale reaches the adapter
24732
+ this.dateAdapter.localeChanges
24733
+ .pipe(takeUntilDestroyed())
24734
+ .subscribe(() => this.cdr.markForCheck());
24599
24735
  }
24600
- ngAfterViewChecked() {
24601
- this.checkPickerOverlay();
24736
+ get startDate() {
24737
+ return this.dateRange.start;
24602
24738
  }
24603
- checkPickerOverlay() {
24604
- const overlayContainerElement = this.overlayContainer.getContainerElement();
24605
- setTimeout(() => {
24606
- this.isPickerDisplayed =
24607
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
24608
- null;
24609
- this.cdr.detectChanges();
24610
- }, 200); // FIXME: find a better way to deal with animation delay
24739
+ get endDate() {
24740
+ return this.dateRange.end;
24741
+ }
24742
+ get selectedDatesCount() {
24743
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24744
+ }
24745
+ openOverlay() {
24746
+ this.expandedBound = 'start';
24747
+ this.overlayOpen = true;
24748
+ }
24749
+ closeOverlay() {
24750
+ this.overlayOpen = false;
24751
+ }
24752
+ toggleBound(bound) {
24753
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24754
+ }
24755
+ formatDate(date) {
24756
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24757
+ }
24758
+ onDateInput(bound, event) {
24759
+ const typedText = event.target.value.trim();
24760
+ const date = typedText
24761
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24762
+ : null;
24763
+ if (!this.isAcceptable(bound, typedText, date)) {
24764
+ // keep the text as typed and flag it; the filter stays on its last value
24765
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24766
+ return;
24767
+ }
24768
+ if (bound === 'start')
24769
+ this.setStartDate(date);
24770
+ else
24771
+ this.setEndDate(date);
24772
+ }
24773
+ normalizeDateInput(bound, event) {
24774
+ if (this.invalidBounds[bound])
24775
+ return;
24776
+ const date = bound === 'start' ? this.startDate : this.endDate;
24777
+ const input = event.target;
24778
+ input.value = date ? this.formatDate(date) : '';
24779
+ }
24780
+ isAcceptable(bound, typedText, date) {
24781
+ if (!typedText)
24782
+ return true;
24783
+ if (!date || !this.dateAdapter.isValid(date))
24784
+ return false;
24785
+ return bound === 'start'
24786
+ ? !this.endDate || date <= this.endDate
24787
+ : !this.startDate || date >= this.startDate;
24788
+ }
24789
+ setStartDate(date) {
24790
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24791
+ this.applyDateRange({ ...this.dateRange, start: date });
24792
+ this.expandedBound = 'end';
24793
+ }
24794
+ setEndDate(date) {
24795
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24796
+ this.applyDateRange({ ...this.dateRange, end: date });
24797
+ this.expandedBound = null;
24798
+ }
24799
+ clearDates(event) {
24800
+ this.invalidBounds = { start: false, end: false };
24801
+ this.expandedBound = 'start';
24802
+ this.applyDateRange({});
24803
+ propagateToDocumentOnly(event);
24804
+ }
24805
+ applyDateRange(dateRange) {
24806
+ this.dateRange = {
24807
+ ...(dateRange.start && { start: dateRange.start }),
24808
+ ...(dateRange.end && { end: dateRange.end }),
24809
+ };
24810
+ this.dateRangeChange.emit(this.dateRange);
24611
24811
  }
24612
24812
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24613
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", startDate: "startDate", endDate: "endDate" }, outputs: { startDateChange: "startDateChange", endDateChange: "endDateChange" }, providers: [
24813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DateRangeDropdownComponent, isStandalone: true, selector: "gn-ui-date-range-dropdown", inputs: { title: "title", dateRange: "dateRange" }, outputs: { dateRangeChange: "dateRangeChange" }, providers: [
24614
24814
  provideIcons({
24615
- matExpandMore,
24815
+ iconoirCalendar,
24816
+ matClose,
24616
24817
  matExpandLess,
24818
+ matExpandMore,
24819
+ }),
24820
+ provideNgIconsConfig({
24821
+ size: '1.5rem',
24617
24822
  }),
24618
- ], viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"], dependencies: [{ kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }] }); }
24823
+ provideLocalizedDateAdapter(),
24824
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"], dependencies: [{ kind: "directive", type: AutofocusDirective, selector: "[gnUiAutofocus]", inputs: ["gnUiAutofocus"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatCalendar, selector: "mat-calendar", inputs: ["headerComponent", "startAt", "startView", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedChange", "yearSelected", "monthSelected", "viewChanged", "_userSelection", "_userDragDrop"], exportAs: ["matCalendar"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24619
24825
  }
24620
24826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24621
24827
  type: Component,
24622
24828
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24623
- NgIconComponent,
24624
- MatNativeDateModule,
24625
- MatDatepickerModule,
24829
+ AutofocusDirective,
24626
24830
  ButtonComponent,
24831
+ MatDatepickerModule,
24832
+ NgIcon,
24833
+ NgTemplateOutlet,
24834
+ OverlayModule,
24835
+ TranslateDirective,
24836
+ TranslatePipe,
24627
24837
  ], providers: [
24628
24838
  provideIcons({
24629
- matExpandMore,
24839
+ iconoirCalendar,
24840
+ matClose,
24630
24841
  matExpandLess,
24842
+ matExpandMore,
24631
24843
  }),
24632
- ], template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n (buttonClick)=\"picker.open()\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n </div>\n <ng-icon\n [name]=\"isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n<!--date range input is just present for output events and hidden from the DOM-->\n<mat-date-range-input [rangePicker]=\"picker\">\n <input\n matStartDate\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n</mat-date-range-input>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host .mat-date-range-input-container{display:none}\n"] }]
24633
- }], propDecorators: { title: [{
24844
+ provideNgIconsConfig({
24845
+ size: '1.5rem',
24846
+ }),
24847
+ provideLocalizedDateAdapter(),
24848
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (selectedDatesCount) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selectedDatesCount }}\n </div>\n }\n </div>\n <button\n class=\"h-6 w-6 flex items-center justify-center\"\n data-test=\"dropdown-clear\"\n >\n @if (selectedDatesCount && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearDates($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n data-test=\"dropdown-toggle\"\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]\"\n data-test=\"date-range-panel\"\n >\n <div class=\"flex flex-col justify-center border-b border-gray-300 pb-1\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'start',\n label: 'daterange.filter.from',\n date: startDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'start') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"start-date-calendar\"\n [selected]=\"startDate\"\n [startAt]=\"startDate ?? endDate\"\n [maxDate]=\"endDate\"\n (selectedChange)=\"setStartDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n <div class=\"flex flex-col justify-center\">\n <ng-container\n [ngTemplateOutlet]=\"boundRow\"\n [ngTemplateOutletContext]=\"{\n bound: 'end',\n label: 'daterange.filter.to',\n date: endDate,\n }\"\n ></ng-container>\n @if (expandedBound === 'end') {\n <div class=\"calendar\" animate.enter=\"expand\" animate.leave=\"collapse\">\n <mat-calendar\n data-test=\"end-date-calendar\"\n [selected]=\"endDate\"\n [startAt]=\"endDate ?? startDate\"\n [minDate]=\"startDate\"\n (selectedChange)=\"setEndDate($event)\"\n ></mat-calendar>\n </div>\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #boundRow let-bound=\"bound\" let-label=\"label\" let-date=\"date\">\n <div class=\"flex items-center gap-1 w-full\">\n <span class=\"w-[80px] shrink-0\" [translate]=\"label\"></span>\n <div\n class=\"flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors\"\n [class]=\"\n invalidBounds[bound]\n ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'\n : 'border-gray-400 focus-within:border-main hover:border-main'\n \"\n >\n <ng-icon\n name=\"iconoirCalendar\"\n class=\"shrink-0\"\n aria-hidden=\"true\"\n ></ng-icon>\n <input\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n [gnUiAutofocus]=\"bound === expandedBound\"\n class=\"min-w-0 grow bg-transparent focus:outline-none\"\n [value]=\"date ? formatDate(date) : ''\"\n [placeholder]=\"'daterange.filter.datePlaceholder' | translate\"\n [attr.aria-label]=\"label | translate\"\n [attr.aria-invalid]=\"invalidBounds[bound] || null\"\n [attr.data-test]=\"bound + '-date-input'\"\n (change)=\"onDateInput(bound, $event)\"\n (blur)=\"normalizeDateInput(bound, $event)\"\n (click)=\"expandedBound !== bound && toggleBound(bound)\"\n />\n </div>\n <div class=\"shrink-0 flex items-center justify-end\">\n <gn-ui-button\n type=\"light\"\n [style.--gn-ui-button-padding]=\"0\"\n [style.--gn-ui-button-width]=\"'24px'\"\n [style.--gn-ui-button-height]=\"'24px'\"\n [attr.aria-expanded]=\"expandedBound === bound\"\n [attr.data-test]=\"bound + '-date-toggle'\"\n (buttonClick)=\"toggleBound(bound)\"\n >\n <ng-icon\n [name]=\"expandedBound === bound ? 'matExpandLess' : 'matExpandMore'\"\n ></ng-icon>\n </gn-ui-button>\n </div>\n </div>\n</ng-template>\n", styles: ["mat-calendar{width:100%}.calendar{display:grid;grid-template-rows:1fr}.calendar>mat-calendar{min-height:0;overflow:hidden}.calendar.expand{animation:open .2s ease-out}.calendar.collapse{animation:open .2s ease-in reverse}@keyframes open{0%{grid-template-rows:0fr;opacity:0}to{grid-template-rows:1fr;opacity:1}}@media (prefers-reduced-motion: reduce){.calendar.expand,.calendar.collapse{animation-duration:1ms}}\n"] }]
24849
+ }], ctorParameters: () => [], propDecorators: { title: [{
24634
24850
  type: Input
24635
- }], startDate: [{
24851
+ }], dateRange: [{
24636
24852
  type: Input
24637
- }], endDate: [{
24638
- type: Input
24639
- }], startDateChange: [{
24640
- type: Output
24641
- }], endDateChange: [{
24853
+ }], dateRangeChange: [{
24642
24854
  type: Output
24643
- }], picker: [{
24855
+ }], overlayOrigin: [{
24644
24856
  type: ViewChild,
24645
- args: ['picker']
24857
+ args: ['overlayOrigin']
24646
24858
  }] } });
24647
24859
 
24648
24860
  class DateRangePickerComponent {
@@ -24656,20 +24868,17 @@ class DateRangePickerComponent {
24656
24868
  provideNgIconsConfig({
24657
24869
  size: '1.5rem',
24658
24870
  }),
24659
- ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24871
+ provideLocalizedDateAdapter(),
24872
+ ], ngImport: i0, template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"], dependencies: [{ kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i1$6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i1$6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i1$6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24660
24873
  }
24661
24874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24662
24875
  type: Component,
24663
- args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
24664
- MatNativeDateModule,
24665
- MatDatepickerModule,
24666
- ButtonComponent,
24667
- NgIconComponent,
24668
- ], providers: [
24876
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24669
24877
  provideIcons({ iconoirCalendar }),
24670
24878
  provideNgIconsConfig({
24671
24879
  size: '1.5rem',
24672
24880
  }),
24881
+ provideLocalizedDateAdapter(),
24673
24882
  ], template: "<span class=\"w-full inline-block relative\">\n <mat-date-range-input [rangePicker]=\"picker\" class=\"gn-ui-text-input\">\n <input\n matStartDate\n placeholder=\"Start date\"\n [value]=\"startDate\"\n (dateInput)=\"startDateChange.emit($event.value)\"\n />\n <input\n matEndDate\n placeholder=\"End date\"\n [value]=\"endDate\"\n (dateInput)=\"endDateChange.emit($event.value)\"\n />\n </mat-date-range-input>\n\n <gn-ui-button\n type=\"light\"\n class=\"absolute inset-y-[var(--side-padding)] right-[var(--side-padding)] z-10\"\n (buttonClick)=\"picker.open()\"\n extraClass=\"h-full\"\n data-cy=\"date-picker-button\"\n >\n <ng-icon name=\"iconoirCalendar\" class=\"text-primary\"></ng-icon>\n </gn-ui-button>\n</span>\n<mat-date-range-picker #picker></mat-date-range-picker>\n", styles: [":host{--gn-ui-button-rounded: 8px;--gn-ui-button-width: 32px;--gn-ui-button-padding: 0;--side-padding: calc(var(--gn-ui-text-input-padding, .6em) - 6px);--text-padding: calc(var(--side-padding) + 40px)}\n"] }]
24674
24883
  }], propDecorators: { startDate: [{
24675
24884
  type: Input
@@ -24687,28 +24896,68 @@ class DragAndDropFileInputComponent {
24687
24896
  constructor() {
24688
24897
  this.placeholder = placeholder;
24689
24898
  this.accept = '*';
24899
+ this.maxFileSizeMb = null;
24900
+ this.icon = null;
24901
+ this.dropzoneBackgroundColor = null;
24902
+ this.textClass = '';
24903
+ this.extraClass = '';
24904
+ this.showFileName = true;
24690
24905
  this.fileChange = new EventEmitter();
24906
+ this.errorChange = new EventEmitter();
24691
24907
  this.selectedFile = null;
24692
24908
  }
24693
24909
  get fileName() {
24694
24910
  return this.selectedFile && this.selectedFile.name;
24695
24911
  }
24912
+ get maxFileSizeBytes() {
24913
+ return typeof this.maxFileSizeMb === 'number'
24914
+ ? megabytesToBytes(this.maxFileSizeMb)
24915
+ : null;
24916
+ }
24696
24917
  selectFile(event) {
24918
+ if (event.rejectedFiles?.length) {
24919
+ const reason = event.rejectedFiles[0].reason;
24920
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24921
+ return;
24922
+ }
24697
24923
  this.selectedFile = event.addedFiles[0];
24698
24924
  this.fileChange.emit(this.selectedFile);
24699
24925
  }
24926
+ openFileSelector() {
24927
+ this.dropzone.showFileSelector();
24928
+ }
24929
+ clear() {
24930
+ this.selectedFile = null;
24931
+ }
24700
24932
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24701
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept" }, outputs: { fileChange: "fileChange" }, ngImport: i0, template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$7.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }] }); }
24933
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DragAndDropFileInputComponent, isStandalone: true, selector: "gn-ui-drag-and-drop-file-input", inputs: { placeholder: "placeholder", accept: "accept", maxFileSizeMb: "maxFileSizeMb", icon: "icon", dropzoneBackgroundColor: "dropzoneBackgroundColor", textClass: "textClass", extraClass: "extraClass", showFileName: "showFileName" }, outputs: { fileChange: "fileChange", errorChange: "errorChange" }, viewQueries: [{ propertyName: "dropzone", first: true, predicate: NgxDropzoneComponent, descendants: true }], ngImport: i0, template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: NgxDropzoneModule }, { kind: "component", type: i1$8.NgxDropzoneComponent, selector: "ngx-dropzone, [ngx-dropzone]", inputs: ["accept", "disabled", "multiple", "maxFileSize", "expandable", "disableClick", "processDirectoryDrop", "id", "aria-label", "aria-labelledby", "aria-describedby"], outputs: ["change"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
24702
24934
  }
24703
24935
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24704
24936
  type: Component,
24705
- args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule], template: "<div class=\"flex h-full\">\n <ngx-dropzone\n class=\"flex-1\"\n [multiple]=\"false\"\n (change)=\"selectFile($event)\"\n [accept]=\"accept\"\n >\n @if (!fileName) {\n <div class=\"text-gray-900 pl-2 py-2\" translate=\"\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName) {\n <div class=\"text-gray-900 pl-2 py-2\">{{ fileName }}</div>\n }\n </ngx-dropzone>\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none}\n"] }]
24937
+ args: [{ selector: 'gn-ui-drag-and-drop-file-input', standalone: true, imports: [NgxDropzoneModule, NgIcon], template: "<div\n class=\"flex h-full items-center\"\n [class]=\"extraClass\"\n (click)=\"openFileSelector()\"\n>\n <ngx-dropzone\n class=\"flex-1 overflow-hidden\"\n [multiple]=\"false\"\n [disableClick]=\"true\"\n [accept]=\"accept\"\n [maxFileSize]=\"maxFileSizeBytes\"\n [style.background-color]=\"dropzoneBackgroundColor\"\n (change)=\"selectFile($event)\"\n >\n @if (!fileName || !showFileName) {\n <div [class]=\"textClass\">\n {{ placeholder }}\n </div>\n }\n\n @if (fileName && showFileName) {\n <div [class]=\"textClass\">\n {{ fileName }}\n </div>\n }\n </ngx-dropzone>\n\n @if (icon) {\n <ng-icon [name]=\"icon\" class=\"shrink-0 text-gray-950\"></ng-icon>\n }\n</div>\n", styles: ["ngx-dropzone{height:auto;border:none;overflow:hidden}\n"] }]
24706
24938
  }], propDecorators: { placeholder: [{
24707
24939
  type: Input
24708
24940
  }], accept: [{
24709
24941
  type: Input
24942
+ }], maxFileSizeMb: [{
24943
+ type: Input
24944
+ }], icon: [{
24945
+ type: Input
24946
+ }], dropzoneBackgroundColor: [{
24947
+ type: Input
24948
+ }], textClass: [{
24949
+ type: Input
24950
+ }], extraClass: [{
24951
+ type: Input
24952
+ }], showFileName: [{
24953
+ type: Input
24710
24954
  }], fileChange: [{
24711
24955
  type: Output
24956
+ }], errorChange: [{
24957
+ type: Output
24958
+ }], dropzone: [{
24959
+ type: ViewChild,
24960
+ args: [NgxDropzoneComponent]
24712
24961
  }] } });
24713
24962
 
24714
24963
  class DropdownMultiselectComponent {
@@ -24863,7 +25112,7 @@ class DropdownMultiselectComponent {
24863
25112
  matExpandMore,
24864
25113
  matExpandLess,
24865
25114
  }),
24866
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25115
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "overlayContainer", first: true, predicate: ["overlayContainer"], descendants: true, read: ElementRef }, { propertyName: "searchFieldInput", first: true, predicate: ["searchFieldInput"], descendants: true }, { propertyName: "checkboxes", predicate: ["checkBox"], descendants: true, read: ElementRef }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24867
25116
  }
24868
25117
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24869
25118
  type: Component,
@@ -24873,7 +25122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24873
25122
  matExpandMore,
24874
25123
  matExpandLess,
24875
25124
  }),
24876
- ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-cy=\"clearSelection\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
25125
+ ], standalone: true, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n (keydown)=\"handleTriggerKeydown($event)\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelectedChoices) {\n <div\n class=\"gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count\"\n >\n {{ selected.length }}\n </div>\n }\n </div>\n <button class=\"h-6 w-6\" data-test=\"dropdown-clear\">\n @if (hasSelectedChoices && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"clearSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayMinWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n #overlayContainer\n >\n <div\n class=\"border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-[2px] flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary\"\n >\n @for (selected of selectedChoices; track selected) {\n <button\n type=\"button\"\n [title]=\"selected.label\"\n class=\"max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity mb-1\"\n (click)=\"select(selected, false)\"\n >\n <div class=\"text-sm truncate leading-[26px] px-2\">\n {{ selected.label }}\n </div>\n <div\n class=\"flex items-center justify-center rounded-full bg-white text-main h-[13px] w-[13px] pt-px -mt-px shrink-0\"\n >\n <ng-icon\n name=\"matClose\"\n class=\"!h-[12px] !w-[11px] text-[12px]\"\n ></ng-icon>\n </div>\n </button>\n }\n\n @if (allowSearch) {\n <div class=\"w-[50%] relative grow shrink\">\n <input\n #searchFieldInput\n class=\"w-full px-2 truncate text-[14px] h-full overlaySearchInput focus:outline-none\"\n [(ngModel)]=\"searchInputValue\"\n [placeholder]=\"'multiselect.filter.placeholder' | translate\"\n />\n @if (!!searchInputValue) {\n <button\n class=\"absolute top-1/2 -translate-y-1/2 right-0 px-[7px] leading-tight clear-search-input mr-2\"\n (click)=\"clearSearchInputValue($event)\"\n >\n <ng-icon class=\"!h-[10px] !w-[12px] text-[12px]\" name=\"matClose\">\n </ng-icon>\n </button>\n }\n </div>\n }\n </div>\n\n @for (choice of filteredChoicesByText; track choice) {\n <label\n [title]=\"choice.label\"\n class=\"flex px-5 py-1 w-full text-gray-900 cursor-pointer hover:text-primary-darkest hover:bg-gray-50 focus-within:text-primary-darkest focus-within:bg-gray-50 transition-colors\"\n >\n <input\n class=\"w-[18px] h-[18px] align-text-top shrink-0\"\n type=\"checkbox\"\n #checkBox\n [checked]=\"isSelected(choice)\"\n (change)=\"select(choice, checkBox.checked)\"\n />\n <span class=\"ml-[8px] text-[14px] truncate\">\n {{ choice.label }}\n </span>\n </label>\n }\n </div>\n</ng-template>\n" }]
24877
25126
  }], propDecorators: { title: [{
24878
25127
  type: Input
24879
25128
  }], choices: [{
@@ -25045,7 +25294,7 @@ class DropdownSelectorComponent {
25045
25294
  provideNgIconsConfig({
25046
25295
  size: '1.5em',
25047
25296
  }),
25048
- ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25297
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "choiceInputs", predicate: ["choiceInputs"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"flex flex-col sm:flex-row sm:items-center relative w-full\">\n @if (showTitle) {\n <span\n class=\"tracking-wide text-sm mb-2 sm:mb-0 sm:mr-2 whitespace-nowrap\"\n [attr.for]=\"id\"\n >\n {{ title }}\n </span>\n }\n <gn-ui-button\n type=\"outline\"\n class=\"grow min-w-0\"\n [style.--gn-ui-button-padding]=\"'8px 8px 8px 16px'\"\n extraClass=\"bg-background flex flex-row w-full {{ extraBtnClass }}\"\n [title]=\"title\"\n [attr.aria-owns]=\"id\"\n (buttonClick)=\"openOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n (keydown)=\"handleTriggerKeydown($event)\"\n [disabled]=\"disabled\"\n >\n <div class=\"grow truncate py-1 mr-2 text-left\">\n {{ getChoiceLabel() | translate }}\n </div>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n </gn-ui-button>\n</div>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container\"\n [style.max-height]=\"overlayMaxHeight\"\n [style.min-width]=\"overlayWidth\"\n role=\"listbox\"\n tabindex=\"-1\"\n [attr.id]=\"id\"\n [attr.aria-multiselectable]=\"true\"\n [attr.aria-label]=\"title\"\n (keydown)=\"handleOverlayKeydown($event)\"\n >\n @for (choice of choices; track choice) {\n <button\n #choiceInputs\n type=\"button\"\n [title]=\"choice.label | translate\"\n class=\"flex px-5 py-1 w-full text-start cursor-pointer transition-colors\"\n [ngClass]=\"\n isSelected(choice)\n ? 'text-white bg-primary hover:text-white hover:bg-primary-darker focus:text-white focus:bg-primary-darker'\n : 'text-gray-900 hover:text-primary-darkest hover:bg-gray-50 focus:text-primary-darkest focus:bg-gray-50'\n \"\n (click)=\"onSelectValue(choice)\"\n (keydown)=\"selectIfEnter($event, choice)\"\n [attr.data-cy-value]=\"choice.value.toString()\"\n [attr.data-cy-active]=\"isSelected(choice) ? 'true' : undefined\"\n >\n <span class=\"text-[14px]\">\n {{ choice.label | translate }}\n </span>\n </button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
25049
25298
  }
25050
25299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25051
25300
  type: Component,
@@ -25785,6 +26034,175 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25785
26034
  type: Output
25786
26035
  }] } });
25787
26036
 
26037
+ marker('search.filters.spatialExtent.import');
26038
+ marker('search.filters.spatialExtent.helpText');
26039
+ marker('search.filters.spatialExtent.error.title');
26040
+ const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile');
26041
+ const LABEL_FROM_FILE_DELETE = marker('search.filters.spatialExtent.bboxFromFileDelete');
26042
+ const LABEL_INITIAL = marker('search.filters.spatialExtent.bboxInitial');
26043
+ const LABEL_INITIAL_DELETE = marker('search.filters.spatialExtent.bboxInitialDelete');
26044
+ class SpatialExtentDropdownComponent {
26045
+ constructor() {
26046
+ this.cd = inject(ChangeDetectorRef);
26047
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26048
+ this.maxFileSizeMb = null;
26049
+ this.bboxChange = new EventEmitter();
26050
+ this.errorChange = new EventEmitter();
26051
+ this.bbox = null;
26052
+ this.fileName = '';
26053
+ this.overlayPositions = [
26054
+ {
26055
+ originX: 'start',
26056
+ originY: 'bottom',
26057
+ overlayX: 'start',
26058
+ overlayY: 'top',
26059
+ offsetY: 8,
26060
+ },
26061
+ {
26062
+ originX: 'start',
26063
+ originY: 'top',
26064
+ overlayX: 'start',
26065
+ overlayY: 'bottom',
26066
+ offsetY: -8,
26067
+ },
26068
+ ];
26069
+ this.scrollStrategy = this.scrollStrategies.reposition();
26070
+ this.overlayOpen = false;
26071
+ this.overlayMinWidth = 'none';
26072
+ this.errorKey = null;
26073
+ }
26074
+ set initialBbox(value) {
26075
+ if (!this.bbox && value) {
26076
+ this.bbox = value;
26077
+ }
26078
+ }
26079
+ get hasSelection() {
26080
+ return !!this.bbox;
26081
+ }
26082
+ get selectionLabelKey() {
26083
+ return this.fileName ? LABEL_FROM_FILE : LABEL_INITIAL;
26084
+ }
26085
+ get selectionDeleteLabelKey() {
26086
+ return this.fileName ? LABEL_FROM_FILE_DELETE : LABEL_INITIAL_DELETE;
26087
+ }
26088
+ get selectionLabelParams() {
26089
+ return { fileName: this.fileName };
26090
+ }
26091
+ openOverlay() {
26092
+ this.overlayMinWidth =
26093
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26094
+ .width + 'px';
26095
+ this.overlayOpen = true;
26096
+ }
26097
+ closeOverlay() {
26098
+ this.overlayOpen = false;
26099
+ }
26100
+ toggleOverlay() {
26101
+ if (this.overlayOpen) {
26102
+ this.closeOverlay();
26103
+ }
26104
+ else {
26105
+ this.openOverlay();
26106
+ }
26107
+ }
26108
+ async handleFileSelected(file) {
26109
+ this.errorKey = null;
26110
+ let content;
26111
+ try {
26112
+ content = await readFileAsText(file);
26113
+ const parsed = JSON.parse(content);
26114
+ const geometry = getGeometryFromGeoJSON(parsed);
26115
+ if (!geometry) {
26116
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26117
+ return;
26118
+ }
26119
+ const bbox = getGeometryBoundingBox(geometry);
26120
+ this.bbox = bbox;
26121
+ this.fileName = file.name;
26122
+ this.bboxChange.emit(bbox);
26123
+ this.cd.markForCheck();
26124
+ }
26125
+ catch {
26126
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26127
+ return;
26128
+ }
26129
+ }
26130
+ handleFileError(error) {
26131
+ if (error === 'file-too-large') {
26132
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26133
+ maxSize: this.maxFileSizeMb,
26134
+ });
26135
+ }
26136
+ else {
26137
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26138
+ }
26139
+ }
26140
+ setError(errorKey, params) {
26141
+ this.errorKey = errorKey;
26142
+ this.errorChange.emit({ key: errorKey, params });
26143
+ this.cd.markForCheck();
26144
+ }
26145
+ removeSelection(event) {
26146
+ this.bbox = null;
26147
+ this.fileName = '';
26148
+ this.errorKey = null;
26149
+ this.fileInput?.clear();
26150
+ this.bboxChange.emit(null);
26151
+ propagateToDocumentOnly(event);
26152
+ }
26153
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26154
+ 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: [
26155
+ provideIcons({
26156
+ iconoirCheckCircle,
26157
+ iconoirImport,
26158
+ iconoirSquareDashed,
26159
+ iconoirTrash,
26160
+ matClose,
26161
+ matExpandLess,
26162
+ matExpandMore,
26163
+ }),
26164
+ ], 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 }); }
26165
+ }
26166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26167
+ type: Component,
26168
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26169
+ ButtonComponent,
26170
+ NgIcon,
26171
+ OverlayModule,
26172
+ TranslatePipe,
26173
+ DragAndDropFileInputComponent,
26174
+ ], providers: [
26175
+ provideIcons({
26176
+ iconoirCheckCircle,
26177
+ iconoirImport,
26178
+ iconoirSquareDashed,
26179
+ iconoirTrash,
26180
+ matClose,
26181
+ matExpandLess,
26182
+ matExpandMore,
26183
+ }),
26184
+ ], 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" }]
26185
+ }], propDecorators: { title: [{
26186
+ type: Input
26187
+ }], maxFileSizeMb: [{
26188
+ type: Input
26189
+ }], initialBbox: [{
26190
+ type: Input
26191
+ }], bboxChange: [{
26192
+ type: Output
26193
+ }], errorChange: [{
26194
+ type: Output
26195
+ }], overlayOrigin: [{
26196
+ type: ViewChild,
26197
+ args: ['overlayOrigin']
26198
+ }], overlay: [{
26199
+ type: ViewChild,
26200
+ args: [CdkConnectedOverlay]
26201
+ }], fileInput: [{
26202
+ type: ViewChild,
26203
+ args: [DragAndDropFileInputComponent]
26204
+ }] } });
26205
+
25788
26206
  class CellPopinComponent {
25789
26207
  constructor() {
25790
26208
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25861,7 +26279,7 @@ class CellPopinComponent {
25861
26279
  this.isOpen = false;
25862
26280
  }
25863
26281
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
25864
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
26282
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CellPopinComponent, isStandalone: true, selector: "gn-ui-cell-popin", inputs: { extraClass: "extraClass", cdkScrollContainer: "cdkScrollContainer", scrollContainer: "scrollContainer", activePopin: "activePopin" }, providers: [provideIcons({ iconoirReduce })], viewQueries: [{ propertyName: "anchorRef", first: true, predicate: ["anchorRef"], descendants: true }], ngImport: i0, template: "<div\n class=\"h-full flex items-center justify-center\"\n cdkOverlayOrigin\n #anchorRef\n #trigger=\"cdkOverlayOrigin\"\n [class]=\"extraClass\"\n data-cy=\"cell-popin\"\n>\n <ng-content select=\"[cellContent]\"></ng-content>\n @if (activePopin) {\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayPush]=\"false\"\n [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"'auto'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayGrowAfterOpen]=\"true\"\n (detach)=\"closeOverlay()\"\n >\n @if (activePopin && isVisible) {\n <div\n class=\"bg-white shadow-lg border border-gray-300 relative\"\n data-cy=\"cell-popin-content\"\n >\n <ng-content select=\"[popinContent]\"></ng-content>\n <gn-ui-button\n data-cy=\"cell-popin-close\"\n [style.--gn-ui-button-background]=\"'bg-transparent'\"\n class=\"absolute top-2 right-2\"\n type=\"light\"\n (buttonClick)=\"closeOverlay()\"\n extraClass=\"w-10 h-8 px-1\"\n >\n <ng-icon name=\"iconoirReduce\" size=\"24\"></ng-icon>\n </gn-ui-button>\n </div>\n }\n </ng-template>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIconComponent, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] }); }
25865
26283
  }
25866
26284
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25867
26285
  type: Component,
@@ -27951,6 +28369,23 @@ class DateRangeSearchField extends SimpleSearchField {
27951
28369
  return 'dateRange';
27952
28370
  }
27953
28371
  }
28372
+ class BoundingBoxSearchField extends SimpleSearchField {
28373
+ getAvailableValues() {
28374
+ return of([]);
28375
+ }
28376
+ getFiltersForValues(values) {
28377
+ return of({
28378
+ [this.esFieldName]: values.map(Number),
28379
+ });
28380
+ }
28381
+ getValuesForFilter(filters) {
28382
+ const filter = filters[this.esFieldName];
28383
+ return of(isBoundingBox(filter) ? filter : []);
28384
+ }
28385
+ getType() {
28386
+ return 'spatialExtent';
28387
+ }
28388
+ }
27954
28389
  marker('search.filters.availableServices.view');
27955
28390
  marker('search.filters.availableServices.download');
27956
28391
  class AvailableServicesField extends SimpleSearchField {
@@ -28095,6 +28530,7 @@ marker('search.filters.producerOrg');
28095
28530
  marker('search.filters.publisherOrg');
28096
28531
  marker('search.filters.user');
28097
28532
  marker('search.filters.changeDate');
28533
+ marker('search.filters.spatialExtent');
28098
28534
  class FieldsService {
28099
28535
  constructor() {
28100
28536
  this.injector = inject(Injector);
@@ -28118,6 +28554,7 @@ class FieldsService {
28118
28554
  user: new UserSearchField(this.injector),
28119
28555
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28120
28556
  availableServices: new AvailableServicesField(this.injector),
28557
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28121
28558
  };
28122
28559
  }
28123
28560
  get supportedFields() {
@@ -29380,7 +29817,7 @@ class ContactPillComponent {
29380
29817
  this.overlayOpen = false;
29381
29818
  }
29382
29819
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29383
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$8.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$8.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29820
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ContactPillComponent, isStandalone: true, selector: "gn-ui-contact-pill", inputs: { contact: "contact" }, ngImport: i0, template: "<gn-ui-button\n [type]=\"overlayOpen ? 'gray-light' : 'primary-light'\"\n extraClass=\"group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded\"\n data-test=\"contact-pill\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <span\n class=\"font-title font-medium text-base leading-tight truncate\"\n [class]=\"!overlayOpen ? 'text-primary-black group-hover:text-white' : ''\"\n [title]=\"displayName\"\n >{{ displayName }}</span\n >\n <div\n class=\"gn-ui-card-icon items-center justify-center w-10 h-8\"\n [class]=\"\n !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''\n \"\n >\n @if (overlayOpen) {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matClose\"></ng-icon>\n } @else {\n <ng-icon class=\"!w-6 !h-6 !text-[24px]\" name=\"matInfoOutline\"></ng-icon>\n }\n </div>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayOffsetX]=\"overlayOffsetX\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div [style.width.px]=\"overlayWidth\">\n <gn-ui-contact-details [contact]=\"contact\"></gn-ui-contact-details>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ContactDetailsComponent, selector: "gn-ui-contact-details", inputs: ["contact"] }], viewProviders: [provideIcons({ matClose, matInfoOutline })], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29384
29821
  }
29385
29822
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29386
29823
  type: Component,
@@ -31302,19 +31739,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31302
31739
  type: Output
31303
31740
  }] } });
31304
31741
 
31742
+ class NotificationsService {
31743
+ constructor() {
31744
+ this.notifications$ = new BehaviorSubject([]);
31745
+ }
31746
+ showNotification(content, timeoutMs, error) {
31747
+ error && console.error(error);
31748
+ const id = Math.floor(Math.random() * 1000000);
31749
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31750
+ if (typeof timeoutMs !== 'undefined') {
31751
+ setTimeout(() => {
31752
+ this.removeNotificationById(id);
31753
+ }, timeoutMs);
31754
+ }
31755
+ return id;
31756
+ }
31757
+ removeNotificationById(id) {
31758
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31759
+ }
31760
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31761
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31762
+ }
31763
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31764
+ type: Injectable,
31765
+ args: [{
31766
+ providedIn: 'root',
31767
+ }]
31768
+ }] });
31769
+
31770
+ class NotificationsContainerComponent {
31771
+ constructor() {
31772
+ this.notificationsService = inject(NotificationsService);
31773
+ }
31774
+ trackById(index, notification) {
31775
+ return notification.id;
31776
+ }
31777
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31778
+ 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: [
31779
+ trigger('enterExit', [
31780
+ transition(':enter', [
31781
+ animate('150ms', keyframes([
31782
+ style({ transform: 'scale(1)', opacity: 0 }),
31783
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31784
+ style({ transform: 'scale(1)', opacity: 1 }),
31785
+ ])),
31786
+ ]),
31787
+ transition(':leave', [
31788
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31789
+ ]),
31790
+ ]),
31791
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31792
+ }
31793
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31794
+ type: Component,
31795
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31796
+ trigger('enterExit', [
31797
+ transition(':enter', [
31798
+ animate('150ms', keyframes([
31799
+ style({ transform: 'scale(1)', opacity: 0 }),
31800
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31801
+ style({ transform: 'scale(1)', opacity: 1 }),
31802
+ ])),
31803
+ ]),
31804
+ transition(':leave', [
31805
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31806
+ ]),
31807
+ ]),
31808
+ ], 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" }]
31809
+ }] });
31810
+
31305
31811
  class FilterDropdownComponent {
31306
31812
  constructor() {
31307
31813
  this.searchFacade = inject(SearchFacade);
31308
31814
  this.searchService = inject(SearchService);
31309
31815
  this.fieldsService = inject(FieldsService);
31816
+ this.notificationsService = inject(NotificationsService);
31817
+ this.translateService = inject(TranslateService);
31818
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31310
31819
  this.selected$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$1((fieldValues) => fieldValues[this.fieldName]), filter$1((selected) => !!selected), startWith$1([]), catchError(() => of([])));
31311
- this.selectedDateRange$ = this.selected$.pipe(map$1((selectedDateRange) => selectedDateRange));
31820
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31821
+ this.selectedBoundingBox$ = this.selected$.pipe(map$1((selected) => Array.isArray(selected) && selected.length > 0
31822
+ ? selected
31823
+ : null));
31824
+ this.spatialExtentErrorNotificationId = null;
31312
31825
  }
31313
31826
  onSelectedValues(values) {
31314
31827
  this.fieldsService
31315
31828
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31316
31829
  .subscribe((filters) => this.searchService.updateFilters(filters));
31317
31830
  }
31831
+ onBboxChange(bbox) {
31832
+ this.fieldsService
31833
+ .buildFiltersFromFieldValues({
31834
+ [this.fieldName]: bbox,
31835
+ })
31836
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31837
+ this.clearSpatialExtentErrorNotification();
31838
+ }
31839
+ onSpatialExtentError(error) {
31840
+ this.clearSpatialExtentErrorNotification();
31841
+ this.spatialExtentErrorNotificationId =
31842
+ this.notificationsService.showNotification({
31843
+ type: 'error',
31844
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31845
+ text: this.translateService.instant(error.key, error.params),
31846
+ });
31847
+ }
31848
+ clearSpatialExtentErrorNotification() {
31849
+ if (this.spatialExtentErrorNotificationId === null)
31850
+ return;
31851
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31852
+ this.spatialExtentErrorNotificationId = null;
31853
+ }
31318
31854
  ngOnInit() {
31319
31855
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31320
31856
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31322,27 +31858,15 @@ class FilterDropdownComponent {
31322
31858
  value: v.value.toString(), // converting to string for the dropdown
31323
31859
  }))), catchError(() => of([])));
31324
31860
  }
31325
- onStartDateChange(start) {
31326
- if (!start)
31327
- return;
31328
- this.dateRange = { ...this.dateRange, start };
31329
- }
31330
- onEndDateChange(end) {
31331
- if (!end)
31332
- return;
31333
- this.dateRange = { ...this.dateRange, end };
31334
- if (this.dateRange.start && this.dateRange.end) {
31335
- this.fieldsService
31336
- .buildFiltersFromFieldValues({
31337
- [this.fieldName]: this.dateRange,
31338
- })
31339
- .subscribe((filters) => {
31340
- return this.searchService.updateFilters(filters);
31341
- });
31342
- }
31861
+ onDateRangeChange(dateRange) {
31862
+ this.fieldsService
31863
+ .buildFiltersFromFieldValues({
31864
+ [this.fieldName]: dateRange,
31865
+ })
31866
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31343
31867
  }
31344
31868
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31345
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "startDate", "endDate"], outputs: ["startDateChange", "endDateChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31869
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb", "initialBbox"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31346
31870
  }
31347
31871
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31348
31872
  type: Component,
@@ -31350,7 +31874,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31350
31874
  CommonModule,
31351
31875
  DateRangeDropdownComponent,
31352
31876
  DropdownMultiselectComponent,
31353
- ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [startDate]=\"(selectedDateRange$ | async)?.start\"\n [endDate]=\"(selectedDateRange$ | async)?.end\"\n (startDateChange)=\"onStartDateChange($event)\"\n (endDateChange)=\"onEndDateChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31877
+ SpatialExtentDropdownComponent,
31878
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [initialBbox]=\"(selectedBoundingBox$ | async) ?? null\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n [attr.data-cy-field]=\"fieldName\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31354
31879
  }], propDecorators: { fieldName: [{
31355
31880
  type: Input
31356
31881
  }], title: [{
@@ -31886,74 +32411,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31886
32411
  args: ['gnUiSearchStateContainer']
31887
32412
  }] } });
31888
32413
 
31889
- class NotificationsService {
31890
- constructor() {
31891
- this.notifications$ = new BehaviorSubject([]);
31892
- }
31893
- showNotification(content, timeoutMs, error) {
31894
- error && console.error(error);
31895
- const id = Math.floor(Math.random() * 1000000);
31896
- this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31897
- if (typeof timeoutMs === 'undefined')
31898
- return;
31899
- setTimeout(() => {
31900
- this.removeNotificationById(id);
31901
- }, timeoutMs);
31902
- }
31903
- removeNotificationById(id) {
31904
- this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31905
- }
31906
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31907
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31908
- }
31909
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31910
- type: Injectable,
31911
- args: [{
31912
- providedIn: 'root',
31913
- }]
31914
- }] });
31915
-
31916
- class NotificationsContainerComponent {
31917
- constructor() {
31918
- this.notificationsService = inject(NotificationsService);
31919
- }
31920
- trackById(index, notification) {
31921
- return notification.id;
31922
- }
31923
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31924
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NotificationsContainerComponent, isStandalone: true, selector: "gn-ui-notifications-container", ngImport: i0, template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: NotificationComponent, selector: "gn-ui-notification", inputs: ["type", "title", "text", "closeMessage"], outputs: ["notificationClose"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
31925
- trigger('enterExit', [
31926
- transition(':enter', [
31927
- animate('150ms', keyframes([
31928
- style({ transform: 'scale(1)', opacity: 0 }),
31929
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31930
- style({ transform: 'scale(1)', opacity: 1 }),
31931
- ])),
31932
- ]),
31933
- transition(':leave', [
31934
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31935
- ]),
31936
- ]),
31937
- ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31938
- }
31939
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31940
- type: Component,
31941
- args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31942
- trigger('enterExit', [
31943
- transition(':enter', [
31944
- animate('150ms', keyframes([
31945
- style({ transform: 'scale(1)', opacity: 0 }),
31946
- style({ transform: 'scale(1.03)', opacity: 0.5 }),
31947
- style({ transform: 'scale(1)', opacity: 1 }),
31948
- ])),
31949
- ]),
31950
- transition(':leave', [
31951
- animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31952
- ]),
31953
- ]),
31954
- ], template: "<div class=\"flex flex-col gap-6 p-6 items-start pointer-events-none\">\n @for (\n notification of notificationsService.notifications$ | async;\n track trackById($index, notification)\n ) {\n <gn-ui-notification\n class=\"max-w-full pointer-events-auto\"\n [text]=\"notification.text\"\n [type]=\"notification.type\"\n [title]=\"notification.title\"\n [closeMessage]=\"notification.closeMessage\"\n (notificationClose)=\"\n notificationsService.removeNotificationById(notification.id)\n \"\n [@enterExit]\n ></gn-ui-notification>\n }\n</div>\n" }]
31955
- }] });
31956
-
31957
32414
  class ResultsTableContainerComponent {
31958
32415
  constructor() {
31959
32416
  this.searchFacade = inject(SearchFacade);
@@ -32038,12 +32495,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32038
32495
 
32039
32496
  marker('search.filters.summaryLabel.user');
32040
32497
  marker('search.filters.summaryLabel.changeDate');
32498
+ const OPEN_BOUND = '…';
32041
32499
  class SearchFiltersSummaryItemComponent {
32042
32500
  constructor() {
32043
32501
  this.searchFacade = inject(SearchFacade);
32044
32502
  this.searchService = inject(SearchService);
32045
32503
  this.fieldsService = inject(FieldsService);
32046
- this.datePipe = inject(DatePipe);
32504
+ this.dateService = inject(DateService);
32047
32505
  this.translate = inject(TranslateService);
32048
32506
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32049
32507
  ? fieldValues[this.fieldName]
@@ -32070,9 +32528,10 @@ class SearchFiltersSummaryItemComponent {
32070
32528
  getReadableValues(fieldValues) {
32071
32529
  return fieldValues.map((value) => {
32072
32530
  if (this.fieldType === 'dateRange') {
32531
+ const { start, end } = value;
32073
32532
  return {
32074
32533
  value,
32075
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32534
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32076
32535
  };
32077
32536
  }
32078
32537
  else if (this.fieldName === 'user') {
@@ -32083,6 +32542,15 @@ class SearchFiltersSummaryItemComponent {
32083
32542
  }
32084
32543
  });
32085
32544
  }
32545
+ formatBound(date) {
32546
+ return date
32547
+ ? this.dateService.formatDate(date, {
32548
+ day: '2-digit',
32549
+ month: '2-digit',
32550
+ year: 'numeric',
32551
+ })
32552
+ : OPEN_BOUND;
32553
+ }
32086
32554
  async removeFilterValue(fieldValue) {
32087
32555
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32088
32556
  const updatedFieldValues = currentFieldValues
@@ -32095,11 +32563,11 @@ class SearchFiltersSummaryItemComponent {
32095
32563
  .subscribe((filters) => this.searchService.updateFilters(filters));
32096
32564
  }
32097
32565
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32098
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, providers: [DatePipe], ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32566
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SearchFiltersSummaryItemComponent, isStandalone: true, selector: "gn-ui-search-filters-summary-item", inputs: { fieldName: "fieldName" }, ngImport: i0, template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BadgeComponent, selector: "gn-ui-badge", inputs: ["clickable", "removable"], outputs: ["badgeRemoveClicked", "badgeClicked"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
32099
32567
  }
32100
32568
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32101
32569
  type: Component,
32102
- args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], providers: [DatePipe], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32570
+ args: [{ selector: 'gn-ui-search-filters-summary-item', standalone: true, imports: [CommonModule, BadgeComponent], template: "@if ((fieldValues$ | async)?.length > 0) {\n <div class=\"flex flex-row items-center gap-2\">\n <span class=\"text-gray-800\" data-cy=\"filterSummaryLabel\">{{\n translatedLabel\n }}</span>\n @for (fieldValue of fieldValues$ | async; track fieldValue) {\n <gn-ui-badge\n class=\"gn-ui-badge\"\n [style.--gn-ui-badge-rounded]=\"'8px'\"\n [style.--gn-ui-badge-padding]=\"'3px 4px'\"\n [style.--gn-ui-badge-text-color]=\"'black'\"\n [style.--gn-ui-badge-background-color]=\"'#F2F2F2'\"\n [style.--gn-ui-badge-font-weight]=\"'700'\"\n [removable]=\"true\"\n (badgeRemoveClicked)=\"removeFilterValue(fieldValue.value)\"\n >{{ fieldValue.label }}</gn-ui-badge\n >\n }\n </div>\n}\n" }]
32103
32571
  }], propDecorators: { fieldName: [{
32104
32572
  type: Input
32105
32573
  }] } });
@@ -32343,11 +32811,11 @@ class AddLayerFromFileComponent {
32343
32811
  }, 5000);
32344
32812
  }
32345
32813
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32346
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept"], outputs: ["fileChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32814
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AddLayerFromFileComponent, isStandalone: true, selector: "gn-ui-add-layer-from-file", ngImport: i0, template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n", styles: [""], dependencies: [{ kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
32347
32815
  }
32348
32816
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32349
32817
  type: Component,
32350
- args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32818
+ args: [{ selector: 'gn-ui-add-layer-from-file', standalone: true, imports: [TranslateDirective, TranslatePipe, DragAndDropFileInputComponent], template: "<div class=\"flex flex-col gap-2 my-2\">\n <div class=\"flex items-center gap-4\">\n <div class=\"grow rounded-md border-2 border-gray-200\">\n <gn-ui-drag-and-drop-file-input\n (fileChange)=\"handleFileChange($event)\"\n [accept]=\"acceptedMimeType.join(',')\"\n [placeholder]=\"'map.addFromFile.placeholder' | translate\"\n textClass=\"text-gray-900 pl-2 py-2\"\n class=\"placeholder-grey\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n </div>\n <p class=\"text-sm text-gray-600\" translate>map.help.addFromFile</p>\n</div>\n\n@if (errorMessage) {\n <div class=\"text-red-500 mt-2\">\n {{ errorMessage }}\n </div>\n}\n\n@if (successMessage) {\n <div class=\"text-green-500 mt-2\">\n {{ successMessage }}\n </div>\n}\n" }]
32351
32819
  }] });
32352
32820
 
32353
32821
  class LayersPanelComponent {
@@ -32512,8 +32980,7 @@ const closeMetadata = createAction('[Metadata view] close');
32512
32980
  Related actions
32513
32981
  */
32514
32982
  const setRelated = createAction('[Metadata view] Set related records', props());
32515
- const setSources = createAction('[Metadata view] Set sources', props());
32516
- const setSourceOf = createAction('[Metadata view] Set has sources', props());
32983
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32517
32984
  /*
32518
32985
  ChartConfig actions
32519
32986
  */
@@ -32545,9 +33012,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32545
33012
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32546
33013
  setChartConfig: setChartConfig,
32547
33014
  setIncompleteMetadata: setIncompleteMetadata,
32548
- setRelated: setRelated,
32549
- setSourceOf: setSourceOf,
32550
- setSources: setSources
33015
+ setLinkedRecords: setLinkedRecords,
33016
+ setRelated: setRelated
32551
33017
  });
32552
33018
 
32553
33019
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32590,12 +33056,9 @@ on(loadFullMetadata, (state) => ({
32590
33056
  on(setRelated, (state, { related }) => ({
32591
33057
  ...state,
32592
33058
  related,
32593
- })), on(setSources, (state, { sources }) => ({
32594
- ...state,
32595
- sources,
32596
- })), on(setSourceOf, (state, { sourceOf }) => ({
33059
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32597
33060
  ...state,
32598
- sourceOf,
33061
+ linkedRecords,
32599
33062
  })),
32600
33063
  /*
32601
33064
  ChartConfig reducers
@@ -32657,8 +33120,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32657
33120
  Related selectors
32658
33121
  */
32659
33122
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32660
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32661
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33123
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32662
33124
  /*
32663
33125
  Metadata selectors
32664
33126
  */
@@ -32759,212 +33221,10 @@ function parseHeaders(httpHeaders) {
32759
33221
  return result;
32760
33222
  }
32761
33223
 
32762
- async function inferDatasetType(url, typeHint) {
32763
- const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32764
- const fileExtension = fileExtensionMatches && fileExtensionMatches.length
32765
- ? fileExtensionMatches[1].toLowerCase()
32766
- : null;
32767
- // 1. type hint
32768
- if (typeHint)
32769
- return Promise.resolve(typeHint);
32770
- // 2. content-type header
32771
- const headers = await fetchHeaders(url);
32772
- if ('supportedType' in headers)
32773
- return headers.supportedType;
32774
- // 3. file extension from url
32775
- else if (SupportedTypes.indexOf(fileExtension) > -1)
32776
- return fileExtension;
32777
- // no type inferred or hinted
32778
- if ('mimeType' in headers)
32779
- throw FetchError.unsupportedType(headers.mimeType);
32780
- else
32781
- throw FetchError.unknownType();
32782
- }
32783
- function fetchHeaders(url) {
32784
- return sharedFetch(url, 'HEAD')
32785
- .catch((error) => {
32786
- throw FetchError.corsOrNetwork(error.message);
32787
- })
32788
- .then((response) => {
32789
- if (!response.ok) {
32790
- throw FetchError.http(response.status);
32791
- }
32792
- return parseHeaders(response.headers);
32793
- });
32794
- }
32795
- function fetchDataAsText(url, cacheActive) {
32796
- const fetchFactory = () => sharedFetch(url)
32797
- .catch((error) => {
32798
- throw FetchError.corsOrNetwork(error.message);
32799
- })
32800
- .then(async (response) => {
32801
- if (!response.ok) {
32802
- const clonedResponse = response.clone();
32803
- throw FetchError.http(response.status, await clonedResponse.text());
32804
- }
32805
- const clonedResponse = response.clone();
32806
- return clonedResponse.text();
32807
- });
32808
- return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
32809
- }
32810
- function fetchDataAsArrayBuffer(url, cacheActive) {
32811
- const fetchFactory = () => sharedFetch(url)
32812
- .catch((error) => {
32813
- throw FetchError.corsOrNetwork(error.message);
32814
- })
32815
- .then(async (response) => {
32816
- if (!response.ok) {
32817
- throw FetchError.http(response.status, await response.text());
32818
- }
32819
- // convert to a numeric array so that we can store the response in cache
32820
- return Array.from(new Uint8Array(await response.arrayBuffer()));
32821
- });
32822
- return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
32823
- return new Uint8Array(array).buffer;
32824
- });
33224
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33225
+ function fieldToSql(name) {
33226
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32825
33227
  }
32826
- function tryParseDate(input) {
32827
- if (typeof input !== 'string')
32828
- return null;
32829
- function tryIso(value) {
32830
- const parsed = parseISO(value);
32831
- return isNaN(parsed.getDate()) ? null : parsed;
32832
- }
32833
- function tryFormat(value, format) {
32834
- const parsed = parse$4(value, format, new Date());
32835
- return isNaN(parsed.getDate()) ? null : parsed;
32836
- }
32837
- return (tryIso(input) ||
32838
- tryFormat(input, 'dd/MM/yyyy') ||
32839
- tryFormat(input, 'dd.MM.yyyy') ||
32840
- tryFormat(input, 'MM/dd/yyyy') ||
32841
- null);
32842
- }
32843
- function tryParseNumber(input) {
32844
- if (isNaN(input))
32845
- return null;
32846
- const parsed = parseFloat(input);
32847
- return isNaN(parsed) ? null : parsed;
32848
- }
32849
- function jsonToGeojsonFeature(object) {
32850
- const { id, properties } = Object.keys(object)
32851
- .map((property) => (property ? property : 'unknown')) //prevent empty strings
32852
- .reduce((prev, curr) => curr.toLowerCase().endsWith('id')
32853
- ? {
32854
- ...prev,
32855
- id: object[curr],
32856
- }
32857
- : {
32858
- ...prev,
32859
- properties: { ...prev.properties, [curr]: object[curr] },
32860
- }, { id: undefined, properties: {} });
32861
- return {
32862
- type: 'Feature',
32863
- geometry: null,
32864
- properties,
32865
- ...(id !== undefined && { id }),
32866
- };
32867
- }
32868
- function mutateProperties(items, mutators) {
32869
- const mutatorKeys = Object.keys(mutators);
32870
- for (let i = 0, ii = items.length; i < ii; i++) {
32871
- const item = items[i];
32872
- for (const mutatorField of mutatorKeys) {
32873
- if (!(mutatorField in item.properties))
32874
- continue;
32875
- item.properties[mutatorField] = mutators[mutatorField](item.properties[mutatorField]);
32876
- }
32877
- }
32878
- return items;
32879
- }
32880
- const SAMPLE_SIZE = 20;
32881
- /**
32882
- * This will infer field types from a list of data items and cast the values accordingly
32883
- * @param items
32884
- * @param inferTypes
32885
- */
32886
- function processItemProperties(items, inferTypes = false) {
32887
- const foundFields = {};
32888
- for (let i = 0, ii = Math.min(SAMPLE_SIZE, items.length); i < ii; i++) {
32889
- const item = items[i];
32890
- const fields = Object.keys(item.properties);
32891
- for (const field of fields) {
32892
- if (!(field in foundFields)) {
32893
- foundFields[field] = {
32894
- label: field,
32895
- name: field,
32896
- type: null,
32897
- };
32898
- }
32899
- const value = item.properties[field];
32900
- const info = foundFields[field];
32901
- if (value === undefined || value === '' || value === null)
32902
- continue;
32903
- if (!inferTypes) {
32904
- if (info.type === null && typeof value === 'number') {
32905
- info.type = 'number';
32906
- }
32907
- else if (info.type === 'number' && typeof value !== 'number') {
32908
- info.type = 'string';
32909
- }
32910
- continue;
32911
- }
32912
- const parsedNumber = tryParseNumber(value);
32913
- if (info.type === null && parsedNumber !== null) {
32914
- info.type = 'number';
32915
- continue;
32916
- }
32917
- else if (info.type === 'number' && parsedNumber === null) {
32918
- info.type = 'string';
32919
- continue;
32920
- }
32921
- const parsedDate = tryParseDate(value);
32922
- if (info.type === null && parsedDate !== null) {
32923
- info.type = 'date';
32924
- }
32925
- else if (info.type === 'date' && parsedDate === null) {
32926
- info.type = 'string';
32927
- }
32928
- }
32929
- }
32930
- const properties = [];
32931
- const mutators = {};
32932
- for (const field in foundFields) {
32933
- const info = foundFields[field];
32934
- if (info.type === 'number') {
32935
- mutators[field] = tryParseNumber;
32936
- }
32937
- else if (info.type === 'date') {
32938
- mutators[field] = tryParseDate;
32939
- }
32940
- properties.push({ ...info, type: info.type || 'string' });
32941
- }
32942
- if (inferTypes) {
32943
- mutateProperties(items, mutators);
32944
- }
32945
- return { items, properties };
32946
- }
32947
- /**
32948
- * This creates a Proxy that allows reading and writing to the data item properties
32949
- * as if it was a simple array of JSON objects
32950
- * @param items
32951
- */
32952
- function getJsonDataItemsProxy(items) {
32953
- return new Proxy(items, {
32954
- get(target, p) {
32955
- if (typeof p === 'string' &&
32956
- !Number.isNaN(parseInt(p)) &&
32957
- target[p]?.properties) {
32958
- return target[p].properties;
32959
- }
32960
- return target[p];
32961
- },
32962
- set() {
32963
- throw new Error('This object is read-only');
32964
- },
32965
- });
32966
- }
32967
-
32968
33228
  function filterToSql(filter) {
32969
33229
  const operator = filter[0];
32970
33230
  const args = filter.slice(1);
@@ -32979,10 +33239,10 @@ function filterToSql(filter) {
32979
33239
  case '=':
32980
33240
  case '!=':
32981
33241
  case 'like':
32982
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33242
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32983
33243
  case 'in': {
32984
33244
  const values = args.slice(1);
32985
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33245
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32986
33246
  }
32987
33247
  case 'and':
32988
33248
  case 'or': {
@@ -33001,17 +33261,18 @@ function aggregationToSql(aggregation) {
33001
33261
  const field = aggregation[1];
33002
33262
  switch (operation) {
33003
33263
  case 'average':
33004
- return `AVG([${field}]) as [average(${field})]`;
33264
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
33005
33265
  case 'sum':
33006
33266
  case 'max':
33007
33267
  case 'min':
33008
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33268
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
33009
33269
  case 'count':
33010
- return 'COUNT(*) as [count()]';
33270
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
33011
33271
  }
33012
33272
  }
33013
33273
  /**
33014
33274
  * Leave arguments at null if not used
33275
+ * @param tableName
33015
33276
  * @param selected
33016
33277
  * @param filter
33017
33278
  * @param sort
@@ -33020,22 +33281,25 @@ function aggregationToSql(aggregation) {
33020
33281
  * @param groupBy
33021
33282
  * @param aggregations
33022
33283
  */
33023
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33284
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33024
33285
  let sqlSelect = 'SELECT *';
33025
- const sqlFrom = ' FROM ?';
33286
+ const sqlFrom = ` FROM ${tableName}`;
33026
33287
  let sqlOrderBy = '';
33027
33288
  let sqlWhere = '';
33028
33289
  let sqlLimit = '';
33029
33290
  let sqlGroupBy = '';
33030
33291
  if (selected !== null) {
33031
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33292
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33293
+ }
33294
+ if (geometryColumn !== null) {
33295
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33032
33296
  }
33033
33297
  if (filter !== null) {
33034
33298
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33035
33299
  }
33036
33300
  if (sort?.length) {
33037
33301
  sqlOrderBy = ` ORDER BY ${sort
33038
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33302
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33039
33303
  .join(', ')}`;
33040
33304
  }
33041
33305
  if (startIndex !== null && count !== null) {
@@ -33045,17 +33309,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33045
33309
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33046
33310
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33047
33311
  const sqlGroupByFields = groupedByDistinct
33048
- .map((group) => `[${group[1]}]`)
33312
+ .map((group) => fieldToSql(group[1]))
33049
33313
  .join(', ');
33050
33314
  const sqlGroupBySelect = groupedByDistinct
33051
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33315
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33052
33316
  .join(', ');
33053
33317
  if (sqlGroupByFields && sqlGroupBySelect) {
33054
33318
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33055
33319
  sqlSelect += `, ${sqlGroupBySelect}`;
33056
33320
  }
33057
33321
  }
33058
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33322
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33323
+ }
33324
+
33325
+ function arrowTableToDataItems(table) {
33326
+ const fields = table.schema.fields;
33327
+ return table.toArray().map((row) => {
33328
+ const rowJson = row.toJSON();
33329
+ const feature = {
33330
+ type: 'Feature',
33331
+ geometry: null,
33332
+ properties: {},
33333
+ };
33334
+ const keys = Object.keys(rowJson);
33335
+ for (let i = 0; i < keys.length; i++) {
33336
+ const key = keys[i];
33337
+ const dataType = fields[i].type;
33338
+ let value = rowJson[key];
33339
+ // this might happen if we get an array inside a field
33340
+ if (value instanceof Vector) {
33341
+ value = Array.from(value);
33342
+ }
33343
+ // cast bigints to ints
33344
+ if (typeof value === 'bigint') {
33345
+ value = Number(value);
33346
+ }
33347
+ // rename columns with empty name
33348
+ if (!key) {
33349
+ feature.properties['unknown'] = value;
33350
+ continue;
33351
+ }
33352
+ // assign properties that look like an id to the geojson `id` field
33353
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33354
+ (typeof value == 'string' || typeof value === 'number')) {
33355
+ feature.id = value;
33356
+ }
33357
+ // if a date is in timestamp (number) format, convert it to native
33358
+ if (typeof value === 'number' &&
33359
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33360
+ value = new Date(value);
33361
+ }
33362
+ // if a binary field (most likely a geometry column): skip
33363
+ if (DataType.isBinary(dataType)) {
33364
+ continue;
33365
+ }
33366
+ // geometry column
33367
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33368
+ feature.geometry = JSON.parse(value);
33369
+ continue;
33370
+ }
33371
+ feature.properties[key] = value;
33372
+ }
33373
+ return feature;
33374
+ });
33375
+ }
33376
+
33377
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33378
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33379
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33380
+ const typesMapping = {
33381
+ INTEGER: 'number',
33382
+ SMALLINT: 'number',
33383
+ TINYINT: 'number',
33384
+ BIGINT: 'number',
33385
+ HUGEINT: 'number',
33386
+ UINTEGER: 'number',
33387
+ USMALLINT: 'number',
33388
+ UTINYINT: 'number',
33389
+ UBIGINT: 'number',
33390
+ UHUGEINT: 'number',
33391
+ DOUBLE: 'number',
33392
+ BIGNUM: 'number',
33393
+ DECIMAL: 'number',
33394
+ NUMERIC: 'number',
33395
+ FLOAT: 'number',
33396
+ REAL: 'number',
33397
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33398
+ TIMESTAMP: 'date',
33399
+ DATE: 'date',
33400
+ CHAR: 'string',
33401
+ VARCHAR: 'string',
33402
+ TEXT: 'string',
33403
+ UUID: 'string',
33404
+ BLOB: 'string',
33405
+ BIT: 'string',
33406
+ INTERVAL: 'string',
33407
+ LIST: 'string',
33408
+ BOOLEAN: 'boolean',
33409
+ };
33410
+ class Engine {
33411
+ async makeInit() {
33412
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33413
+ let worker_url = bundle.mainWorker;
33414
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33415
+ if (worker_url.startsWith('https://')) {
33416
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33417
+ type: 'text/javascript',
33418
+ }));
33419
+ }
33420
+ const worker = new Worker(worker_url);
33421
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33422
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33423
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33424
+ // setup duckdb options
33425
+ const conn = await this.db.connect();
33426
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33427
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33428
+ await conn.query(`SET TimeZone = '${timezone}';`);
33429
+ await conn.query(`SET threads = 1;`);
33430
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33431
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33432
+ conn.close();
33433
+ return this;
33434
+ }
33435
+ isReady() {
33436
+ if (!this.init_) {
33437
+ this.init_ = this.makeInit();
33438
+ }
33439
+ return this.init_;
33440
+ }
33441
+ /**
33442
+ * Returns information about a dataset once it's loaded:
33443
+ * - a list of properties description
33444
+ * - the name of the dataset geometry column (null if no geometry present)
33445
+ * @param datasetId name of the table under which the dataset will be stored
33446
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33447
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33448
+ */
33449
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33450
+ const conn = await this.db.connect();
33451
+ let results;
33452
+ // either we want to recreate the table, or we keep it if it already exists
33453
+ const safeLoadQuery = forceReload
33454
+ ? `DROP TABLE IF EXISTS ${datasetId};
33455
+ ${loadQuery}`
33456
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33457
+ // create the table
33458
+ try {
33459
+ results = await conn.query(safeLoadQuery);
33460
+ }
33461
+ catch (e) {
33462
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33463
+ }
33464
+ // read rows count
33465
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33466
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33467
+ // read columns
33468
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33469
+ let geometryColumn = null;
33470
+ const properties = results
33471
+ .toArray()
33472
+ .map((row) => {
33473
+ const rowObj = row.toJSON();
33474
+ if (rowObj['data_type'] === 'GEOMETRY') {
33475
+ // the geometry is not part of the properties
33476
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33477
+ // all get discarded
33478
+ geometryColumn = rowObj['column_name'];
33479
+ return null;
33480
+ }
33481
+ return {
33482
+ name: rowObj['column_name'],
33483
+ label: rowObj['column_name'],
33484
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33485
+ };
33486
+ })
33487
+ .filter((prop) => prop !== null);
33488
+ conn.close();
33489
+ return {
33490
+ properties,
33491
+ geometryColumn,
33492
+ rowsCount: Number(recordsCount),
33493
+ };
33494
+ }
33495
+ // register a Uint8 buffer using a handle in the duckdb instance
33496
+ async registerData(name, buffer) {
33497
+ return this.db.registerFileBuffer(name, buffer);
33498
+ }
33499
+ /**
33500
+ * @param query duckdb-specific query for fetching items
33501
+ */
33502
+ async queryItems(query) {
33503
+ const conn = await this.db.connect();
33504
+ const results = await conn.query(query);
33505
+ conn.close();
33506
+ return arrowTableToDataItems(results);
33507
+ }
33508
+ close() {
33509
+ this.db?.terminate();
33510
+ }
33511
+ }
33512
+ let engine = null;
33513
+ async function getEngine() {
33514
+ if (!engine) {
33515
+ engine = new Engine();
33516
+ }
33517
+ return engine.isReady();
33059
33518
  }
33060
33519
 
33061
33520
  class BaseReader {
@@ -33068,10 +33527,18 @@ class BaseReader {
33068
33527
  this.sort = null;
33069
33528
  this.startIndex = null;
33070
33529
  this.count = null;
33530
+ this.loadPromise_ = Promise.resolve();
33531
+ this.cacheEnabled = false;
33532
+ }
33533
+ enableCache(enabled) {
33534
+ this.cacheEnabled = enabled;
33071
33535
  }
33072
33536
  load() {
33073
33537
  throw new Error('not implemented');
33074
33538
  }
33539
+ get isLoaded() {
33540
+ return this.loadPromise_;
33541
+ }
33075
33542
  get properties() {
33076
33543
  throw new Error('not implemented');
33077
33544
  }
@@ -33120,178 +33587,237 @@ class BaseReader {
33120
33587
  }
33121
33588
  }
33122
33589
 
33123
- class BaseCacheReader extends BaseReader {
33124
- constructor(url, cacheActive = true) {
33125
- super(url);
33126
- this.url = url;
33127
- this.cacheActive = cacheActive;
33128
- }
33129
- setCacheActive(value) {
33130
- this.cacheActive = value;
33131
- }
33132
- }
33133
-
33134
- class BaseFileReader extends BaseCacheReader {
33135
- getData() {
33590
+ /**
33591
+ * This reader handles file formats supported natively by DuckDB
33592
+ */
33593
+ class BaseFileReader extends BaseReader {
33594
+ // a table id should not exceed 63 chars
33595
+ generateDatasetId() {
33596
+ // generate a hash out of the url
33597
+ let hash = 0;
33598
+ for (const char of this.url) {
33599
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33600
+ hash = hash >>> 0; // make it unsigned
33601
+ }
33602
+ return `datafetcher_${hash.toString(16)}`;
33603
+ }
33604
+ async getLoadQuery() {
33136
33605
  throw new Error('not implemented');
33137
33606
  }
33138
- load() {
33139
- this.parseResult_ = this.getData();
33607
+ async load() {
33608
+ this.datasetId = this.generateDatasetId();
33609
+ this.loadPromise_ = getEngine()
33610
+ .then((engine) => {
33611
+ this.engine = engine;
33612
+ return this.getLoadQuery();
33613
+ })
33614
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33615
+ .then((datasetInfo) => {
33616
+ this.properties_ = datasetInfo.properties;
33617
+ this.geometryColumn = datasetInfo.geometryColumn;
33618
+ this.rowsCount = datasetInfo.rowsCount;
33619
+ // returns void for the loaded promise
33620
+ });
33140
33621
  }
33141
33622
  get properties() {
33142
- return this.parseResult_.then((result) => result.properties);
33623
+ return this.isLoaded.then(() => this.properties_);
33143
33624
  }
33144
33625
  get info() {
33145
- return this.parseResult_.then((result) => ({
33146
- itemsCount: result.items.length,
33626
+ return this.isLoaded.then(() => ({
33627
+ itemsCount: this.rowsCount,
33628
+ hasGeometry: !!this.geometryColumn,
33147
33629
  }));
33148
33630
  }
33149
33631
  async read() {
33150
- const items = (await this.parseResult_).items;
33151
- // no query defined: return the full results as is
33152
- if (this.groupedBy == null &&
33153
- this.aggregations == null &&
33154
- this.selected == null &&
33155
- this.sort == null &&
33156
- this.filter == null &&
33157
- this.startIndex == null &&
33158
- this.count == null) {
33159
- return items;
33160
- }
33161
- const jsonItems = getJsonDataItemsProxy(items);
33162
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33163
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33164
- return result.map(jsonToGeojsonFeature);
33165
- }
33166
- }
33167
-
33168
- function parseCsv(text) {
33169
- // first parse the header to guess the delimiter
33170
- // note that we do that to not rely on Papaparse logic for guessing delimiter
33171
- let delimiter;
33172
- try {
33173
- const header = text.split('\n')[0];
33174
- const result = Papa.parse(header, {
33175
- header: false,
33176
- });
33177
- delimiter = result.meta.delimiter;
33632
+ await this.isLoaded;
33633
+ // if only certain fields are selected, omit the geometry
33634
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33635
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33636
+ return this.engine.queryItems(query);
33178
33637
  }
33179
- catch (e) {
33180
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33181
- }
33182
- const parsed = Papa.parse(text, {
33183
- header: true,
33184
- skipEmptyLines: true,
33185
- delimiter,
33186
- });
33187
- if (parsed.errors.length) {
33188
- throw new Error('CSV parsing failed for the following reasons:\n' +
33189
- parsed.errors
33190
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33191
- .join('\n'));
33192
- }
33193
- const items = parsed.data.map(jsonToGeojsonFeature);
33194
- return processItemProperties(items, true);
33195
33638
  }
33639
+
33196
33640
  class CsvReader extends BaseFileReader {
33197
- getData() {
33198
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33641
+ async getLoadQuery() {
33642
+ // first we get a list of columns hich have a detected type of VARCHAR
33643
+ // 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
33644
+ return `
33645
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33646
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33647
+ force_not_null = getvariable('strColumns'),
33648
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33649
+ );`;
33199
33650
  }
33200
33651
  }
33201
33652
 
33202
- /**
33203
- * This parser only supports arrays of simple flat objects with properties
33204
- * @param text
33205
- */
33206
- function parseJson(text) {
33207
- const parsed = JSON.parse(text);
33208
- if (!Array.isArray(parsed)) {
33209
- throw new Error('Could not parse JSON, expected an array at root level');
33210
- }
33211
- return processItemProperties(parsed.map(jsonToGeojsonFeature));
33212
- }
33213
33653
  class JsonReader extends BaseFileReader {
33214
- getData() {
33215
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33654
+ async getLoadQuery() {
33655
+ return `
33656
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33657
+ maximum_object_size = 536870912 -- 500MB
33658
+ );`;
33216
33659
  }
33217
33660
  }
33218
33661
 
33219
- /**
33220
- * This parser supports both Geojson Feature collections or arrays
33221
- * of Features
33222
- * @param text
33223
- */
33224
- function parseGeojson(text) {
33225
- const parsed = JSON.parse(text);
33226
- const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33227
- if (!Array.isArray(features)) {
33228
- throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33229
- }
33230
- return processItemProperties(features);
33231
- }
33232
33662
  class GeojsonReader extends BaseFileReader {
33233
- getData() {
33234
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33663
+ async getLoadQuery() {
33664
+ return `
33665
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33666
+ allowed_drivers = ['GeoJSON']
33667
+ );`;
33235
33668
  }
33236
33669
  }
33237
33670
 
33671
+ async function inferDatasetType(url, typeHint) {
33672
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33673
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33674
+ ? fileExtensionMatches[1].toLowerCase()
33675
+ : null;
33676
+ // 1. type hint
33677
+ if (typeHint)
33678
+ return Promise.resolve(typeHint);
33679
+ // 2. content-type header
33680
+ const headers = await fetchHeaders(url);
33681
+ if ('supportedType' in headers)
33682
+ return headers.supportedType;
33683
+ // 3. file extension from url
33684
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33685
+ return fileExtension;
33686
+ // no type inferred or hinted
33687
+ if ('mimeType' in headers)
33688
+ throw FetchError.unsupportedType(headers.mimeType);
33689
+ else
33690
+ throw FetchError.unknownType();
33691
+ }
33692
+ function fetchHeaders(url) {
33693
+ return sharedFetch(url, 'HEAD')
33694
+ .catch((error) => {
33695
+ throw FetchError.corsOrNetwork(error.message);
33696
+ })
33697
+ .then((response) => {
33698
+ if (!response.ok) {
33699
+ throw FetchError.http(response.status);
33700
+ }
33701
+ return parseHeaders(response.headers);
33702
+ });
33703
+ }
33704
+ function fetchDataAsText(url, cacheActive) {
33705
+ const fetchFactory = () => sharedFetch(url)
33706
+ .catch((error) => {
33707
+ throw FetchError.corsOrNetwork(error.message);
33708
+ })
33709
+ .then(async (response) => {
33710
+ if (!response.ok) {
33711
+ const clonedResponse = response.clone();
33712
+ throw FetchError.http(response.status, await clonedResponse.text());
33713
+ }
33714
+ const clonedResponse = response.clone();
33715
+ return clonedResponse.text();
33716
+ });
33717
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33718
+ }
33719
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33720
+ const fetchFactory = () => sharedFetch(url)
33721
+ .catch((error) => {
33722
+ throw FetchError.corsOrNetwork(error.message);
33723
+ })
33724
+ .then(async (response) => {
33725
+ if (!response.ok) {
33726
+ throw FetchError.http(response.status, await response.text());
33727
+ }
33728
+ // convert to a numeric array so that we can store the response in cache
33729
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33730
+ });
33731
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33732
+ return new Uint8Array(array).buffer;
33733
+ });
33734
+ }
33238
33735
  /**
33239
- * This will read the first sheet of the excel workbook and expect the first
33240
- * line to contain the properties names
33241
- * @param buffer
33736
+ * This creates a Proxy that allows reading and writing to the data item properties
33737
+ * as if it was a simple array of JSON objects
33738
+ * @param items
33242
33739
  */
33243
- function parseExcel(buffer) {
33244
- return import('xlsx').then(({ read, utils }) => {
33245
- const workbook = read(buffer);
33246
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
33247
- let json = utils.sheet_to_json(sheet);
33248
- if (!json.length) {
33249
- json = [];
33250
- }
33251
- return processItemProperties(json.map(jsonToGeojsonFeature), true);
33740
+ function getJsonDataItemsProxy(items) {
33741
+ return new Proxy(items, {
33742
+ get(target, p) {
33743
+ if (typeof p === 'string' &&
33744
+ !Number.isNaN(parseInt(p)) &&
33745
+ target[p]?.properties) {
33746
+ return target[p].properties;
33747
+ }
33748
+ return target[p];
33749
+ },
33750
+ set() {
33751
+ throw new Error('This object is read-only');
33752
+ },
33252
33753
  });
33253
33754
  }
33755
+
33254
33756
  class ExcelReader extends BaseFileReader {
33255
- getData() {
33256
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33757
+ async getLoadQuery() {
33758
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33759
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33760
+ const bufferHandle = `B${this.datasetId}`;
33761
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33762
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33763
+ const isXls = Array.from(magicNumber)
33764
+ .map((n) => n.toString(16).toUpperCase())
33765
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33766
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33767
+ if (isXls) {
33768
+ buffer = await import('xlsx').then(({ read, utils }) => {
33769
+ const workbook = read(buffer);
33770
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33771
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33772
+ });
33773
+ }
33774
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33775
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33776
+ return `
33777
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33778
+ }
33779
+ }
33780
+
33781
+ class GmlReader extends BaseFileReader {
33782
+ async getLoadQuery() {
33783
+ return `
33784
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33785
+ allowed_drivers = ['GML']
33786
+ );`;
33257
33787
  }
33258
33788
  }
33259
33789
 
33260
- function parseGml(text, namespace, version) {
33261
- const splittedNamespace = namespace.split(':');
33262
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33790
+ const formatGeojson = new GeoJSON$1();
33791
+ function parseGeojson(text) {
33792
+ const parsed = JSON.parse(text);
33793
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33794
+ if (!Array.isArray(features)) {
33795
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33796
+ }
33797
+ return features;
33798
+ }
33799
+ function parseGml(text, featureType, version) {
33800
+ const parts = featureType.split(':');
33801
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33263
33802
  const match = regex.exec(text);
33264
33803
  if (match && match.length >= 2) {
33265
- const wf = new WFS({
33804
+ const wfs = new WFS({
33266
33805
  featureNS: match[1],
33267
- featureType: splittedNamespace[1],
33806
+ featureType: parts[1],
33268
33807
  version: version,
33269
33808
  });
33270
33809
  let features;
33271
33810
  try {
33272
- features = wf.readFeatures(text);
33811
+ features = wfs.readFeatures(text);
33273
33812
  }
33274
33813
  catch (e) {
33275
- throw Error("Couldn't parse WFS with GML features");
33814
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33276
33815
  }
33277
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33278
- return processItemProperties(geojsonItem.features, true);
33816
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33817
+ return geojsonItem.features;
33279
33818
  }
33280
33819
  throw Error("Couldn't retrieve namespace url");
33281
33820
  }
33282
- class GmlReader extends BaseFileReader {
33283
- constructor(url, namespace, version, cacheActive = true) {
33284
- super(url);
33285
- this.url = url;
33286
- this.namespace = namespace;
33287
- this.version = version;
33288
- this.cacheActive = cacheActive;
33289
- }
33290
- getData() {
33291
- return fetchDataAsText(this.url, this.cacheActive).then((text) => parseGml(text, this.namespace, this.version));
33292
- }
33293
- }
33294
-
33295
33821
  async function getWfsEndpoint(wfsUrl) {
33296
33822
  try {
33297
33823
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33319,21 +33845,65 @@ async function getWfsEndpoint(wfsUrl) {
33319
33845
  }
33320
33846
  }
33321
33847
  }
33322
- class WfsReader extends BaseCacheReader {
33323
- constructor(url, wfsEndpoint, featureTypeName, cacheActive) {
33324
- super(url, cacheActive);
33325
- this.endpoint = wfsEndpoint;
33326
- this.featureTypeName = featureTypeName;
33327
- this.version = this.endpoint.getVersion();
33848
+ class WfsReader extends BaseReader {
33849
+ constructor(url, featureTypeName) {
33850
+ super(url);
33851
+ this.endpoint = getWfsEndpoint(url);
33852
+ this.featureType = this.endpoint
33853
+ .then((endpoint) => {
33854
+ const featureTypes = endpoint.getFeatureTypes();
33855
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33856
+ ? featureTypes[0].name
33857
+ : featureTypeName);
33858
+ })
33859
+ .then((featureType) => {
33860
+ if (!featureType) {
33861
+ throw new Error('wfs.featuretype.notfound');
33862
+ }
33863
+ return featureType;
33864
+ });
33865
+ }
33866
+ get backupReader() {
33867
+ if (this.backupReader_) {
33868
+ return this.backupReader_;
33869
+ }
33870
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33871
+ let reader;
33872
+ if (endpoint.supportsJson(featureType.name)) {
33873
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33874
+ asJson: true,
33875
+ outputCrs: 'EPSG:4326',
33876
+ }));
33877
+ }
33878
+ else {
33879
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33880
+ (featureType.defaultCrs === 'EPSG:4326' ||
33881
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33882
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33883
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33884
+ outputCrs: 'EPSG:4326',
33885
+ }));
33886
+ }
33887
+ }
33888
+ reader.enableCache(this.cacheEnabled);
33889
+ reader.load();
33890
+ return reader;
33891
+ });
33892
+ return this.backupReader_;
33328
33893
  }
33329
33894
  get properties() {
33330
- return this.endpoint
33331
- .getFeatureTypeFull(this.featureTypeName)
33332
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33895
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33333
33896
  const originalType = featureType.properties[prop];
33334
- const type = originalType === 'float' || originalType === 'integer'
33335
- ? 'number'
33336
- : originalType; // FIXME: ogc-client typing is incorrect, should be a string union
33897
+ let type;
33898
+ if (originalType === 'float' || originalType === 'integer') {
33899
+ type = 'number';
33900
+ }
33901
+ else if (originalType === 'boolean') {
33902
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33903
+ }
33904
+ else {
33905
+ type = originalType;
33906
+ }
33337
33907
  return {
33338
33908
  name: prop,
33339
33909
  label: prop,
@@ -33342,83 +33912,63 @@ class WfsReader extends BaseCacheReader {
33342
33912
  }));
33343
33913
  }
33344
33914
  get info() {
33345
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33915
+ return this.featureType.then((result) => ({
33346
33916
  itemsCount: result.objectCount,
33917
+ hasGeometry: !!result.geometryName,
33347
33918
  }));
33348
33919
  }
33349
- static async createReader(wfsUrlEndpoint, featureTypeName) {
33350
- const wfsEndpoint = await getWfsEndpoint(wfsUrlEndpoint);
33351
- const featureTypes = wfsEndpoint.getFeatureTypes();
33352
- const featureType = wfsEndpoint.getFeatureTypeSummary(featureTypes.length === 1 && !featureTypeName
33353
- ? featureTypes[0].name
33354
- : featureTypeName);
33355
- if (!featureType) {
33356
- throw new Error('wfs.featuretype.notfound');
33357
- }
33358
- if (wfsEndpoint.supportsStartIndex()) {
33359
- return new WfsReader(wfsUrlEndpoint, wfsEndpoint, featureType.name);
33360
- }
33361
- else if (wfsEndpoint.supportsJson(featureType.name)) {
33362
- return new GeojsonReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33363
- asJson: true,
33364
- outputCrs: 'EPSG:4326',
33365
- }));
33366
- }
33367
- else {
33368
- if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33369
- (featureType.defaultCrs === 'EPSG:4326' ||
33370
- featureType.otherCrs?.includes('EPSG:4326'))) {
33371
- return new GmlReader(wfsEndpoint.getFeatureUrl(featureType.name, {
33372
- outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33373
- outputCrs: 'EPSG:4326',
33374
- }), featureType.name, wfsEndpoint.getVersion());
33375
- }
33376
- throw new Error('wfs.geojsongml.notsupported');
33377
- }
33920
+ load() {
33921
+ // Nothing to load for Wfs
33378
33922
  }
33379
- async getData(aggregation, groupedBy) {
33380
- if (aggregation || groupedBy) {
33381
- return { items: await this.getQueryData() };
33923
+ async read() {
33924
+ const endpoint = await this.endpoint;
33925
+ const featureType = await this.featureType;
33926
+ // if we can't use the WFS protocol we fall back to the backup reader
33927
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33928
+ const backupReader = await this.backupReader;
33929
+ backupReader.selectAll();
33930
+ if (this.selected) {
33931
+ backupReader.select(...this.selected);
33932
+ }
33933
+ if (this.aggregations) {
33934
+ backupReader.aggregate(...this.aggregations);
33935
+ }
33936
+ if (this.groupedBy) {
33937
+ backupReader.groupBy(...this.groupedBy);
33938
+ }
33939
+ if (this.sort) {
33940
+ backupReader.orderBy(...this.sort);
33941
+ }
33942
+ if (this.startIndex !== null && this.count !== null) {
33943
+ backupReader.limit(this.startIndex, this.count);
33944
+ }
33945
+ return backupReader.read();
33382
33946
  }
33383
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33947
+ const asJson = endpoint.supportsJson(featureType.name);
33384
33948
  const attributes = this.selected ?? undefined;
33385
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33949
+ let sortBy = null;
33950
+ if (this.sort) {
33951
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33952
+ sortBy = Array.isArray(this.sort[0])
33953
+ ? this.sort.map(mapSort)
33954
+ : mapSort(this.sort);
33955
+ }
33956
+ const url = endpoint.getFeatureUrl(featureType.name, {
33386
33957
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33387
33958
  ...(this.count !== null && { maxFeatures: this.count }),
33388
33959
  asJson,
33389
33960
  outputCrs: 'EPSG:4326',
33390
33961
  attributes,
33391
- // sortBy: this.sort // TODO: no sort in ogc-client?
33962
+ sortBy,
33392
33963
  });
33393
- if (Array.isArray(this.sort) && this.sort.length > 0) {
33394
- const finalUrl = new URL(url);
33395
- const sorts = this.sort
33396
- .map((fieldSort) => `${fieldSort[1]}+${fieldSort[0] === 'asc' ? 'A' : 'D'}`)
33397
- .join(',');
33398
- // Direct update on string url to prevent encoding of +A and +D
33399
- url = `${url}${finalUrl.search ? '&' : ''}SORTBY=${sorts}`;
33400
- }
33401
- return fetchDataAsText(url, this.cacheActive).then((text) => asJson
33964
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33402
33965
  ? parseGeojson(text)
33403
- : parseGml(text, this.featureTypeName, this.version));
33404
- }
33405
- async getQueryData() {
33406
- const items = (await this.getData()).items;
33407
- const jsonItems = getJsonDataItemsProxy(items);
33408
- const query = generateSqlQuery(this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations);
33409
- const result = await import('alasql').then((module) => module.default(query, [jsonItems]));
33410
- return result.map(jsonToGeojsonFeature);
33411
- }
33412
- load() {
33413
- // Nothing to load for Wfs
33414
- }
33415
- async read() {
33416
- return (await this.getData(this.aggregations, this.groupedBy)).items;
33966
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33417
33967
  }
33418
33968
  }
33419
33969
 
33420
- async function openDataset(url, typeHint, options, cacheActive) {
33421
- const fileType = await inferDatasetType(url, typeHint);
33970
+ async function openDataset(url, options) {
33971
+ const fileType = await inferDatasetType(url, options?.typeHint);
33422
33972
  let reader;
33423
33973
  try {
33424
33974
  switch (fileType) {
@@ -33435,18 +33985,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33435
33985
  reader = new ExcelReader(url);
33436
33986
  break;
33437
33987
  case 'gml':
33438
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33988
+ reader = new GmlReader(url);
33439
33989
  break;
33440
33990
  case 'wfs':
33441
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33991
+ reader = new WfsReader(url, options?.wfsFeatureType);
33442
33992
  break;
33443
33993
  }
33444
- reader.setCacheActive(cacheActive);
33994
+ reader.enableCache(options?.enableCache ?? true);
33445
33995
  reader.load();
33446
33996
  return reader;
33447
33997
  }
33448
33998
  catch (e) {
33449
- //WfsReader may already raise a FetchError
33999
+ // WfsReader may already raise a FetchError
33450
34000
  if (e instanceof FetchError)
33451
34001
  throw e;
33452
34002
  else
@@ -33457,13 +34007,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33457
34007
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33458
34008
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33459
34009
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
33460
- * File type is determined liked so:
34010
+ * File type is determined like so:
33461
34011
  * 1. if a type hint is given, use it
33462
34012
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33463
34013
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33464
34014
  */
33465
- async function readDataset(url, typeHint, options, cacheActive = true) {
33466
- const reader = await openDataset(url, typeHint, options, cacheActive);
34015
+ async function readDataset(url, options) {
34016
+ const reader = await openDataset(url, options);
33467
34017
  try {
33468
34018
  return await reader.read();
33469
34019
  }
@@ -33694,9 +34244,11 @@ class DataService {
33694
34244
  getDataset(link, cacheActive) {
33695
34245
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33696
34246
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33697
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34247
+ return from(openDataset(wfsUrlEndpoint, {
34248
+ typeHint: 'wfs',
33698
34249
  wfsFeatureType: link.name,
33699
- }, cacheActive));
34250
+ enableCache: cacheActive,
34251
+ }));
33700
34252
  }
33701
34253
  else if (link.type === 'download') {
33702
34254
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33704,12 +34256,15 @@ class DataService {
33704
34256
  const supportedType = SupportedTypes.indexOf(format) > -1
33705
34257
  ? format
33706
34258
  : undefined;
33707
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34259
+ return from(openDataset(linkProxifiedUrl, {
34260
+ typeHint: supportedType,
34261
+ enableCache: cacheActive,
34262
+ })).pipe();
33708
34263
  }
33709
34264
  else if (link.type === 'service' &&
33710
34265
  link.accessServiceProtocol === 'esriRest') {
33711
34266
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33712
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34267
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33713
34268
  }
33714
34269
  else if (link.type === 'service' &&
33715
34270
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33723,7 +34278,10 @@ class DataService {
33723
34278
  }
33724
34279
  const urlWithoutLimit = new URL(geojsonUrl);
33725
34280
  urlWithoutLimit.searchParams.delete('limit');
33726
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34281
+ return openDataset(urlWithoutLimit.toString(), {
34282
+ typeHint: 'geojson',
34283
+ enableCache: cacheActive,
34284
+ });
33727
34285
  }));
33728
34286
  }
33729
34287
  return throwError(() => 'protocol not supported');
@@ -33975,20 +34533,31 @@ class DataTableComponent {
33975
34533
  this.eltRef = inject(ElementRef);
33976
34534
  this.cdr = inject(ChangeDetectorRef);
33977
34535
  this.translateService = inject(TranslateService);
33978
- this._featureAttributes = [];
34536
+ this.columnsFromFeatureCatalog = null;
34537
+ this.columnsFromDataset = [];
33979
34538
  this.selected = new EventEmitter();
33980
- this.properties$ = new BehaviorSubject(null);
33981
34539
  this.loading$ = new BehaviorSubject(false);
33982
34540
  this.error = null;
33983
34541
  }
33984
34542
  set featureAttributes(value) {
33985
- this._featureAttributes = value;
33986
- this.properties$.next(value.map((attr) => attr.value));
34543
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34544
+ name: attrs.value,
34545
+ label: attrs.label,
34546
+ }));
33987
34547
  }
33988
34548
  set dataset(value) {
33989
34549
  this.dataset_ = value;
33990
34550
  this.dataset_.load();
33991
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34551
+ this.dataset_.info.then((info) => {
34552
+ this.count = info.itemsCount;
34553
+ this.cdr.detectChanges();
34554
+ });
34555
+ }
34556
+ get columns() {
34557
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34558
+ }
34559
+ get columnNames() {
34560
+ return this.columns.map((c) => c.name);
33992
34561
  }
33993
34562
  ngOnInit() {
33994
34563
  this.dataSource = new DataTableDataSource();
@@ -34023,11 +34592,12 @@ class DataTableComponent {
34023
34592
  }
34024
34593
  async readData() {
34025
34594
  this.loading$.next(true);
34026
- // wait for properties to be read
34027
- const properties = await firstValueFrom(this.properties$.pipe(filter((p) => !!p)));
34028
- const propsWithoutGeom = properties.filter((p) => !p.toLowerCase().startsWith('geom'));
34029
- this.dataset_.select(...propsWithoutGeom);
34030
34595
  try {
34596
+ // wait for properties to be read
34597
+ if (!this.columnsFromFeatureCatalog) {
34598
+ this.columnsFromDataset = await this.dataset_.properties;
34599
+ }
34600
+ this.dataset_.select(...this.columnNames);
34031
34601
  await this.dataSource.showData(this.dataset_.read());
34032
34602
  this.error = null;
34033
34603
  }
@@ -34057,7 +34627,7 @@ class DataTableComponent {
34057
34627
  }
34058
34628
  }
34059
34629
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34060
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: LetDirective, selector: "[ngrxLet]", inputs: ["ngrxLet", "ngrxLetSuspenseTpl"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34630
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: DataTableComponent, isStandalone: true, selector: "gn-ui-data-table", inputs: { featureAttributes: "featureAttributes", dataset: "dataset", activeId: "activeId" }, outputs: { selected: "selected" }, providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }, { propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$b.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$b.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$b.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$b.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$b.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$b.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$b.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$b.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$b.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$b.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2$4.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2$4.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: LoadingMaskComponent, selector: "gn-ui-loading-mask", inputs: ["message"] }, { kind: "component", type: PopupAlertComponent, selector: "gn-ui-popup-alert", inputs: ["icon", "type", "position"] }, { kind: "directive", type: TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
34061
34631
  }
34062
34632
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34063
34633
  type: Component,
@@ -34069,10 +34639,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34069
34639
  CommonModule,
34070
34640
  LoadingMaskComponent,
34071
34641
  PopupAlertComponent,
34072
- LetDirective,
34073
34642
  TranslatePipe,
34074
34643
  TranslateDirective,
34075
- ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n *ngrxLet=\"properties$ as properties\"\n >\n @for (attr of _featureAttributes; track attr) {\n <ng-container [matColumnDef]=\"attr.value\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ attr.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[attr.value] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"properties; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: properties\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34644
+ ], providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }], selector: 'gn-ui-data-table', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col border border-gray-300 rounded-lg bg-white h-full\">\n <div class=\"flex-1 overflow-y-hidden overflow-x-auto rounded-lg relative\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n (matSortChange)=\"setSort($event)\"\n [matSortDisableClear]=\"true\"\n >\n @for (column of columns; track column) {\n <ng-container [matColumnDef]=\"column.name\">\n <th\n mat-header-cell\n *matHeaderCellDef\n mat-sort-header\n class=\"text-sm text-black bg-white\"\n >\n {{ column.label }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let element\"\n class=\"whitespace-nowrap pr-1 truncate\"\n >\n {{ element[column.name] }}\n </td>\n </ng-container>\n }\n\n <tr mat-header-row *matHeaderRowDef=\"columnNames; sticky: true\"></tr>\n <tr\n [id]=\"getRowEltId(row.id)\"\n mat-row\n *matRowDef=\"let row; columns: columnNames\"\n (click)=\"selected.emit(row)\"\n [class.active]=\"\n activeId !== undefined && activeId !== null && row.id === activeId\n \"\n ></tr>\n </table>\n @if (loading$ | async) {\n <gn-ui-loading-mask\n class=\"sticky inset-0\"\n [message]=\"'table.loading.data' | translate\"\n ></gn-ui-loading-mask>\n }\n @if (error) {\n <gn-ui-popup-alert\n type=\"warning\"\n icon=\"matErrorOutlineOutline\"\n class=\"absolute m-2 inset-0 z-[100]\"\n >\n <span translate>{{ error }}</span>\n </gn-ui-popup-alert>\n }\n </div>\n <div class=\"flex justify-between items-center overflow-hidden\">\n <div class=\"text-gray-900 px-4 py-2 text-sm\">\n <span class=\"count font-extrabold text-primary\">{{ count }}</span\n >&nbsp;<span translate>table.object.count</span>.\n </div>\n\n <mat-paginator\n class=\"my-[-16px]\"\n (page)=\"setPagination()\"\n [length]=\"count\"\n [pageSize]=\"10\"\n [showFirstLastButtons]=\"true\"\n [hidePageSize]=\"true\"\n ></mat-paginator>\n </div>\n</div>\n", styles: ["table{width:100%;background:#fff}th.mat-mdc-header-cell,td.mat-mdc-cell,td.mat-mdc-footer-cell{padding-right:20px}tr.mat-mdc-row,tr.mat-mdc-footer-row{height:36px}tr:hover{background:#f5f5f5}tr.mat-mdc-header-row{height:48px}[mat-header-cell]{color:#0000008a;font-size:12px;font-weight:500}tr{cursor:pointer}.active .mat-mdc-cell{color:var(--color-primary)}.mat-mdc-paginator{background:none}\n"] }]
34076
34645
  }], propDecorators: { featureAttributes: [{
34077
34646
  type: Input
34078
34647
  }], dataset: [{
@@ -34175,7 +34744,7 @@ class ChartViewComponent {
34175
34744
  }), shareReplay$1(1));
34176
34745
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34177
34746
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34178
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34747
+ .filter((prop) => prop.type === 'number')
34179
34748
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34180
34749
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34181
34750
  const newProp = choices[0]?.value || '';
@@ -34202,6 +34771,7 @@ class ChartViewComponent {
34202
34771
  this.aggregation$,
34203
34772
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34204
34773
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34774
+ this.loading = true;
34205
34775
  return dataset
34206
34776
  .groupBy(['distinct', xProp])
34207
34777
  .aggregate(fieldAgg)
@@ -34443,6 +35013,7 @@ class GeoTableViewComponent {
34443
35013
  }));
34444
35014
  }
34445
35015
  async initMapContext() {
35016
+ this.dataset.load();
34446
35017
  this.dataset.selectAll();
34447
35018
  return {
34448
35019
  layers: [
@@ -34616,8 +35187,7 @@ class MdViewFacade {
34616
35187
  }));
34617
35188
  this.error$ = this.store.pipe(select(getMetadataError));
34618
35189
  this.related$ = this.store.pipe(select(getRelated));
34619
- this.sources$ = this.store.pipe(select(getSources));
34620
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35190
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34621
35191
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34622
35192
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34623
35193
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34736,12 +35306,9 @@ class MdViewEffects {
34736
35306
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34737
35307
  return setRelated({ related });
34738
35308
  }), catchError(() => of(setRelated({ related: null })))));
34739
- this.loadSources$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSources(full)), map$1((sources) => {
34740
- return setSources({ sources });
34741
- }), catchError(() => of(setSources({ sources: null })))));
34742
- this.loadSourceOf$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSourceOf(full)), map$1((sourceOf) => {
34743
- return setSourceOf({ sourceOf });
34744
- }), catchError(() => of(setSourceOf({ sourceOf: null })))));
35309
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35310
+ return setLinkedRecords({ linkedRecords });
35311
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34745
35312
  /*
34746
35313
  UserFeedback effects
34747
35314
  */
@@ -40020,7 +40587,7 @@ function flattenQueryParams(params) {
40020
40587
  const start = flattened[key].start;
40021
40588
  const end = flattened[key].end;
40022
40589
  flattened[key] = [
40023
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40590
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40024
40591
  ];
40025
40592
  }
40026
40593
  }
@@ -40377,5 +40944,5 @@ const CHART_TYPE_VALUES = [
40377
40944
  * Generated bundle index. Do not edit.
40378
40945
  */
40379
40946
 
40380
- export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isConfigLoaded, isDateRange, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40947
+ export { ADD_RESULTS, ADD_SEARCH, AVAILABLE_LICENSES, AbstractAction, AbstractSearchField, ActionMenuComponent, AddLayerFromCatalogComponent, AddLayerRecordPreviewComponent, AddResults, AddSearch, AnchorLinkDirective, ApiCardComponent, ApplicationBannerComponent, AuthService, AutocompleteComponent, AutofocusDirective, AvailableServicesField, AvatarComponent, AvatarServiceInterface, BASEMAP_LAYERS, BadgeComponent, BaseConverter, BaseFileReader, BaseReader, BlockListComponent, BoundingBoxSearchField, ButtonComponent, CHART_TYPE_VALUES, CLEAR_ERROR, CLEAR_RESULTS, CarouselComponent, CatalogTitleComponent, CellPopinComponent, ChartComponent, ChartViewComponent, CheckToggleComponent, CheckboxComponent, ClearError, ClearResults, ColorScaleComponent, ConfirmationDialogComponent, ContactDetailsComponent, ContactDetailsFormComponent, ContactPillComponent, ContentGhostComponent, CopyTextButtonComponent, DEFAULT_BASEMAP_LAYER, DEFAULT_CONFIGURATION, DEFAULT_GN4_LOGIN_URL, DEFAULT_GN4_LOGOUT_URL, DEFAULT_GN4_SETTINGS_URL, DEFAULT_LANG, DEFAULT_PAGE_SIZE, DEFAULT_RECORD_CONVERTER, DEFAULT_RESULTS_LAYOUT_CONFIG, DEFAULT_SEARCH_KEY, DEFAULT_SPATIAL_EXTENT_STYLE, DISABLE_AUTH, DISABLE_DRAFT, DO_NOT_USE_DEFAULT_BASEMAP, DataService, DataTableComponent, DataViewComponent, DataViewPermalinkComponent, DataViewShareComponent, DataViewWebComponentComponent, DatePickerComponent, DateRangeDropdownComponent, DateRangeInputsComponent, DateRangePickerComponent, DateRangeSearchField, DateService, DcatApConverter, DefaultRouterModule, DownloadItemComponent, DownloadsListComponent, DragAndDropFileInputComponent, DropdownMultiselectComponent, DropdownSelectorComponent, EDITOR_FEATURE_KEY, ES_QUERY_FIELDS_PRIORITY, ES_RESOURCES_VALUES, ES_SOURCE_SUMMARY, EXTERNAL_VIEWER_OPEN_NEW_TAB, EXTERNAL_VIEWER_URL_TEMPLATE, EditableLabelDirective, EditorFacade, EditorService, ElasticsearchService, ErrorComponent, ErrorType, ExpandablePanelButtonComponent, ExpandablePanelComponent, ExternalLinkCardComponent, ExternalViewerButtonComponent, FIELDS_BRIEF, FIELDS_SUMMARY, FILTER_GEOMETRY, FILTER_SUMMARY_IGNORE_LIST, FORMATS, FacetBlockComponent, FacetItemComponent, FacetListComponent, FacetsContainerComponent, FavoriteStarComponent, FavoritesService, FeatureCatalogListComponent, FeatureDetailComponent, FeatureEditorModule, FeatureMapModule, FeatureRecordModule, FeatureSearchModule, FetchError, FieldFocusDirective, FieldsService, FigureComponent, FigureContainerComponent, FileInputComponent, FileTranslateLoader, FilesDropDirective, FilterDropdownComponent, FormFieldArrayComponent, FormFieldComponent, FormFieldDateComponent, FormFieldFileComponent, FormFieldKeywordsComponent, FormFieldLicenseComponent, FormFieldObjectComponent, FormFieldRichComponent, FormFieldSimpleComponent, FormFieldSpatialExtentComponent, FormFieldTemporalExtentsComponent, FormFieldTopicsComponent, FormFieldWrapperComponent, FullTextSearchField, FuzzySearchComponent, GEONETWORK_UI_TAG_NAME, GEONETWORK_UI_VERSION, GeoDataBadgeComponent, GeoTableViewComponent, GeocodingComponent, GeojsonReader, Gn4Converter, Gn4PlatformMapper, Gn4PlatformService, Gn4Repository, Gn4SettingsService, GnUiHumanizeDateDirective, GpfApiDlComponent, GravatarService, I18nInterceptor, ISO_TOPICS, ImageFallbackDirective, ImageInputComponent, ImageOverlayPreviewComponent, ImportRecordComponent, InlineFilterComponent, InteractiveTableColumnComponent, InteractiveTableComponent, InternalLinkCardComponent, IsSpatialSearchField, Iso191153Converter, Iso19139Converter, KeywordBadgeComponent, KindBadgeComponent, LANGUAGES_LIST, LANGUAGE_NAMES, LANGUAGE_STORAGE_KEY, LANG_2_TO_3_MAPPER, LOGIN_URL, LOGOUT_URL, LONLAT_CRS_CODES, LanguageSwitcherComponent, LayersPanelComponent, LicenseSearchField, LinkClassifierService, LinkUsage, LoadingMaskComponent, LogService, MAP_FEATURE_KEY, MAP_VIEW_CONSTRAINTS, MAX_UPLOAD_SIZE_MB, METADATA_LANGUAGE, MapContainerComponent, MapFacade, MapLegendComponent, MapStateContainerComponent, MapStyleService, MapUtilsService, MapViewComponent, MarkdownEditorComponent, MarkdownParserComponent, MaxLinesComponent, mdview_actions as MdViewActions, MdViewFacade, MetadataCatalogComponent, MetadataContactComponent, MetadataDoiComponent, MetadataInfoComponent, MetadataLinkType, MetadataMapperContext, MetadataQualityComponent, MetadataQualityItemComponent, MetadataQualityPanelComponent, ModalDialogComponent, MultilingualPanelComponent, MultilingualSearchField, MyOrgService, NAMESPACES, NOT_APPLICABLE_CONSTRAINT, NOT_KNOWN_CONSTRAINT, NotificationComponent, NotificationsContainerComponent, NotificationsService, OPEN_DATA_LICENSE, ORGANIZATIONS_STRATEGY, ORGANIZATION_PAGE_URL_TOKEN, ORGANIZATION_URL_TOKEN, OnlineResourceCardComponent, OnlineServiceResourceInputComponent, OrganisationPreviewComponent, OrganisationsComponent, OrganisationsFilterComponent, OrganisationsResultComponent, OrganizationSearchField, OrganizationsFromGroupsService, OrganizationsFromMetadataService, OrganizationsServiceInterface, OwnerSearchField, PAGINATE, PARSE_DELIMITER, PATCH_RESULTS_AGGREGATIONS, PROXY_PATH, Paginate, PaginationButtonsComponent, PaginationComponent, PaginationDotsComponent, PatchResultsAggregations, PlatformServiceInterface, PopoverComponent, PopupAlertComponent, PossibleResourceTypes, PossibleResourceTypesDefinition, PreviousNextButtonsComponent, ProgressBarComponent, ProxyService, QUERY_FIELDS, RECORD_DATASET_URL_TOKEN, RECORD_REUSE_URL_TOKEN, RECORD_SERVICE_URL_TOKEN, REQUEST_MORE_ON_AGGREGATION, REQUEST_MORE_RESULTS, REQUEST_NEW_RESULTS, RESULTS_LAYOUT_CONFIG, REUSE_LIGHT_CONFIGURATION, ROUTER_CONFIG, ROUTER_ROUTE_DATASET, ROUTER_ROUTE_ORGANIZATION, ROUTER_ROUTE_REUSE, ROUTER_ROUTE_SEARCH, ROUTER_ROUTE_SERVICE, ROUTER_STATE_KEY, ROUTE_PARAMS, RecordApiFormComponent, RecordFormComponent, RecordKindField, RecordMetaComponent, RecordMetricComponent, RecordPreviewCardComponent, RecordPreviewComponent, RecordPreviewFeedComponent, RecordPreviewListComponent, RecordPreviewRowComponent, RecordPreviewTextComponent, RecordPreviewTitleComponent, RecordStatusValues, RecordsMetricsComponent, RecordsRepositoryInterface, RecordsService, RequestMoreOnAggregation, RequestMoreResults, RequestNewResults, ResourceTypeLegacyField, ResultsHitsContainerComponent, ResultsHitsNumberComponent, ResultsHitsSearchKindComponent, ResultsLayoutComponent, ResultsLayoutConfigItem, ResultsListComponent, ResultsListContainerComponent, ResultsListItemComponent, ResultsTableComponent, ResultsTableContainerComponent, ReusePresentationForms, RoleLabels, RoleValues, RouterEffects, RouterFacade, RouterService, SEARCH_FEATURE_KEY, SETTINGS_URL, SET_CONFIG_AGGREGATIONS, SET_CONFIG_FILTERS, SET_CONFIG_REQUEST_FIELDS, SET_ERROR, SET_FAVORITES_ONLY, SET_FILTERS, SET_INCLUDE_ON_AGGREGATION, SET_PAGE_SIZE, SET_RESULTS_AGGREGATIONS, SET_RESULTS_HITS, SET_RESULTS_LAYOUT, SET_SEARCH, SET_SORT_BY, SET_SPATIAL_FILTER_ENABLED, SPATIAL_SCOPES, SearchEffects, SearchFacade, SearchFeatureCatalogComponent, SearchFiltersSummaryComponent, SearchFiltersSummaryItemComponent, SearchInputComponent, SearchRouterContainerDirective, SearchService, SearchStateContainerDirective, SelectionService, ServiceCapabilitiesComponent, SetConfigAggregations, SetConfigFilters, SetConfigRequestFields, SetError, SetFavoritesOnly, SetFilters, SetIncludeOnAggregation, SetPageSize, SetResultsAggregations, SetResultsHits, SetResultsLayout, SetSearch, SetSortBy, SetSpatialFilterEnabled, SimpleSearchField, SiteTitleComponent, SortByComponent, SortByEnum, SortableListComponent, SourceLabelComponent, SourcesService, SpatialExtentComponent, SpatialExtentDropdownComponent, SpinningLoaderComponent, StacItemsResultGridComponent, StacViewComponent, StarToggleComponent, StickyHeaderComponent, SupportedTypes, SwitchToggleComponent, THUMBNAIL_PLACEHOLDER, TRANSLATE_DEBUG_CONFIG, TRANSLATE_DEFAULT_CONFIG, TRANSLATE_WITH_OVERRIDES_CONFIG, TableViewComponent, TextAreaComponent, TextInputComponent, ThemeService, ThumbnailComponent, TranslatedSearchField, TruncatedTextComponent, UPDATE_CONFIG_AGGREGATIONS, UPDATE_FILTERS, UPDATE_REQUEST_AGGREGATION_TERM, UpdateConfigAggregations, UpdateFilters, UrlInputComponent, UserFeedbackItemComponent, UserPreviewComponent, UserSearchField, VECTOR_STYLE_DEFAULT, ViewportIntersectorComponent, WEB_COMPONENT_EMBEDDER_URL, XmlParseError, _reset, allChildrenElement, appConfigWithTranslationFixture, appendChildTree, appendChildren, assertValidXml, associationTypeValues, bboxToPolygon, blockModelFixture, bytesToMegabytes, canEditRecord, checkFileFormat, clearSelectedFeatures, createChild, createDocument, createElement, createFuzzyFilter, createNestedChild, createNestedElement, createSpatialExtentLayer, currentPage, defaultMapStyleFixture, defaultMapStyleHlFixture, downgradeImage, downsizeImage, draftSaveSuccess, dragPanCondition, dropEmptyTranslations, editorReducer, emptyBlockModelFixture, findChildElement, findChildOrCreate, findChildrenElement, findConverterForDocument, findNestedChildOrCreate, findNestedElement, findNestedElements, findParent, firstChildElement, formatDate, formatUserInfo, getAddressLines, getAllKeysValidator, getArrayItem, getAsArray, getAsUrl, getBadgeColor, getCustomTranslations, getError, getFavoritesOnly, getFileFormat, getFileFormatFromServiceOutput, getFirstValue, getFormatPriority, getGeometryBoundingBox, getGeometryFromGeoJSON, getGlobalConfig, getIndividualDisplayName, getIsMobile, getJsonDataItemsProxy, getKeywordHierarchyPath, getLayers, getLinkId, getLinkLabel, getLinkPriority, getMapContext, getMapContextLayerFromConfig, getMapState, getMetadataQualityConfig, getMimeTypeForFormat, getNamespace, getOptionalEditorConfig, getOptionalMapConfig, getOptionalSearchConfig, getPageSize, getQualityValidators, getResourceType, getReusePresentationForm, getReuseType, getRootElement, getSearchConfigAggregations, getSearchFilters, getSearchResults, getSearchResultsAggregations, getSearchResultsHits, getSearchResultsLayout, getSearchResultsLoading, getSearchSortBy, getSearchState, getSearchStateSearch, getSelectedFeatures, getSpatialFilterEnabled, getTemporalRangeUnion, getThemeConfig, handleScrollOnNavigation, hasRecordChangedSinceDraft, hasRecordChangedSinceDraftSuccess, initSearch, initialEditorState, initialMapState, initialState, isBoundingBox, isConfigLoaded, isDateRange, isFileExtensionValid, isFormatInQueryParam, isPublished, itemModelFixture, kindToCodeListValue, loadAppConfig, malformedConfigFixture, mapConfigFixture, mapContact, mapKeywords, mapLogo, mapOrganization, mapReducer, markRecordAsChanged, matchesNoApplicableConstraint, matchesNoKnownConstraint, megabytesToBytes, mimeTypeToFormat, minimalAppConfigFixture, missingMandatoryConfigFixture, mouseWheelZoomCondition, noDuplicateFileName, okAppConfigFixture, openDataset, openRecord, organizationsServiceFactory, parse, parseXmlString, placeholder, prioritizePageScroll, propagateToDocumentOnly, provideGn4, provideI18n, provideLocalizedDateAdapter, provideRepositoryUrl, readAttribute, readDataset, readDatasetHeaders, readFileAsText, readText, reducer$2 as reducer, reducerSearch, removeAllChildren, removeChildren, removeChildrenByName, removeSearchParams, removeWhitespace, renameElements, saveRecord, saveRecordFailure, saveRecordSuccess, selectCanEditRecord, selectCurrentPage, selectEditorConfig, selectEditorState, selectFallback, selectFallbackFields, selectField, selectHasRecordChanged, selectIsPublished, selectRecord, selectRecordChangedSinceSave, selectRecordSaveError, selectRecordSaving, selectRecordSections, selectRecordSource, selectTranslatedField, selectTranslatedValue, setContext, setCurrentPage, setEditorConfiguration, setFieldVisibility, setFocusedField, setSelectedFeatures, setTextContent, sortByFromString, sortByToString, sortByToStrings, spatialExtentToGeometry, spatialExtentsToFeatureCollection, stripHtml, stripNamespace, toDate, toIndividual, toLang2, toLang3, totalPages, undoRecordDraft, unrecognizedKeysConfigFixture, updateFrequencyCodeValues, updateLanguages, updateRecordField, updateRecordLanguages, wmsLayerFlatten, writeAttribute, wrongLanguageCodeConfigFixture, xmlToString };
40381
40948
  //# sourceMappingURL=geonetwork-ui.mjs.map