geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.5f9922c2e

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 +1216 -677
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +212 -78
  4. package/index.d.ts.map +1 -1
  5. package/package.json +11 -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 +9 -4
  24. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -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 +117 -0
  44. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  45. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -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 +14 -3
  70. package/translations/en.json +13 -2
  71. package/translations/es.json +11 -0
  72. package/translations/fr.json +14 -3
  73. package/translations/it.json +12 -1
  74. package/translations/nl.json +11 -0
  75. package/translations/pt.json +11 -0
  76. package/translations/sk.json +12 -1
  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.5c2e5295e";
20437
+ var version = "2.11.0-dev.5f9922c2e";
20373
20438
  var engines = {
20374
20439
  node: ">=24"
20375
20440
  };
@@ -20399,20 +20464,21 @@ var peerDependencies = {
20399
20464
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20465
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20466
  "@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",
20467
+ "@ngx-translate/core": "17.x",
20468
+ "@ngx-translate/http-loader": "17.x",
20469
+ "flag-icons": "~7.5.0",
20405
20470
  rxjs: "7.x",
20406
20471
  "zone.js": "*",
20407
20472
  tailwindcss: "3.x"
20408
20473
  };
20409
20474
  var dependencies = {
20410
20475
  "@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",
20476
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20477
+ "@duckdb/duckdb-wasm": "~1.32.0",
20478
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20479
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20480
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20481
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20482
  "@ltd/j-toml": "~1.38.0",
20417
20483
  "@messageformat/core": "~3.4.0",
20418
20484
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20486,7 @@ var dependencies = {
20420
20486
  "@ng-icons/material-icons": "~32.5.0",
20421
20487
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20488
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20489
+ "apache-arrow": "~17.0.0",
20424
20490
  "chart.js": "~4.5.1",
20425
20491
  "chroma-js": "~3.2.0",
20426
20492
  "date-fns": "4.1.0",
@@ -20433,7 +20499,6 @@ var dependencies = {
20433
20499
  "ngx-dropzone": "~3.1.0",
20434
20500
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20501
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20502
  proj4: "~2.20.4",
20438
20503
  rdflib: "~2.3.5",
20439
20504
  semver: "~7.7.4",
@@ -20678,6 +20743,7 @@ class ElasticsearchService {
20678
20743
  values: uuids,
20679
20744
  },
20680
20745
  },
20746
+ size: uuids.length,
20681
20747
  };
20682
20748
  }
20683
20749
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -20785,7 +20851,13 @@ class ElasticsearchService {
20785
20851
  isCurrentSearchLang() {
20786
20852
  return this.metadataLang === 'current';
20787
20853
  }
20788
- filtersToQuery(filters) {
20854
+ findSpatialFilterExtent(filters) {
20855
+ if (typeof filters === 'string') {
20856
+ return undefined;
20857
+ }
20858
+ return Object.values(filters).find(isBoundingBox);
20859
+ }
20860
+ filtersToQuery(filters, spatialFilterExtent = this.findSpatialFilterExtent(filters)) {
20789
20861
  const addQuote = (key) => (/^\/.+\/$/.test(key) ? key : `"${key}"`);
20790
20862
  const makeQuery = (filter) => {
20791
20863
  if (typeof filter === 'string') {
@@ -20804,7 +20876,9 @@ class ElasticsearchService {
20804
20876
  ? filters
20805
20877
  : Object.keys(filters)
20806
20878
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
20879
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
20807
20880
  .filter((fieldname) => !isDateRange(filters[fieldname]))
20881
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
20808
20882
  .filter((fieldname) => filters[fieldname] &&
20809
20883
  JSON.stringify(filters[fieldname]) !== '{}')
20810
20884
  .map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
@@ -20840,6 +20914,21 @@ class ElasticsearchService {
20840
20914
  },
20841
20915
  },
20842
20916
  },
20917
+ spatialFilterExtent && {
20918
+ geo_shape: {
20919
+ geom: {
20920
+ shape: {
20921
+ type: 'envelope',
20922
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
20923
+ coordinates: [
20924
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
20925
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
20926
+ ],
20927
+ },
20928
+ relation: 'intersects',
20929
+ },
20930
+ },
20931
+ },
20843
20932
  ].filter(Boolean);
20844
20933
  return queryParts.length > 0 ? queryParts : undefined;
20845
20934
  }
@@ -20866,7 +20955,9 @@ class ElasticsearchService {
20866
20955
  },
20867
20956
  });
20868
20957
  }
20869
- const queryFilters = this.filtersToQuery(fieldSearchFilters);
20958
+ // a spatial extent filter takes precedence over the preference geometry for boosting
20959
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters);
20960
+ const queryFilters = this.filtersToQuery(fieldSearchFilters, spatialFilterExtent);
20870
20961
  if (queryFilters) {
20871
20962
  filter.push(...queryFilters);
20872
20963
  }
@@ -20877,7 +20968,10 @@ class ElasticsearchService {
20877
20968
  },
20878
20969
  });
20879
20970
  }
20880
- if (geometry) {
20971
+ const boostGeometry = spatialFilterExtent
20972
+ ? bboxToPolygon(spatialFilterExtent)
20973
+ : geometry;
20974
+ if (boostGeometry) {
20881
20975
  // boosts applied using the filter geometry:
20882
20976
  // * records completely within the geometry receive a boost of 5
20883
20977
  // * records intersecting the geometry receive a boost of 2
@@ -20886,7 +20980,7 @@ class ElasticsearchService {
20886
20980
  should.push({
20887
20981
  geo_shape: {
20888
20982
  geom: {
20889
- shape: geometry,
20983
+ shape: boostGeometry,
20890
20984
  relation: 'within',
20891
20985
  },
20892
20986
  boost: 5.0,
@@ -20894,7 +20988,7 @@ class ElasticsearchService {
20894
20988
  }, {
20895
20989
  geo_shape: {
20896
20990
  geom: {
20897
- shape: geometry,
20991
+ shape: boostGeometry,
20898
20992
  relation: 'intersects',
20899
20993
  },
20900
20994
  boost: 2.0,
@@ -20903,7 +20997,7 @@ class ElasticsearchService {
20903
20997
  // this will boost the results variably depending on their distance from the given geometry
20904
20998
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
20905
20999
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
20906
- const bbox = getGeometryBoundingBox(geometry);
21000
+ const bbox = getGeometryBoundingBox(boostGeometry);
20907
21001
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
20908
21002
  const northToCenter = new LineString([
20909
21003
  [center[0], bbox[3]],
@@ -21229,7 +21323,7 @@ class Gn4Repository {
21229
21323
  }
21230
21324
  getRecord(uniqueIdentifier) {
21231
21325
  return this.gn4SearchApi
21232
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21326
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21233
21327
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21234
21328
  }
21235
21329
  getMultipleRecords(uniqueIdentifiers) {
@@ -21283,19 +21377,28 @@ class Gn4Repository {
21283
21377
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21284
21378
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21285
21379
  }
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);
21380
+ getLinkedRecords(record) {
21381
+ const siblings = (record.extras?.['siblings'] ?? []);
21382
+ const relations = [
21383
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21384
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21385
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21386
+ [
21387
+ 'associated',
21388
+ (record.extras?.['associatedIdentifiers'] ?? []),
21389
+ ],
21390
+ ];
21391
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21392
+ if (requested.length === 0) {
21393
+ return of([]);
21297
21394
  }
21298
- return of(null);
21395
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21396
+ record,
21397
+ relation,
21398
+ associationType: relation === 'sibling'
21399
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21400
+ : undefined,
21401
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21299
21402
  }
21300
21403
  aggregate(params) {
21301
21404
  // if aggregations are empty, return an empty object right away
@@ -22556,9 +22659,8 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22556
22659
  });
22557
22660
 
22558
22661
  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>`,
22662
+ type: 'maplibre-style',
22663
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22562
22664
  };
22563
22665
  const DEFAULT_VIEW = {
22564
22666
  center: [0, 15],
@@ -23273,6 +23375,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23273
23375
  'search_preset',
23274
23376
  'advanced_filters',
23275
23377
  'limit',
23378
+ 'spatial_extent_max_file_size',
23276
23379
  ], warnings, errors);
23277
23380
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23278
23381
  searchConfig =
@@ -23290,6 +23393,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23290
23393
  })),
23291
23394
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23292
23395
  LIMIT: parsedSearchSection.limit,
23396
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23293
23397
  };
23294
23398
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23295
23399
  metadataQualityConfig =
@@ -24547,12 +24651,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24547
24651
  type: Input
24548
24652
  }] } });
24549
24653
 
24654
+ function provideLocalizedDateAdapter() {
24655
+ return [
24656
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24657
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24658
+ {
24659
+ provide: DateAdapter,
24660
+ useFactory: () => {
24661
+ const dateService = inject(DateService);
24662
+ const adapter = new DateFnsAdapter();
24663
+ dateService
24664
+ .getDateFnsLocale()
24665
+ .then((locale) => adapter.setLocale(locale));
24666
+ return adapter;
24667
+ },
24668
+ },
24669
+ ];
24670
+ }
24671
+
24550
24672
  class DatePickerComponent {
24551
24673
  constructor() {
24552
- this.dateAdapter = inject(DateAdapter);
24553
- this.translate = inject(TranslateService);
24554
24674
  this.dateChange = new EventEmitter();
24555
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24556
24675
  }
24557
24676
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24558
24677
  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 +24679,19 @@ class DatePickerComponent {
24560
24679
  provideNgIconsConfig({
24561
24680
  size: '1.5rem',
24562
24681
  }),
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 }); }
24682
+ provideLocalizedDateAdapter(),
24683
+ ], 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
24684
  }
24569
24685
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24570
24686
  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: [
24687
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24577
24688
  provideIcons({ iconoirCalendar }),
24578
24689
  provideNgIconsConfig({
24579
24690
  size: '1.5rem',
24580
24691
  }),
24581
- {
24582
- provide: MAT_DATE_LOCALE,
24583
- useFactory: (locale) => locale,
24584
- },
24692
+ provideLocalizedDateAdapter(),
24585
24693
  ], 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: [{
24694
+ }], propDecorators: { date: [{
24587
24695
  type: Input
24588
24696
  }], dateChange: [{
24589
24697
  type: Output
@@ -24591,58 +24699,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24591
24699
 
24592
24700
  class DateRangeDropdownComponent {
24593
24701
  constructor() {
24594
- this.overlayContainer = inject(OverlayContainer);
24702
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24703
+ this.dateAdapter = inject(DateAdapter);
24704
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24595
24705
  this.cdr = inject(ChangeDetectorRef);
24596
- this.startDateChange = new EventEmitter();
24597
- this.endDateChange = new EventEmitter();
24598
- this.isPickerDisplayed = false;
24706
+ this.dateRange = {};
24707
+ this.dateRangeChange = new EventEmitter();
24708
+ this.overlayPositions = [
24709
+ {
24710
+ originX: 'start',
24711
+ originY: 'bottom',
24712
+ overlayX: 'start',
24713
+ overlayY: 'top',
24714
+ offsetY: 8,
24715
+ },
24716
+ {
24717
+ originX: 'start',
24718
+ originY: 'top',
24719
+ overlayX: 'start',
24720
+ overlayY: 'bottom',
24721
+ offsetY: -8,
24722
+ },
24723
+ ];
24724
+ this.scrollStrategy = this.scrollStrategies.reposition();
24725
+ this.overlayOpen = false;
24726
+ this.expandedBound = 'start';
24727
+ this.invalidBounds = { start: false, end: false };
24728
+ // redraw when the lazily loaded UI locale reaches the adapter
24729
+ this.dateAdapter.localeChanges
24730
+ .pipe(takeUntilDestroyed())
24731
+ .subscribe(() => this.cdr.markForCheck());
24599
24732
  }
24600
- ngAfterViewChecked() {
24601
- this.checkPickerOverlay();
24733
+ get startDate() {
24734
+ return this.dateRange.start;
24602
24735
  }
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
24736
+ get endDate() {
24737
+ return this.dateRange.end;
24738
+ }
24739
+ get selectedDatesCount() {
24740
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24741
+ }
24742
+ openOverlay() {
24743
+ this.expandedBound = 'start';
24744
+ this.overlayOpen = true;
24745
+ }
24746
+ closeOverlay() {
24747
+ this.overlayOpen = false;
24748
+ }
24749
+ toggleBound(bound) {
24750
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24751
+ }
24752
+ formatDate(date) {
24753
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24754
+ }
24755
+ onDateInput(bound, event) {
24756
+ const typedText = event.target.value.trim();
24757
+ const date = typedText
24758
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24759
+ : null;
24760
+ if (!this.isAcceptable(bound, typedText, date)) {
24761
+ // keep the text as typed and flag it; the filter stays on its last value
24762
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24763
+ return;
24764
+ }
24765
+ if (bound === 'start')
24766
+ this.setStartDate(date);
24767
+ else
24768
+ this.setEndDate(date);
24769
+ }
24770
+ normalizeDateInput(bound, event) {
24771
+ if (this.invalidBounds[bound])
24772
+ return;
24773
+ const date = bound === 'start' ? this.startDate : this.endDate;
24774
+ const input = event.target;
24775
+ input.value = date ? this.formatDate(date) : '';
24776
+ }
24777
+ isAcceptable(bound, typedText, date) {
24778
+ if (!typedText)
24779
+ return true;
24780
+ if (!date || !this.dateAdapter.isValid(date))
24781
+ return false;
24782
+ return bound === 'start'
24783
+ ? !this.endDate || date <= this.endDate
24784
+ : !this.startDate || date >= this.startDate;
24785
+ }
24786
+ setStartDate(date) {
24787
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24788
+ this.applyDateRange({ ...this.dateRange, start: date });
24789
+ this.expandedBound = 'end';
24790
+ }
24791
+ setEndDate(date) {
24792
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24793
+ this.applyDateRange({ ...this.dateRange, end: date });
24794
+ this.expandedBound = null;
24795
+ }
24796
+ clearDates(event) {
24797
+ this.invalidBounds = { start: false, end: false };
24798
+ this.expandedBound = 'start';
24799
+ this.applyDateRange({});
24800
+ propagateToDocumentOnly(event);
24801
+ }
24802
+ applyDateRange(dateRange) {
24803
+ this.dateRange = {
24804
+ ...(dateRange.start && { start: dateRange.start }),
24805
+ ...(dateRange.end && { end: dateRange.end }),
24806
+ };
24807
+ this.dateRangeChange.emit(this.dateRange);
24611
24808
  }
24612
24809
  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: [
24810
+ 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
24811
  provideIcons({
24615
- matExpandMore,
24812
+ iconoirCalendar,
24813
+ matClose,
24616
24814
  matExpandLess,
24815
+ matExpandMore,
24816
+ }),
24817
+ provideNgIconsConfig({
24818
+ size: '1.5rem',
24617
24819
  }),
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"] }] }); }
24820
+ provideLocalizedDateAdapter(),
24821
+ ], 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
24822
  }
24620
24823
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24621
24824
  type: Component,
24622
24825
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24623
- NgIconComponent,
24624
- MatNativeDateModule,
24625
- MatDatepickerModule,
24826
+ AutofocusDirective,
24626
24827
  ButtonComponent,
24828
+ MatDatepickerModule,
24829
+ NgIcon,
24830
+ NgTemplateOutlet,
24831
+ OverlayModule,
24832
+ TranslateDirective,
24833
+ TranslatePipe,
24627
24834
  ], providers: [
24628
24835
  provideIcons({
24629
- matExpandMore,
24836
+ iconoirCalendar,
24837
+ matClose,
24630
24838
  matExpandLess,
24839
+ matExpandMore,
24631
24840
  }),
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: [{
24634
- type: Input
24635
- }], startDate: [{
24841
+ provideNgIconsConfig({
24842
+ size: '1.5rem',
24843
+ }),
24844
+ provideLocalizedDateAdapter(),
24845
+ ], 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"] }]
24846
+ }], ctorParameters: () => [], propDecorators: { title: [{
24636
24847
  type: Input
24637
- }], endDate: [{
24848
+ }], dateRange: [{
24638
24849
  type: Input
24639
- }], startDateChange: [{
24640
- type: Output
24641
- }], endDateChange: [{
24850
+ }], dateRangeChange: [{
24642
24851
  type: Output
24643
- }], picker: [{
24852
+ }], overlayOrigin: [{
24644
24853
  type: ViewChild,
24645
- args: ['picker']
24854
+ args: ['overlayOrigin']
24646
24855
  }] } });
24647
24856
 
24648
24857
  class DateRangePickerComponent {
@@ -24656,20 +24865,17 @@ class DateRangePickerComponent {
24656
24865
  provideNgIconsConfig({
24657
24866
  size: '1.5rem',
24658
24867
  }),
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 }); }
24868
+ provideLocalizedDateAdapter(),
24869
+ ], 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
24870
  }
24661
24871
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24662
24872
  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: [
24873
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24669
24874
  provideIcons({ iconoirCalendar }),
24670
24875
  provideNgIconsConfig({
24671
24876
  size: '1.5rem',
24672
24877
  }),
24878
+ provideLocalizedDateAdapter(),
24673
24879
  ], 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
24880
  }], propDecorators: { startDate: [{
24675
24881
  type: Input
@@ -24687,28 +24893,68 @@ class DragAndDropFileInputComponent {
24687
24893
  constructor() {
24688
24894
  this.placeholder = placeholder;
24689
24895
  this.accept = '*';
24896
+ this.maxFileSizeMb = null;
24897
+ this.icon = null;
24898
+ this.dropzoneBackgroundColor = null;
24899
+ this.textClass = '';
24900
+ this.extraClass = '';
24901
+ this.showFileName = true;
24690
24902
  this.fileChange = new EventEmitter();
24903
+ this.errorChange = new EventEmitter();
24691
24904
  this.selectedFile = null;
24692
24905
  }
24693
24906
  get fileName() {
24694
24907
  return this.selectedFile && this.selectedFile.name;
24695
24908
  }
24909
+ get maxFileSizeBytes() {
24910
+ return typeof this.maxFileSizeMb === 'number'
24911
+ ? megabytesToBytes(this.maxFileSizeMb)
24912
+ : null;
24913
+ }
24696
24914
  selectFile(event) {
24915
+ if (event.rejectedFiles?.length) {
24916
+ const reason = event.rejectedFiles[0].reason;
24917
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24918
+ return;
24919
+ }
24697
24920
  this.selectedFile = event.addedFiles[0];
24698
24921
  this.fileChange.emit(this.selectedFile);
24699
24922
  }
24923
+ openFileSelector() {
24924
+ this.dropzone.showFileSelector();
24925
+ }
24926
+ clear() {
24927
+ this.selectedFile = null;
24928
+ }
24700
24929
  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"] }] }); }
24930
+ 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
24931
  }
24703
24932
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24704
24933
  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"] }]
24934
+ 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
24935
  }], propDecorators: { placeholder: [{
24707
24936
  type: Input
24708
24937
  }], accept: [{
24709
24938
  type: Input
24939
+ }], maxFileSizeMb: [{
24940
+ type: Input
24941
+ }], icon: [{
24942
+ type: Input
24943
+ }], dropzoneBackgroundColor: [{
24944
+ type: Input
24945
+ }], textClass: [{
24946
+ type: Input
24947
+ }], extraClass: [{
24948
+ type: Input
24949
+ }], showFileName: [{
24950
+ type: Input
24710
24951
  }], fileChange: [{
24711
24952
  type: Output
24953
+ }], errorChange: [{
24954
+ type: Output
24955
+ }], dropzone: [{
24956
+ type: ViewChild,
24957
+ args: [NgxDropzoneComponent]
24712
24958
  }] } });
24713
24959
 
24714
24960
  class DropdownMultiselectComponent {
@@ -24863,7 +25109,7 @@ class DropdownMultiselectComponent {
24863
25109
  matExpandMore,
24864
25110
  matExpandLess,
24865
25111
  }),
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 }); }
25112
+ ], 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
25113
  }
24868
25114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24869
25115
  type: Component,
@@ -24873,7 +25119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24873
25119
  matExpandMore,
24874
25120
  matExpandLess,
24875
25121
  }),
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" }]
25122
+ ], 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
25123
  }], propDecorators: { title: [{
24878
25124
  type: Input
24879
25125
  }], choices: [{
@@ -25045,7 +25291,7 @@ class DropdownSelectorComponent {
25045
25291
  provideNgIconsConfig({
25046
25292
  size: '1.5em',
25047
25293
  }),
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 }); }
25294
+ ], 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
25295
  }
25050
25296
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25051
25297
  type: Component,
@@ -25785,6 +26031,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25785
26031
  type: Output
25786
26032
  }] } });
25787
26033
 
26034
+ marker('search.filters.spatialExtent.import');
26035
+ marker('search.filters.spatialExtent.helpText');
26036
+ marker('search.filters.spatialExtent.error.title');
26037
+ marker('search.filters.spatialExtent.bboxPrefix');
26038
+ marker('search.filters.spatialExtent.bboxDelete');
26039
+ class SpatialExtentDropdownComponent {
26040
+ constructor() {
26041
+ this.cd = inject(ChangeDetectorRef);
26042
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26043
+ this.maxFileSizeMb = null;
26044
+ this.bboxChange = new EventEmitter();
26045
+ this.errorChange = new EventEmitter();
26046
+ this.bbox = null;
26047
+ this.fileName = '';
26048
+ this.overlayPositions = [
26049
+ {
26050
+ originX: 'start',
26051
+ originY: 'bottom',
26052
+ overlayX: 'start',
26053
+ overlayY: 'top',
26054
+ offsetY: 8,
26055
+ },
26056
+ {
26057
+ originX: 'start',
26058
+ originY: 'top',
26059
+ overlayX: 'start',
26060
+ overlayY: 'bottom',
26061
+ offsetY: -8,
26062
+ },
26063
+ ];
26064
+ this.scrollStrategy = this.scrollStrategies.reposition();
26065
+ this.overlayOpen = false;
26066
+ this.overlayMinWidth = 'none';
26067
+ this.errorKey = null;
26068
+ }
26069
+ get hasSelection() {
26070
+ return !!this.bbox;
26071
+ }
26072
+ openOverlay() {
26073
+ this.overlayMinWidth =
26074
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26075
+ .width + 'px';
26076
+ this.overlayOpen = true;
26077
+ }
26078
+ closeOverlay() {
26079
+ this.overlayOpen = false;
26080
+ }
26081
+ toggleOverlay() {
26082
+ if (this.overlayOpen) {
26083
+ this.closeOverlay();
26084
+ }
26085
+ else {
26086
+ this.openOverlay();
26087
+ }
26088
+ }
26089
+ async handleFileSelected(file) {
26090
+ this.errorKey = null;
26091
+ let content;
26092
+ try {
26093
+ content = await readFileAsText(file);
26094
+ const parsed = JSON.parse(content);
26095
+ const geometry = getGeometryFromGeoJSON(parsed);
26096
+ if (!geometry) {
26097
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26098
+ return;
26099
+ }
26100
+ const bbox = getGeometryBoundingBox(geometry);
26101
+ this.bbox = bbox;
26102
+ this.fileName = file.name;
26103
+ this.bboxChange.emit(bbox);
26104
+ this.cd.markForCheck();
26105
+ }
26106
+ catch {
26107
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26108
+ return;
26109
+ }
26110
+ }
26111
+ handleFileError(error) {
26112
+ if (error === 'file-too-large') {
26113
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26114
+ maxSize: this.maxFileSizeMb,
26115
+ });
26116
+ }
26117
+ else {
26118
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26119
+ }
26120
+ }
26121
+ setError(errorKey, params) {
26122
+ this.errorKey = errorKey;
26123
+ this.errorChange.emit({ key: errorKey, params });
26124
+ this.cd.markForCheck();
26125
+ }
26126
+ removeSelection(event) {
26127
+ this.bbox = null;
26128
+ this.fileName = '';
26129
+ this.errorKey = null;
26130
+ this.fileInput?.clear();
26131
+ this.bboxChange.emit(null);
26132
+ propagateToDocumentOnly(event);
26133
+ }
26134
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26135
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SpatialExtentDropdownComponent, isStandalone: true, selector: "gn-ui-spatial-extent-dropdown", inputs: { title: "title", maxFileSizeMb: "maxFileSizeMb" }, outputs: { bboxChange: "bboxChange", errorChange: "errorChange" }, providers: [
26136
+ provideIcons({
26137
+ iconoirCheckCircle,
26138
+ iconoirImport,
26139
+ iconoirSquareDashed,
26140
+ iconoirTrash,
26141
+ matClose,
26142
+ matExpandLess,
26143
+ matExpandMore,
26144
+ }),
26145
+ ], viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: ["overlayOrigin"], descendants: true }, { propertyName: "overlay", first: true, predicate: CdkConnectedOverlay, descendants: true }, { propertyName: "fileInput", first: true, predicate: DragAndDropFileInputComponent, descendants: true }], ngImport: i0, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "gn-ui-button", inputs: ["type", "disabled", "extraClass"], outputs: ["buttonClick"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1$7.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1$7.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: DragAndDropFileInputComponent, selector: "gn-ui-drag-and-drop-file-input", inputs: ["placeholder", "accept", "maxFileSizeMb", "icon", "dropzoneBackgroundColor", "textClass", "extraClass", "showFileName"], outputs: ["fileChange", "errorChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
26146
+ }
26147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26148
+ type: Component,
26149
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26150
+ ButtonComponent,
26151
+ NgIcon,
26152
+ OverlayModule,
26153
+ TranslatePipe,
26154
+ DragAndDropFileInputComponent,
26155
+ ], providers: [
26156
+ provideIcons({
26157
+ iconoirCheckCircle,
26158
+ iconoirImport,
26159
+ iconoirSquareDashed,
26160
+ iconoirTrash,
26161
+ matClose,
26162
+ matExpandLess,
26163
+ matExpandMore,
26164
+ }),
26165
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<gn-ui-button\n type=\"outline\"\n extraClass=\"bg-background w-full !p-[8px] !pl-[16px]\"\n [title]=\"title\"\n (buttonClick)=\"toggleOverlay()\"\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n>\n <div class=\"grow flex items-center mr-2 gap-2 overflow-hidden\">\n <div class=\"text-left font-medium truncate py-1\">\n {{ title }}\n </div>\n @if (hasSelection) {\n <div\n class=\"bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center\"\n >\n 1\n </div>\n }\n </div>\n <button class=\"h-6 w-6 flex items-center\" data-cy=\"clearSelection\">\n @if (hasSelection && !overlayOpen) {\n <ng-icon\n class=\"shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn\"\n (click)=\"removeSelection($event)\"\n name=\"matClose\"\n ></ng-icon>\n }\n </button>\n <ng-icon\n [name]=\"overlayOpen ? 'matExpandLess' : 'matExpandMore'\"\n class=\"shrink-0 opacity-40\"\n >\n </ng-icon>\n</gn-ui-button>\n\n<ng-template\n cdkConnectedOverlay\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"overlayOpen\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n (overlayOutsideClick)=\"closeOverlay()\"\n (detach)=\"closeOverlay()\"\n>\n <div\n class=\"bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2\"\n [style.min-width]=\"overlayMinWidth\"\n >\n <div class=\"text-xs text-black\">\n {{ 'search.filters.spatialExtent.helpText' | translate }}\n </div>\n\n <div\n [class]=\"\n (hasSelection\n ? 'border-b-[1px] border-gray-400 pb-[20px]'\n : 'pb-[8px]') + ' text-black'\n \"\n >\n <gn-ui-drag-and-drop-file-input\n accept=\".json,.geojson\"\n [placeholder]=\"'search.filters.spatialExtent.import' | translate\"\n [maxFileSizeMb]=\"maxFileSizeMb\"\n [showFileName]=\"false\"\n textClass=\"text-black truncate\"\n dropzoneBackgroundColor=\"transparent\"\n icon=\"iconoirImport\"\n extraClass=\"gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between\"\n (fileChange)=\"handleFileSelected($event)\"\n (errorChange)=\"handleFileError($event)\"\n data-cy=\"importGeojson\"\n ></gn-ui-drag-and-drop-file-input>\n </div>\n\n @if (hasSelection) {\n <gn-ui-button\n [type]=\"'primary-light'\"\n [title]=\"\n ('search.filters.spatialExtent.bboxDelete' | translate) +\n ' ' +\n fileName\n \"\n (buttonClick)=\"removeSelection($event)\"\n extraClass=\"group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white\"\n data-test=\"spatial-extent-selected-item\"\n >\n <ng-icon\n name=\"iconoirSquareDashed\"\n class=\"shrink-0\"\n size=\"15px\"\n ></ng-icon>\n <span class=\"group-hover:hidden truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}\n {{ fileName }}\n </span>\n <span class=\"hidden group-hover:block truncate text-sm m-auto\">\n {{ 'search.filters.spatialExtent.bboxDelete' | translate }}\n {{ fileName }}\n </span>\n <ng-icon\n name=\"iconoirCheckCircle\"\n class=\"group-hover:hidden shrink-0\"\n size=\"14px\"\n ></ng-icon>\n <ng-icon\n name=\"iconoirTrash\"\n class=\"!hidden group-hover:!block shrink-0\"\n size=\"15px\"\n ></ng-icon>\n </gn-ui-button>\n }\n </div>\n</ng-template>\n" }]
26166
+ }], propDecorators: { title: [{
26167
+ type: Input
26168
+ }], maxFileSizeMb: [{
26169
+ type: Input
26170
+ }], bboxChange: [{
26171
+ type: Output
26172
+ }], errorChange: [{
26173
+ type: Output
26174
+ }], overlayOrigin: [{
26175
+ type: ViewChild,
26176
+ args: ['overlayOrigin']
26177
+ }], overlay: [{
26178
+ type: ViewChild,
26179
+ args: [CdkConnectedOverlay]
26180
+ }], fileInput: [{
26181
+ type: ViewChild,
26182
+ args: [DragAndDropFileInputComponent]
26183
+ }] } });
26184
+
25788
26185
  class CellPopinComponent {
25789
26186
  constructor() {
25790
26187
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25861,7 +26258,7 @@ class CellPopinComponent {
25861
26258
  this.isOpen = false;
25862
26259
  }
25863
26260
  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"] }] }); }
26261
+ 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
26262
  }
25866
26263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25867
26264
  type: Component,
@@ -27951,6 +28348,23 @@ class DateRangeSearchField extends SimpleSearchField {
27951
28348
  return 'dateRange';
27952
28349
  }
27953
28350
  }
28351
+ class BoundingBoxSearchField extends SimpleSearchField {
28352
+ getAvailableValues() {
28353
+ return of([]);
28354
+ }
28355
+ getFiltersForValues(values) {
28356
+ return of({
28357
+ [this.esFieldName]: values.map(Number),
28358
+ });
28359
+ }
28360
+ getValuesForFilter(filters) {
28361
+ const filter = filters[this.esFieldName];
28362
+ return of(isBoundingBox(filter) ? filter : []);
28363
+ }
28364
+ getType() {
28365
+ return 'spatialExtent';
28366
+ }
28367
+ }
27954
28368
  marker('search.filters.availableServices.view');
27955
28369
  marker('search.filters.availableServices.download');
27956
28370
  class AvailableServicesField extends SimpleSearchField {
@@ -28095,6 +28509,7 @@ marker('search.filters.producerOrg');
28095
28509
  marker('search.filters.publisherOrg');
28096
28510
  marker('search.filters.user');
28097
28511
  marker('search.filters.changeDate');
28512
+ marker('search.filters.spatialExtent');
28098
28513
  class FieldsService {
28099
28514
  constructor() {
28100
28515
  this.injector = inject(Injector);
@@ -28118,6 +28533,7 @@ class FieldsService {
28118
28533
  user: new UserSearchField(this.injector),
28119
28534
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28120
28535
  availableServices: new AvailableServicesField(this.injector),
28536
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
28121
28537
  };
28122
28538
  }
28123
28539
  get supportedFields() {
@@ -29380,7 +29796,7 @@ class ContactPillComponent {
29380
29796
  this.overlayOpen = false;
29381
29797
  }
29382
29798
  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 }); }
29799
+ 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
29800
  }
29385
29801
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29386
29802
  type: Component,
@@ -31302,19 +31718,111 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31302
31718
  type: Output
31303
31719
  }] } });
31304
31720
 
31721
+ class NotificationsService {
31722
+ constructor() {
31723
+ this.notifications$ = new BehaviorSubject([]);
31724
+ }
31725
+ showNotification(content, timeoutMs, error) {
31726
+ error && console.error(error);
31727
+ const id = Math.floor(Math.random() * 1000000);
31728
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31729
+ if (typeof timeoutMs !== 'undefined') {
31730
+ setTimeout(() => {
31731
+ this.removeNotificationById(id);
31732
+ }, timeoutMs);
31733
+ }
31734
+ return id;
31735
+ }
31736
+ removeNotificationById(id) {
31737
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31738
+ }
31739
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31740
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31741
+ }
31742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31743
+ type: Injectable,
31744
+ args: [{
31745
+ providedIn: 'root',
31746
+ }]
31747
+ }] });
31748
+
31749
+ class NotificationsContainerComponent {
31750
+ constructor() {
31751
+ this.notificationsService = inject(NotificationsService);
31752
+ }
31753
+ trackById(index, notification) {
31754
+ return notification.id;
31755
+ }
31756
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31757
+ 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: [
31758
+ trigger('enterExit', [
31759
+ transition(':enter', [
31760
+ animate('150ms', keyframes([
31761
+ style({ transform: 'scale(1)', opacity: 0 }),
31762
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31763
+ style({ transform: 'scale(1)', opacity: 1 }),
31764
+ ])),
31765
+ ]),
31766
+ transition(':leave', [
31767
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31768
+ ]),
31769
+ ]),
31770
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31771
+ }
31772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31773
+ type: Component,
31774
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31775
+ trigger('enterExit', [
31776
+ transition(':enter', [
31777
+ animate('150ms', keyframes([
31778
+ style({ transform: 'scale(1)', opacity: 0 }),
31779
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31780
+ style({ transform: 'scale(1)', opacity: 1 }),
31781
+ ])),
31782
+ ]),
31783
+ transition(':leave', [
31784
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31785
+ ]),
31786
+ ]),
31787
+ ], 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" }]
31788
+ }] });
31789
+
31305
31790
  class FilterDropdownComponent {
31306
31791
  constructor() {
31307
31792
  this.searchFacade = inject(SearchFacade);
31308
31793
  this.searchService = inject(SearchService);
31309
31794
  this.fieldsService = inject(FieldsService);
31795
+ this.notificationsService = inject(NotificationsService);
31796
+ this.translateService = inject(TranslateService);
31797
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31310
31798
  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));
31799
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31800
+ this.spatialExtentErrorNotificationId = null;
31312
31801
  }
31313
31802
  onSelectedValues(values) {
31314
31803
  this.fieldsService
31315
31804
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31316
31805
  .subscribe((filters) => this.searchService.updateFilters(filters));
31317
31806
  }
31807
+ onBboxChange(bbox) {
31808
+ console.log(bbox);
31809
+ this.clearSpatialExtentErrorNotification();
31810
+ }
31811
+ onSpatialExtentError(error) {
31812
+ this.clearSpatialExtentErrorNotification();
31813
+ this.spatialExtentErrorNotificationId =
31814
+ this.notificationsService.showNotification({
31815
+ type: 'error',
31816
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31817
+ text: this.translateService.instant(error.key, error.params),
31818
+ });
31819
+ }
31820
+ clearSpatialExtentErrorNotification() {
31821
+ if (this.spatialExtentErrorNotificationId === null)
31822
+ return;
31823
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31824
+ this.spatialExtentErrorNotificationId = null;
31825
+ }
31318
31826
  ngOnInit() {
31319
31827
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31320
31828
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31322,27 +31830,15 @@ class FilterDropdownComponent {
31322
31830
  value: v.value.toString(), // converting to string for the dropdown
31323
31831
  }))), catchError(() => of([])));
31324
31832
  }
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
- }
31833
+ onDateRangeChange(dateRange) {
31834
+ this.fieldsService
31835
+ .buildFiltersFromFieldValues({
31836
+ [this.fieldName]: dateRange,
31837
+ })
31838
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31343
31839
  }
31344
31840
  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 }); }
31841
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilterDropdownComponent, isStandalone: true, selector: "gn-ui-filter-dropdown", inputs: { fieldName: "fieldName", title: "title" }, ngImport: i0, template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DateRangeDropdownComponent, selector: "gn-ui-date-range-dropdown", inputs: ["title", "dateRange"], outputs: ["dateRangeChange"] }, { kind: "component", type: DropdownMultiselectComponent, selector: "gn-ui-dropdown-multiselect", inputs: ["title", "choices", "selected", "allowSearch", "maxRows", "searchInputValue"], outputs: ["selectValues"] }, { kind: "component", type: SpatialExtentDropdownComponent, selector: "gn-ui-spatial-extent-dropdown", inputs: ["title", "maxFileSizeMb"], outputs: ["bboxChange", "errorChange"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31346
31842
  }
31347
31843
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31348
31844
  type: Component,
@@ -31350,7 +31846,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31350
31846
  CommonModule,
31351
31847
  DateRangeDropdownComponent,
31352
31848
  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" }]
31849
+ SpatialExtentDropdownComponent,
31850
+ ], template: "@if (fieldType === 'dateRange') {\n <gn-ui-date-range-dropdown\n [title]=\"title\"\n [dateRange]=\"(selectedDateRange$ | async) ?? {}\"\n (dateRangeChange)=\"onDateRangeChange($event)\"\n ></gn-ui-date-range-dropdown>\n} @else if (fieldType === 'spatialExtent') {\n <gn-ui-spatial-extent-dropdown\n [title]=\"title\"\n [maxFileSizeMb]=\"spatialExtentMaxFileSize\"\n (bboxChange)=\"onBboxChange($event)\"\n (errorChange)=\"onSpatialExtentError($event)\"\n ></gn-ui-spatial-extent-dropdown>\n} @else {\n <gn-ui-dropdown-multiselect\n class=\"w-full\"\n [title]=\"title\"\n [maxRows]=\"6\"\n [choices]=\"(choices$ | async) || []\"\n [selected]=\"selected$ | async\"\n [allowSearch]=\"true\"\n (selectValues)=\"onSelectedValues($event)\"\n [attr.data-cy-field]=\"fieldName\"\n >\n </gn-ui-dropdown-multiselect>\n}\n" }]
31354
31851
  }], propDecorators: { fieldName: [{
31355
31852
  type: Input
31356
31853
  }], title: [{
@@ -31886,74 +32383,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31886
32383
  args: ['gnUiSearchStateContainer']
31887
32384
  }] } });
31888
32385
 
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
32386
  class ResultsTableContainerComponent {
31958
32387
  constructor() {
31959
32388
  this.searchFacade = inject(SearchFacade);
@@ -32038,12 +32467,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32038
32467
 
32039
32468
  marker('search.filters.summaryLabel.user');
32040
32469
  marker('search.filters.summaryLabel.changeDate');
32470
+ const OPEN_BOUND = '…';
32041
32471
  class SearchFiltersSummaryItemComponent {
32042
32472
  constructor() {
32043
32473
  this.searchFacade = inject(SearchFacade);
32044
32474
  this.searchService = inject(SearchService);
32045
32475
  this.fieldsService = inject(FieldsService);
32046
- this.datePipe = inject(DatePipe);
32476
+ this.dateService = inject(DateService);
32047
32477
  this.translate = inject(TranslateService);
32048
32478
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32049
32479
  ? fieldValues[this.fieldName]
@@ -32070,9 +32500,10 @@ class SearchFiltersSummaryItemComponent {
32070
32500
  getReadableValues(fieldValues) {
32071
32501
  return fieldValues.map((value) => {
32072
32502
  if (this.fieldType === 'dateRange') {
32503
+ const { start, end } = value;
32073
32504
  return {
32074
32505
  value,
32075
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32506
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32076
32507
  };
32077
32508
  }
32078
32509
  else if (this.fieldName === 'user') {
@@ -32083,6 +32514,15 @@ class SearchFiltersSummaryItemComponent {
32083
32514
  }
32084
32515
  });
32085
32516
  }
32517
+ formatBound(date) {
32518
+ return date
32519
+ ? this.dateService.formatDate(date, {
32520
+ day: '2-digit',
32521
+ month: '2-digit',
32522
+ year: 'numeric',
32523
+ })
32524
+ : OPEN_BOUND;
32525
+ }
32086
32526
  async removeFilterValue(fieldValue) {
32087
32527
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32088
32528
  const updatedFieldValues = currentFieldValues
@@ -32095,11 +32535,11 @@ class SearchFiltersSummaryItemComponent {
32095
32535
  .subscribe((filters) => this.searchService.updateFilters(filters));
32096
32536
  }
32097
32537
  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" }] }); }
32538
+ 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
32539
  }
32100
32540
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32101
32541
  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" }]
32542
+ 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
32543
  }], propDecorators: { fieldName: [{
32104
32544
  type: Input
32105
32545
  }] } });
@@ -32343,11 +32783,11 @@ class AddLayerFromFileComponent {
32343
32783
  }, 5000);
32344
32784
  }
32345
32785
  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" }] }); }
32786
+ 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
32787
  }
32348
32788
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32349
32789
  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" }]
32790
+ 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
32791
  }] });
32352
32792
 
32353
32793
  class LayersPanelComponent {
@@ -32512,8 +32952,7 @@ const closeMetadata = createAction('[Metadata view] close');
32512
32952
  Related actions
32513
32953
  */
32514
32954
  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());
32955
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32517
32956
  /*
32518
32957
  ChartConfig actions
32519
32958
  */
@@ -32545,9 +32984,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32545
32984
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32546
32985
  setChartConfig: setChartConfig,
32547
32986
  setIncompleteMetadata: setIncompleteMetadata,
32548
- setRelated: setRelated,
32549
- setSourceOf: setSourceOf,
32550
- setSources: setSources
32987
+ setLinkedRecords: setLinkedRecords,
32988
+ setRelated: setRelated
32551
32989
  });
32552
32990
 
32553
32991
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32590,12 +33028,9 @@ on(loadFullMetadata, (state) => ({
32590
33028
  on(setRelated, (state, { related }) => ({
32591
33029
  ...state,
32592
33030
  related,
32593
- })), on(setSources, (state, { sources }) => ({
32594
- ...state,
32595
- sources,
32596
- })), on(setSourceOf, (state, { sourceOf }) => ({
33031
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32597
33032
  ...state,
32598
- sourceOf,
33033
+ linkedRecords,
32599
33034
  })),
32600
33035
  /*
32601
33036
  ChartConfig reducers
@@ -32657,8 +33092,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32657
33092
  Related selectors
32658
33093
  */
32659
33094
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32660
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32661
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33095
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32662
33096
  /*
32663
33097
  Metadata selectors
32664
33098
  */
@@ -32759,212 +33193,10 @@ function parseHeaders(httpHeaders) {
32759
33193
  return result;
32760
33194
  }
32761
33195
 
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
- });
33196
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33197
+ function fieldToSql(name) {
33198
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
32794
33199
  }
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
- });
32825
- }
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
33200
  function filterToSql(filter) {
32969
33201
  const operator = filter[0];
32970
33202
  const args = filter.slice(1);
@@ -32979,10 +33211,10 @@ function filterToSql(filter) {
32979
33211
  case '=':
32980
33212
  case '!=':
32981
33213
  case 'like':
32982
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33214
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32983
33215
  case 'in': {
32984
33216
  const values = args.slice(1);
32985
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
33217
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
32986
33218
  }
32987
33219
  case 'and':
32988
33220
  case 'or': {
@@ -33001,17 +33233,18 @@ function aggregationToSql(aggregation) {
33001
33233
  const field = aggregation[1];
33002
33234
  switch (operation) {
33003
33235
  case 'average':
33004
- return `AVG([${field}]) as [average(${field})]`;
33236
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
33005
33237
  case 'sum':
33006
33238
  case 'max':
33007
33239
  case 'min':
33008
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33240
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
33009
33241
  case 'count':
33010
- return 'COUNT(*) as [count()]';
33242
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
33011
33243
  }
33012
33244
  }
33013
33245
  /**
33014
33246
  * Leave arguments at null if not used
33247
+ * @param tableName
33015
33248
  * @param selected
33016
33249
  * @param filter
33017
33250
  * @param sort
@@ -33020,22 +33253,25 @@ function aggregationToSql(aggregation) {
33020
33253
  * @param groupBy
33021
33254
  * @param aggregations
33022
33255
  */
33023
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33256
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33024
33257
  let sqlSelect = 'SELECT *';
33025
- const sqlFrom = ' FROM ?';
33258
+ const sqlFrom = ` FROM ${tableName}`;
33026
33259
  let sqlOrderBy = '';
33027
33260
  let sqlWhere = '';
33028
33261
  let sqlLimit = '';
33029
33262
  let sqlGroupBy = '';
33030
33263
  if (selected !== null) {
33031
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33264
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33265
+ }
33266
+ if (geometryColumn !== null) {
33267
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33032
33268
  }
33033
33269
  if (filter !== null) {
33034
33270
  sqlWhere = ` WHERE ${filterToSql(filter)}`;
33035
33271
  }
33036
33272
  if (sort?.length) {
33037
33273
  sqlOrderBy = ` ORDER BY ${sort
33038
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33274
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33039
33275
  .join(', ')}`;
33040
33276
  }
33041
33277
  if (startIndex !== null && count !== null) {
@@ -33045,17 +33281,212 @@ function generateSqlQuery(selected = null, filter = null, sort = null, startInde
33045
33281
  sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33046
33282
  const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33047
33283
  const sqlGroupByFields = groupedByDistinct
33048
- .map((group) => `[${group[1]}]`)
33284
+ .map((group) => fieldToSql(group[1]))
33049
33285
  .join(', ');
33050
33286
  const sqlGroupBySelect = groupedByDistinct
33051
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33287
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33052
33288
  .join(', ');
33053
33289
  if (sqlGroupByFields && sqlGroupBySelect) {
33054
33290
  sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33055
33291
  sqlSelect += `, ${sqlGroupBySelect}`;
33056
33292
  }
33057
33293
  }
33058
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33294
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33295
+ }
33296
+
33297
+ function arrowTableToDataItems(table) {
33298
+ const fields = table.schema.fields;
33299
+ return table.toArray().map((row) => {
33300
+ const rowJson = row.toJSON();
33301
+ const feature = {
33302
+ type: 'Feature',
33303
+ geometry: null,
33304
+ properties: {},
33305
+ };
33306
+ const keys = Object.keys(rowJson);
33307
+ for (let i = 0; i < keys.length; i++) {
33308
+ const key = keys[i];
33309
+ const dataType = fields[i].type;
33310
+ let value = rowJson[key];
33311
+ // this might happen if we get an array inside a field
33312
+ if (value instanceof Vector) {
33313
+ value = Array.from(value);
33314
+ }
33315
+ // cast bigints to ints
33316
+ if (typeof value === 'bigint') {
33317
+ value = Number(value);
33318
+ }
33319
+ // rename columns with empty name
33320
+ if (!key) {
33321
+ feature.properties['unknown'] = value;
33322
+ continue;
33323
+ }
33324
+ // assign properties that look like an id to the geojson `id` field
33325
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33326
+ (typeof value == 'string' || typeof value === 'number')) {
33327
+ feature.id = value;
33328
+ }
33329
+ // if a date is in timestamp (number) format, convert it to native
33330
+ if (typeof value === 'number' &&
33331
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33332
+ value = new Date(value);
33333
+ }
33334
+ // if a binary field (most likely a geometry column): skip
33335
+ if (DataType.isBinary(dataType)) {
33336
+ continue;
33337
+ }
33338
+ // geometry column
33339
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33340
+ feature.geometry = JSON.parse(value);
33341
+ continue;
33342
+ }
33343
+ feature.properties[key] = value;
33344
+ }
33345
+ return feature;
33346
+ });
33347
+ }
33348
+
33349
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33350
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33351
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33352
+ const typesMapping = {
33353
+ INTEGER: 'number',
33354
+ SMALLINT: 'number',
33355
+ TINYINT: 'number',
33356
+ BIGINT: 'number',
33357
+ HUGEINT: 'number',
33358
+ UINTEGER: 'number',
33359
+ USMALLINT: 'number',
33360
+ UTINYINT: 'number',
33361
+ UBIGINT: 'number',
33362
+ UHUGEINT: 'number',
33363
+ DOUBLE: 'number',
33364
+ BIGNUM: 'number',
33365
+ DECIMAL: 'number',
33366
+ NUMERIC: 'number',
33367
+ FLOAT: 'number',
33368
+ REAL: 'number',
33369
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33370
+ TIMESTAMP: 'date',
33371
+ DATE: 'date',
33372
+ CHAR: 'string',
33373
+ VARCHAR: 'string',
33374
+ TEXT: 'string',
33375
+ UUID: 'string',
33376
+ BLOB: 'string',
33377
+ BIT: 'string',
33378
+ INTERVAL: 'string',
33379
+ LIST: 'string',
33380
+ BOOLEAN: 'boolean',
33381
+ };
33382
+ class Engine {
33383
+ async makeInit() {
33384
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33385
+ let worker_url = bundle.mainWorker;
33386
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33387
+ if (worker_url.startsWith('https://')) {
33388
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33389
+ type: 'text/javascript',
33390
+ }));
33391
+ }
33392
+ const worker = new Worker(worker_url);
33393
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33394
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33395
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33396
+ // setup duckdb options
33397
+ const conn = await this.db.connect();
33398
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33399
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33400
+ await conn.query(`SET TimeZone = '${timezone}';`);
33401
+ await conn.query(`SET threads = 1;`);
33402
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33403
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33404
+ conn.close();
33405
+ return this;
33406
+ }
33407
+ isReady() {
33408
+ if (!this.init_) {
33409
+ this.init_ = this.makeInit();
33410
+ }
33411
+ return this.init_;
33412
+ }
33413
+ /**
33414
+ * Returns information about a dataset once it's loaded:
33415
+ * - a list of properties description
33416
+ * - the name of the dataset geometry column (null if no geometry present)
33417
+ * @param datasetId name of the table under which the dataset will be stored
33418
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33419
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33420
+ */
33421
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33422
+ const conn = await this.db.connect();
33423
+ let results;
33424
+ // either we want to recreate the table, or we keep it if it already exists
33425
+ const safeLoadQuery = forceReload
33426
+ ? `DROP TABLE IF EXISTS ${datasetId};
33427
+ ${loadQuery}`
33428
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33429
+ // create the table
33430
+ try {
33431
+ results = await conn.query(safeLoadQuery);
33432
+ }
33433
+ catch (e) {
33434
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33435
+ }
33436
+ // read rows count
33437
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33438
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33439
+ // read columns
33440
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33441
+ let geometryColumn = null;
33442
+ const properties = results
33443
+ .toArray()
33444
+ .map((row) => {
33445
+ const rowObj = row.toJSON();
33446
+ if (rowObj['data_type'] === 'GEOMETRY') {
33447
+ // the geometry is not part of the properties
33448
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33449
+ // all get discarded
33450
+ geometryColumn = rowObj['column_name'];
33451
+ return null;
33452
+ }
33453
+ return {
33454
+ name: rowObj['column_name'],
33455
+ label: rowObj['column_name'],
33456
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33457
+ };
33458
+ })
33459
+ .filter((prop) => prop !== null);
33460
+ conn.close();
33461
+ return {
33462
+ properties,
33463
+ geometryColumn,
33464
+ rowsCount: Number(recordsCount),
33465
+ };
33466
+ }
33467
+ // register a Uint8 buffer using a handle in the duckdb instance
33468
+ async registerData(name, buffer) {
33469
+ return this.db.registerFileBuffer(name, buffer);
33470
+ }
33471
+ /**
33472
+ * @param query duckdb-specific query for fetching items
33473
+ */
33474
+ async queryItems(query) {
33475
+ const conn = await this.db.connect();
33476
+ const results = await conn.query(query);
33477
+ conn.close();
33478
+ return arrowTableToDataItems(results);
33479
+ }
33480
+ close() {
33481
+ this.db?.terminate();
33482
+ }
33483
+ }
33484
+ let engine = null;
33485
+ async function getEngine() {
33486
+ if (!engine) {
33487
+ engine = new Engine();
33488
+ }
33489
+ return engine.isReady();
33059
33490
  }
33060
33491
 
33061
33492
  class BaseReader {
@@ -33068,10 +33499,18 @@ class BaseReader {
33068
33499
  this.sort = null;
33069
33500
  this.startIndex = null;
33070
33501
  this.count = null;
33502
+ this.loadPromise_ = Promise.resolve();
33503
+ this.cacheEnabled = false;
33504
+ }
33505
+ enableCache(enabled) {
33506
+ this.cacheEnabled = enabled;
33071
33507
  }
33072
33508
  load() {
33073
33509
  throw new Error('not implemented');
33074
33510
  }
33511
+ get isLoaded() {
33512
+ return this.loadPromise_;
33513
+ }
33075
33514
  get properties() {
33076
33515
  throw new Error('not implemented');
33077
33516
  }
@@ -33120,178 +33559,237 @@ class BaseReader {
33120
33559
  }
33121
33560
  }
33122
33561
 
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() {
33562
+ /**
33563
+ * This reader handles file formats supported natively by DuckDB
33564
+ */
33565
+ class BaseFileReader extends BaseReader {
33566
+ // a table id should not exceed 63 chars
33567
+ generateDatasetId() {
33568
+ // generate a hash out of the url
33569
+ let hash = 0;
33570
+ for (const char of this.url) {
33571
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33572
+ hash = hash >>> 0; // make it unsigned
33573
+ }
33574
+ return `datafetcher_${hash.toString(16)}`;
33575
+ }
33576
+ async getLoadQuery() {
33136
33577
  throw new Error('not implemented');
33137
33578
  }
33138
- load() {
33139
- this.parseResult_ = this.getData();
33579
+ async load() {
33580
+ this.datasetId = this.generateDatasetId();
33581
+ this.loadPromise_ = getEngine()
33582
+ .then((engine) => {
33583
+ this.engine = engine;
33584
+ return this.getLoadQuery();
33585
+ })
33586
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33587
+ .then((datasetInfo) => {
33588
+ this.properties_ = datasetInfo.properties;
33589
+ this.geometryColumn = datasetInfo.geometryColumn;
33590
+ this.rowsCount = datasetInfo.rowsCount;
33591
+ // returns void for the loaded promise
33592
+ });
33140
33593
  }
33141
33594
  get properties() {
33142
- return this.parseResult_.then((result) => result.properties);
33595
+ return this.isLoaded.then(() => this.properties_);
33143
33596
  }
33144
33597
  get info() {
33145
- return this.parseResult_.then((result) => ({
33146
- itemsCount: result.items.length,
33598
+ return this.isLoaded.then(() => ({
33599
+ itemsCount: this.rowsCount,
33600
+ hasGeometry: !!this.geometryColumn,
33147
33601
  }));
33148
33602
  }
33149
33603
  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;
33178
- }
33179
- catch (e) {
33180
- throw new Error('CSV parsing failed: the delimiter could not be guessed');
33604
+ await this.isLoaded;
33605
+ // if only certain fields are selected, omit the geometry
33606
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33607
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33608
+ return this.engine.queryItems(query);
33181
33609
  }
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
33610
  }
33611
+
33196
33612
  class CsvReader extends BaseFileReader {
33197
- getData() {
33198
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33613
+ async getLoadQuery() {
33614
+ // first we get a list of columns hich have a detected type of VARCHAR
33615
+ // 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
33616
+ return `
33617
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33618
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33619
+ force_not_null = getvariable('strColumns'),
33620
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33621
+ );`;
33199
33622
  }
33200
33623
  }
33201
33624
 
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
33625
  class JsonReader extends BaseFileReader {
33214
- getData() {
33215
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33626
+ async getLoadQuery() {
33627
+ return `
33628
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33629
+ maximum_object_size = 536870912 -- 500MB
33630
+ );`;
33216
33631
  }
33217
33632
  }
33218
33633
 
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
33634
  class GeojsonReader extends BaseFileReader {
33233
- getData() {
33234
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33635
+ async getLoadQuery() {
33636
+ return `
33637
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33638
+ allowed_drivers = ['GeoJSON']
33639
+ );`;
33235
33640
  }
33236
33641
  }
33237
33642
 
33643
+ async function inferDatasetType(url, typeHint) {
33644
+ const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
33645
+ const fileExtension = fileExtensionMatches && fileExtensionMatches.length
33646
+ ? fileExtensionMatches[1].toLowerCase()
33647
+ : null;
33648
+ // 1. type hint
33649
+ if (typeHint)
33650
+ return Promise.resolve(typeHint);
33651
+ // 2. content-type header
33652
+ const headers = await fetchHeaders(url);
33653
+ if ('supportedType' in headers)
33654
+ return headers.supportedType;
33655
+ // 3. file extension from url
33656
+ else if (SupportedTypes.indexOf(fileExtension) > -1)
33657
+ return fileExtension;
33658
+ // no type inferred or hinted
33659
+ if ('mimeType' in headers)
33660
+ throw FetchError.unsupportedType(headers.mimeType);
33661
+ else
33662
+ throw FetchError.unknownType();
33663
+ }
33664
+ function fetchHeaders(url) {
33665
+ return sharedFetch(url, 'HEAD')
33666
+ .catch((error) => {
33667
+ throw FetchError.corsOrNetwork(error.message);
33668
+ })
33669
+ .then((response) => {
33670
+ if (!response.ok) {
33671
+ throw FetchError.http(response.status);
33672
+ }
33673
+ return parseHeaders(response.headers);
33674
+ });
33675
+ }
33676
+ function fetchDataAsText(url, cacheActive) {
33677
+ const fetchFactory = () => sharedFetch(url)
33678
+ .catch((error) => {
33679
+ throw FetchError.corsOrNetwork(error.message);
33680
+ })
33681
+ .then(async (response) => {
33682
+ if (!response.ok) {
33683
+ const clonedResponse = response.clone();
33684
+ throw FetchError.http(response.status, await clonedResponse.text());
33685
+ }
33686
+ const clonedResponse = response.clone();
33687
+ return clonedResponse.text();
33688
+ });
33689
+ return cacheActive ? useCache(fetchFactory, url, 'asText') : fetchFactory();
33690
+ }
33691
+ function fetchDataAsArrayBuffer(url, cacheActive) {
33692
+ const fetchFactory = () => sharedFetch(url)
33693
+ .catch((error) => {
33694
+ throw FetchError.corsOrNetwork(error.message);
33695
+ })
33696
+ .then(async (response) => {
33697
+ if (!response.ok) {
33698
+ throw FetchError.http(response.status, await response.text());
33699
+ }
33700
+ // convert to a numeric array so that we can store the response in cache
33701
+ return Array.from(new Uint8Array(await response.arrayBuffer()));
33702
+ });
33703
+ return (cacheActive ? useCache(fetchFactory, url, 'asArrayBuffer') : fetchFactory()).then((array) => {
33704
+ return new Uint8Array(array).buffer;
33705
+ });
33706
+ }
33238
33707
  /**
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
33708
+ * This creates a Proxy that allows reading and writing to the data item properties
33709
+ * as if it was a simple array of JSON objects
33710
+ * @param items
33242
33711
  */
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);
33712
+ function getJsonDataItemsProxy(items) {
33713
+ return new Proxy(items, {
33714
+ get(target, p) {
33715
+ if (typeof p === 'string' &&
33716
+ !Number.isNaN(parseInt(p)) &&
33717
+ target[p]?.properties) {
33718
+ return target[p].properties;
33719
+ }
33720
+ return target[p];
33721
+ },
33722
+ set() {
33723
+ throw new Error('This object is read-only');
33724
+ },
33252
33725
  });
33253
33726
  }
33727
+
33254
33728
  class ExcelReader extends BaseFileReader {
33255
- getData() {
33256
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33729
+ async getLoadQuery() {
33730
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33731
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33732
+ const bufferHandle = `B${this.datasetId}`;
33733
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33734
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33735
+ const isXls = Array.from(magicNumber)
33736
+ .map((n) => n.toString(16).toUpperCase())
33737
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33738
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33739
+ if (isXls) {
33740
+ buffer = await import('xlsx').then(({ read, utils }) => {
33741
+ const workbook = read(buffer);
33742
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33743
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33744
+ });
33745
+ }
33746
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33747
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33748
+ return `
33749
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33750
+ }
33751
+ }
33752
+
33753
+ class GmlReader extends BaseFileReader {
33754
+ async getLoadQuery() {
33755
+ return `
33756
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33757
+ allowed_drivers = ['GML']
33758
+ );`;
33257
33759
  }
33258
33760
  }
33259
33761
 
33260
- function parseGml(text, namespace, version) {
33261
- const splittedNamespace = namespace.split(':');
33262
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33762
+ const formatGeojson = new GeoJSON$1();
33763
+ function parseGeojson(text) {
33764
+ const parsed = JSON.parse(text);
33765
+ const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33766
+ if (!Array.isArray(features)) {
33767
+ throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33768
+ }
33769
+ return features;
33770
+ }
33771
+ function parseGml(text, featureType, version) {
33772
+ const parts = featureType.split(':');
33773
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33263
33774
  const match = regex.exec(text);
33264
33775
  if (match && match.length >= 2) {
33265
- const wf = new WFS({
33776
+ const wfs = new WFS({
33266
33777
  featureNS: match[1],
33267
- featureType: splittedNamespace[1],
33778
+ featureType: parts[1],
33268
33779
  version: version,
33269
33780
  });
33270
33781
  let features;
33271
33782
  try {
33272
- features = wf.readFeatures(text);
33783
+ features = wfs.readFeatures(text);
33273
33784
  }
33274
33785
  catch (e) {
33275
- throw Error("Couldn't parse WFS with GML features");
33786
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33276
33787
  }
33277
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33278
- return processItemProperties(geojsonItem.features, true);
33788
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33789
+ return geojsonItem.features;
33279
33790
  }
33280
33791
  throw Error("Couldn't retrieve namespace url");
33281
33792
  }
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
33793
  async function getWfsEndpoint(wfsUrl) {
33296
33794
  try {
33297
33795
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33319,21 +33817,65 @@ async function getWfsEndpoint(wfsUrl) {
33319
33817
  }
33320
33818
  }
33321
33819
  }
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();
33820
+ class WfsReader extends BaseReader {
33821
+ constructor(url, featureTypeName) {
33822
+ super(url);
33823
+ this.endpoint = getWfsEndpoint(url);
33824
+ this.featureType = this.endpoint
33825
+ .then((endpoint) => {
33826
+ const featureTypes = endpoint.getFeatureTypes();
33827
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33828
+ ? featureTypes[0].name
33829
+ : featureTypeName);
33830
+ })
33831
+ .then((featureType) => {
33832
+ if (!featureType) {
33833
+ throw new Error('wfs.featuretype.notfound');
33834
+ }
33835
+ return featureType;
33836
+ });
33837
+ }
33838
+ get backupReader() {
33839
+ if (this.backupReader_) {
33840
+ return this.backupReader_;
33841
+ }
33842
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33843
+ let reader;
33844
+ if (endpoint.supportsJson(featureType.name)) {
33845
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33846
+ asJson: true,
33847
+ outputCrs: 'EPSG:4326',
33848
+ }));
33849
+ }
33850
+ else {
33851
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33852
+ (featureType.defaultCrs === 'EPSG:4326' ||
33853
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33854
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33855
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33856
+ outputCrs: 'EPSG:4326',
33857
+ }));
33858
+ }
33859
+ }
33860
+ reader.enableCache(this.cacheEnabled);
33861
+ reader.load();
33862
+ return reader;
33863
+ });
33864
+ return this.backupReader_;
33328
33865
  }
33329
33866
  get properties() {
33330
- return this.endpoint
33331
- .getFeatureTypeFull(this.featureTypeName)
33332
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33867
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33333
33868
  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
33869
+ let type;
33870
+ if (originalType === 'float' || originalType === 'integer') {
33871
+ type = 'number';
33872
+ }
33873
+ else if (originalType === 'boolean') {
33874
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33875
+ }
33876
+ else {
33877
+ type = originalType;
33878
+ }
33337
33879
  return {
33338
33880
  name: prop,
33339
33881
  label: prop,
@@ -33342,83 +33884,63 @@ class WfsReader extends BaseCacheReader {
33342
33884
  }));
33343
33885
  }
33344
33886
  get info() {
33345
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33887
+ return this.featureType.then((result) => ({
33346
33888
  itemsCount: result.objectCount,
33889
+ hasGeometry: !!result.geometryName,
33347
33890
  }));
33348
33891
  }
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
- }
33892
+ load() {
33893
+ // Nothing to load for Wfs
33378
33894
  }
33379
- async getData(aggregation, groupedBy) {
33380
- if (aggregation || groupedBy) {
33381
- return { items: await this.getQueryData() };
33895
+ async read() {
33896
+ const endpoint = await this.endpoint;
33897
+ const featureType = await this.featureType;
33898
+ // if we can't use the WFS protocol we fall back to the backup reader
33899
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33900
+ const backupReader = await this.backupReader;
33901
+ backupReader.selectAll();
33902
+ if (this.selected) {
33903
+ backupReader.select(...this.selected);
33904
+ }
33905
+ if (this.aggregations) {
33906
+ backupReader.aggregate(...this.aggregations);
33907
+ }
33908
+ if (this.groupedBy) {
33909
+ backupReader.groupBy(...this.groupedBy);
33910
+ }
33911
+ if (this.sort) {
33912
+ backupReader.orderBy(...this.sort);
33913
+ }
33914
+ if (this.startIndex !== null && this.count !== null) {
33915
+ backupReader.limit(this.startIndex, this.count);
33916
+ }
33917
+ return backupReader.read();
33382
33918
  }
33383
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33919
+ const asJson = endpoint.supportsJson(featureType.name);
33384
33920
  const attributes = this.selected ?? undefined;
33385
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33921
+ let sortBy = null;
33922
+ if (this.sort) {
33923
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33924
+ sortBy = Array.isArray(this.sort[0])
33925
+ ? this.sort.map(mapSort)
33926
+ : mapSort(this.sort);
33927
+ }
33928
+ const url = endpoint.getFeatureUrl(featureType.name, {
33386
33929
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33387
33930
  ...(this.count !== null && { maxFeatures: this.count }),
33388
33931
  asJson,
33389
33932
  outputCrs: 'EPSG:4326',
33390
33933
  attributes,
33391
- // sortBy: this.sort // TODO: no sort in ogc-client?
33934
+ sortBy,
33392
33935
  });
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
33936
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33402
33937
  ? 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;
33938
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33417
33939
  }
33418
33940
  }
33419
33941
 
33420
- async function openDataset(url, typeHint, options, cacheActive) {
33421
- const fileType = await inferDatasetType(url, typeHint);
33942
+ async function openDataset(url, options) {
33943
+ const fileType = await inferDatasetType(url, options?.typeHint);
33422
33944
  let reader;
33423
33945
  try {
33424
33946
  switch (fileType) {
@@ -33435,18 +33957,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33435
33957
  reader = new ExcelReader(url);
33436
33958
  break;
33437
33959
  case 'gml':
33438
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33960
+ reader = new GmlReader(url);
33439
33961
  break;
33440
33962
  case 'wfs':
33441
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33963
+ reader = new WfsReader(url, options?.wfsFeatureType);
33442
33964
  break;
33443
33965
  }
33444
- reader.setCacheActive(cacheActive);
33966
+ reader.enableCache(options?.enableCache ?? true);
33445
33967
  reader.load();
33446
33968
  return reader;
33447
33969
  }
33448
33970
  catch (e) {
33449
- //WfsReader may already raise a FetchError
33971
+ // WfsReader may already raise a FetchError
33450
33972
  if (e instanceof FetchError)
33451
33973
  throw e;
33452
33974
  else
@@ -33457,13 +33979,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33457
33979
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33458
33980
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33459
33981
  * 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:
33982
+ * File type is determined like so:
33461
33983
  * 1. if a type hint is given, use it
33462
33984
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33463
33985
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33464
33986
  */
33465
- async function readDataset(url, typeHint, options, cacheActive = true) {
33466
- const reader = await openDataset(url, typeHint, options, cacheActive);
33987
+ async function readDataset(url, options) {
33988
+ const reader = await openDataset(url, options);
33467
33989
  try {
33468
33990
  return await reader.read();
33469
33991
  }
@@ -33694,9 +34216,11 @@ class DataService {
33694
34216
  getDataset(link, cacheActive) {
33695
34217
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33696
34218
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33697
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34219
+ return from(openDataset(wfsUrlEndpoint, {
34220
+ typeHint: 'wfs',
33698
34221
  wfsFeatureType: link.name,
33699
- }, cacheActive));
34222
+ enableCache: cacheActive,
34223
+ }));
33700
34224
  }
33701
34225
  else if (link.type === 'download') {
33702
34226
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33704,12 +34228,15 @@ class DataService {
33704
34228
  const supportedType = SupportedTypes.indexOf(format) > -1
33705
34229
  ? format
33706
34230
  : undefined;
33707
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34231
+ return from(openDataset(linkProxifiedUrl, {
34232
+ typeHint: supportedType,
34233
+ enableCache: cacheActive,
34234
+ })).pipe();
33708
34235
  }
33709
34236
  else if (link.type === 'service' &&
33710
34237
  link.accessServiceProtocol === 'esriRest') {
33711
34238
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33712
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34239
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33713
34240
  }
33714
34241
  else if (link.type === 'service' &&
33715
34242
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33723,7 +34250,10 @@ class DataService {
33723
34250
  }
33724
34251
  const urlWithoutLimit = new URL(geojsonUrl);
33725
34252
  urlWithoutLimit.searchParams.delete('limit');
33726
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34253
+ return openDataset(urlWithoutLimit.toString(), {
34254
+ typeHint: 'geojson',
34255
+ enableCache: cacheActive,
34256
+ });
33727
34257
  }));
33728
34258
  }
33729
34259
  return throwError(() => 'protocol not supported');
@@ -33975,20 +34505,31 @@ class DataTableComponent {
33975
34505
  this.eltRef = inject(ElementRef);
33976
34506
  this.cdr = inject(ChangeDetectorRef);
33977
34507
  this.translateService = inject(TranslateService);
33978
- this._featureAttributes = [];
34508
+ this.columnsFromFeatureCatalog = null;
34509
+ this.columnsFromDataset = [];
33979
34510
  this.selected = new EventEmitter();
33980
- this.properties$ = new BehaviorSubject(null);
33981
34511
  this.loading$ = new BehaviorSubject(false);
33982
34512
  this.error = null;
33983
34513
  }
33984
34514
  set featureAttributes(value) {
33985
- this._featureAttributes = value;
33986
- this.properties$.next(value.map((attr) => attr.value));
34515
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34516
+ name: attrs.value,
34517
+ label: attrs.label,
34518
+ }));
33987
34519
  }
33988
34520
  set dataset(value) {
33989
34521
  this.dataset_ = value;
33990
34522
  this.dataset_.load();
33991
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34523
+ this.dataset_.info.then((info) => {
34524
+ this.count = info.itemsCount;
34525
+ this.cdr.detectChanges();
34526
+ });
34527
+ }
34528
+ get columns() {
34529
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34530
+ }
34531
+ get columnNames() {
34532
+ return this.columns.map((c) => c.name);
33992
34533
  }
33993
34534
  ngOnInit() {
33994
34535
  this.dataSource = new DataTableDataSource();
@@ -34023,11 +34564,12 @@ class DataTableComponent {
34023
34564
  }
34024
34565
  async readData() {
34025
34566
  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
34567
  try {
34568
+ // wait for properties to be read
34569
+ if (!this.columnsFromFeatureCatalog) {
34570
+ this.columnsFromDataset = await this.dataset_.properties;
34571
+ }
34572
+ this.dataset_.select(...this.columnNames);
34031
34573
  await this.dataSource.showData(this.dataset_.read());
34032
34574
  this.error = null;
34033
34575
  }
@@ -34057,7 +34599,7 @@ class DataTableComponent {
34057
34599
  }
34058
34600
  }
34059
34601
  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 }); }
34602
+ 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
34603
  }
34062
34604
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34063
34605
  type: Component,
@@ -34069,10 +34611,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34069
34611
  CommonModule,
34070
34612
  LoadingMaskComponent,
34071
34613
  PopupAlertComponent,
34072
- LetDirective,
34073
34614
  TranslatePipe,
34074
34615
  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"] }]
34616
+ ], 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
34617
  }], propDecorators: { featureAttributes: [{
34077
34618
  type: Input
34078
34619
  }], dataset: [{
@@ -34175,7 +34716,7 @@ class ChartViewComponent {
34175
34716
  }), shareReplay$1(1));
34176
34717
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34177
34718
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34178
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34719
+ .filter((prop) => prop.type === 'number')
34179
34720
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34180
34721
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34181
34722
  const newProp = choices[0]?.value || '';
@@ -34202,6 +34743,7 @@ class ChartViewComponent {
34202
34743
  this.aggregation$,
34203
34744
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34204
34745
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34746
+ this.loading = true;
34205
34747
  return dataset
34206
34748
  .groupBy(['distinct', xProp])
34207
34749
  .aggregate(fieldAgg)
@@ -34443,6 +34985,7 @@ class GeoTableViewComponent {
34443
34985
  }));
34444
34986
  }
34445
34987
  async initMapContext() {
34988
+ this.dataset.load();
34446
34989
  this.dataset.selectAll();
34447
34990
  return {
34448
34991
  layers: [
@@ -34616,8 +35159,7 @@ class MdViewFacade {
34616
35159
  }));
34617
35160
  this.error$ = this.store.pipe(select(getMetadataError));
34618
35161
  this.related$ = this.store.pipe(select(getRelated));
34619
- this.sources$ = this.store.pipe(select(getSources));
34620
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35162
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34621
35163
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34622
35164
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34623
35165
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34736,12 +35278,9 @@ class MdViewEffects {
34736
35278
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34737
35279
  return setRelated({ related });
34738
35280
  }), 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 })))));
35281
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35282
+ return setLinkedRecords({ linkedRecords });
35283
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34745
35284
  /*
34746
35285
  UserFeedback effects
34747
35286
  */
@@ -40020,7 +40559,7 @@ function flattenQueryParams(params) {
40020
40559
  const start = flattened[key].start;
40021
40560
  const end = flattened[key].end;
40022
40561
  flattened[key] = [
40023
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40562
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40024
40563
  ];
40025
40564
  }
40026
40565
  }
@@ -40377,5 +40916,5 @@ const CHART_TYPE_VALUES = [
40377
40916
  * Generated bundle index. Do not edit.
40378
40917
  */
40379
40918
 
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 };
40919
+ 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
40920
  //# sourceMappingURL=geonetwork-ui.mjs.map