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

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 (75) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1248 -747
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +195 -76
  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 +1 -0
  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/repository/records-repository.interface.ts +2 -2
  11. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  12. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  13. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  14. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  15. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  16. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  17. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  18. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  19. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  20. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  21. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  22. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  23. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  24. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  25. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  26. package/src/libs/feature/search/src/lib/utils/service/fields.ts +16 -1
  27. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  28. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  29. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  30. package/src/libs/ui/inputs/src/index.ts +2 -0
  31. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  32. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  33. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  34. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  35. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  36. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  37. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  38. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  39. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  40. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  41. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  42. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  43. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  44. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -5
  45. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  46. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  47. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  48. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  49. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  50. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  51. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  52. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  53. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  54. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  55. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  56. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  57. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  58. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  59. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  60. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  61. package/src/libs/util/shared/src/index.ts +1 -0
  62. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  63. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  64. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  65. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  66. package/tailwind.base.css +5 -0
  67. package/translations/de.json +13 -2
  68. package/translations/en.json +12 -1
  69. package/translations/es.json +11 -0
  70. package/translations/fr.json +13 -2
  71. package/translations/it.json +11 -0
  72. package/translations/nl.json +11 -0
  73. package/translations/pt.json +11 -0
  74. package/translations/sk.json +11 -0
  75. 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]}` : '';
@@ -20342,6 +20376,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20342
20376
  }]
20343
20377
  }] });
20344
20378
 
20379
+ class AutofocusDirective {
20380
+ constructor() {
20381
+ this.el = inject(ElementRef);
20382
+ this.injector = inject(Injector);
20383
+ }
20384
+ set autofocus(active) {
20385
+ if (!active)
20386
+ return;
20387
+ afterNextRender(() => this.el.nativeElement.focus(), {
20388
+ injector: this.injector,
20389
+ });
20390
+ }
20391
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
20392
+ 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 }); }
20393
+ }
20394
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AutofocusDirective, decorators: [{
20395
+ type: Directive,
20396
+ args: [{
20397
+ selector: '[gnUiAutofocus]',
20398
+ standalone: true,
20399
+ }]
20400
+ }], propDecorators: { autofocus: [{
20401
+ type: Input,
20402
+ args: [{ alias: 'gnUiAutofocus', transform: booleanAttribute }]
20403
+ }] } });
20404
+
20345
20405
  class ImageFallbackDirective {
20346
20406
  constructor() {
20347
20407
  this.el = inject(ElementRef);
@@ -20369,7 +20429,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
20369
20429
  }] } });
20370
20430
 
20371
20431
  var name = "geonetwork-ui";
20372
- var version = "2.11.0-dev.a83f0f51a";
20432
+ var version = "2.11.0-dev.aed5b2e5b";
20373
20433
  var engines = {
20374
20434
  node: ">=24"
20375
20435
  };
@@ -20399,20 +20459,21 @@ var peerDependencies = {
20399
20459
  "@ngrx/store": "19.x || 20.x || 21.x",
20400
20460
  "@ngrx/store-devtools": "19.x || 20.x || 21.x",
20401
20461
  "@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",
20462
+ "@ngx-translate/core": "17.x",
20463
+ "@ngx-translate/http-loader": "17.x",
20464
+ "flag-icons": "~7.5.0",
20405
20465
  rxjs: "7.x",
20406
20466
  "zone.js": "*",
20407
20467
  tailwindcss: "3.x"
20408
20468
  };
20409
20469
  var dependencies = {
20410
20470
  "@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",
20471
+ "@camptocamp/ogc-client": "1.3.1-dev.200c02a",
20472
+ "@duckdb/duckdb-wasm": "~1.32.0",
20473
+ "@geospatial-sdk/core": "0.0.5-dev.78",
20474
+ "@geospatial-sdk/geocoding": "0.0.5-dev.78",
20475
+ "@geospatial-sdk/legend": "0.0.5-dev.78",
20476
+ "@geospatial-sdk/openlayers": "0.0.5-dev.78",
20416
20477
  "@ltd/j-toml": "~1.38.0",
20417
20478
  "@messageformat/core": "~3.4.0",
20418
20479
  "@ng-icons/core": "~32.5.0",
@@ -20420,7 +20481,7 @@ var dependencies = {
20420
20481
  "@ng-icons/material-icons": "~32.5.0",
20421
20482
  "@ng-icons/tabler-icons": "~32.4.0",
20422
20483
  "@rgrove/parse-xml": "4.2.0",
20423
- alasql: "~4.17.0",
20484
+ "apache-arrow": "~17.0.0",
20424
20485
  "chart.js": "~4.5.1",
20425
20486
  "chroma-js": "~3.2.0",
20426
20487
  "date-fns": "4.1.0",
@@ -20433,7 +20494,6 @@ var dependencies = {
20433
20494
  "ngx-dropzone": "~3.1.0",
20434
20495
  "ngx-translate-messageformat-compiler": "~7.2.0",
20435
20496
  ol: "~10.8.0",
20436
- papaparse: "~5.5.3",
20437
20497
  proj4: "~2.20.4",
20438
20498
  rdflib: "~2.3.5",
20439
20499
  semver: "~7.7.4",
@@ -20678,6 +20738,7 @@ class ElasticsearchService {
20678
20738
  values: uuids,
20679
20739
  },
20680
20740
  },
20741
+ size: uuids.length,
20681
20742
  };
20682
20743
  }
20683
20744
  getRelatedRecordPayload(record, size = 6, _source = [...ES_SOURCE_SUMMARY, 'createDate']) {
@@ -21229,7 +21290,7 @@ class Gn4Repository {
21229
21290
  }
21230
21291
  getRecord(uniqueIdentifier) {
21231
21292
  return this.gn4SearchApi
21232
- .search('bucket', ['fcats', 'hassources'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21293
+ .search('bucket', ['fcats', 'hassources', 'siblings', 'associated'], JSON.stringify(this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])))
21233
21294
  .pipe(map$1((results) => results.hits.hits[0]), switchMap((record) => record ? this.gn4Mapper.readRecord(record) : of(null)));
21234
21295
  }
21235
21296
  getMultipleRecords(uniqueIdentifiers) {
@@ -21283,19 +21344,28 @@ class Gn4Repository {
21283
21344
  .search('bucket', null, JSON.stringify(this.gn4SearchHelper.getRelatedRecordPayload(similarTo, 3)))
21284
21345
  .pipe(switchMap((results) => this.gn4Mapper.readRecords(results.hits.hits)));
21285
21346
  }
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);
21347
+ getLinkedRecords(record) {
21348
+ const siblings = (record.extras?.['siblings'] ?? []);
21349
+ const relations = [
21350
+ ['source', (record.extras?.['sourcesIdentifiers'] ?? [])],
21351
+ ['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? [])],
21352
+ ['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
21353
+ [
21354
+ 'associated',
21355
+ (record.extras?.['associatedIdentifiers'] ?? []),
21356
+ ],
21357
+ ];
21358
+ const requested = relations.filter(([, identifiers]) => identifiers.length > 0);
21359
+ if (requested.length === 0) {
21360
+ return of([]);
21297
21361
  }
21298
- return of(null);
21362
+ return forkJoin(requested.map(([relation, identifiers]) => this.getMultipleRecords(identifiers).pipe(map$1((records) => (records ?? []).map((record) => ({
21363
+ record,
21364
+ relation,
21365
+ associationType: relation === 'sibling'
21366
+ ? siblings.find(({ uniqueIdentifier }) => uniqueIdentifier === record.uniqueIdentifier)?.associationType
21367
+ : undefined,
21368
+ }))), catchError(() => of([]))))).pipe(map$1((groups) => groups.flat()));
21299
21369
  }
21300
21370
  aggregate(params) {
21301
21371
  // if aggregations are empty, return an empty object right away
@@ -22556,9 +22626,8 @@ const VECTOR_STYLE_DEFAULT = new InjectionToken('vectorStyleDefault', {
22556
22626
  });
22557
22627
 
22558
22628
  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>`,
22629
+ type: 'maplibre-style',
22630
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
22562
22631
  };
22563
22632
  const DEFAULT_VIEW = {
22564
22633
  center: [0, 15],
@@ -23273,6 +23342,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23273
23342
  'search_preset',
23274
23343
  'advanced_filters',
23275
23344
  'limit',
23345
+ 'spatial_extent_max_file_size',
23276
23346
  ], warnings, errors);
23277
23347
  const parsedSearchParams = parseMultiConfigSection(parsed, 'search_preset', ['name'], ['sort', 'filters'], warnings, errors);
23278
23348
  searchConfig =
@@ -23290,6 +23360,7 @@ function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
23290
23360
  })),
23291
23361
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
23292
23362
  LIMIT: parsedSearchSection.limit,
23363
+ SPATIAL_EXTENT_MAX_FILE_SIZE: parsedSearchSection.spatial_extent_max_file_size,
23293
23364
  };
23294
23365
  const parsedMetadataQualitySection = parseConfigSection(parsed, 'metadata-quality', [], ['enabled'], warnings, errors);
23295
23366
  metadataQualityConfig =
@@ -24547,12 +24618,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24547
24618
  type: Input
24548
24619
  }] } });
24549
24620
 
24621
+ function provideLocalizedDateAdapter() {
24622
+ return [
24623
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
24624
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
24625
+ {
24626
+ provide: DateAdapter,
24627
+ useFactory: () => {
24628
+ const dateService = inject(DateService);
24629
+ const adapter = new DateFnsAdapter();
24630
+ dateService
24631
+ .getDateFnsLocale()
24632
+ .then((locale) => adapter.setLocale(locale));
24633
+ return adapter;
24634
+ },
24635
+ },
24636
+ ];
24637
+ }
24638
+
24550
24639
  class DatePickerComponent {
24551
24640
  constructor() {
24552
- this.dateAdapter = inject(DateAdapter);
24553
- this.translate = inject(TranslateService);
24554
24641
  this.dateChange = new EventEmitter();
24555
- this.dateAdapter.setLocale(this.translate.getCurrentLang());
24556
24642
  }
24557
24643
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
24558
24644
  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 +24646,19 @@ class DatePickerComponent {
24560
24646
  provideNgIconsConfig({
24561
24647
  size: '1.5rem',
24562
24648
  }),
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 }); }
24649
+ provideLocalizedDateAdapter(),
24650
+ ], 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
24651
  }
24569
24652
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DatePickerComponent, decorators: [{
24570
24653
  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: [
24654
+ args: [{ selector: 'gn-ui-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24577
24655
  provideIcons({ iconoirCalendar }),
24578
24656
  provideNgIconsConfig({
24579
24657
  size: '1.5rem',
24580
24658
  }),
24581
- {
24582
- provide: MAT_DATE_LOCALE,
24583
- useFactory: (locale) => locale,
24584
- },
24659
+ provideLocalizedDateAdapter(),
24585
24660
  ], 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: [{
24661
+ }], propDecorators: { date: [{
24587
24662
  type: Input
24588
24663
  }], dateChange: [{
24589
24664
  type: Output
@@ -24591,58 +24666,159 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24591
24666
 
24592
24667
  class DateRangeDropdownComponent {
24593
24668
  constructor() {
24594
- this.overlayContainer = inject(OverlayContainer);
24669
+ this.scrollStrategies = inject(ScrollStrategyOptions);
24670
+ this.dateAdapter = inject(DateAdapter);
24671
+ this.dateFormats = inject(MAT_DATE_FORMATS);
24595
24672
  this.cdr = inject(ChangeDetectorRef);
24596
- this.startDateChange = new EventEmitter();
24597
- this.endDateChange = new EventEmitter();
24598
- this.isPickerDisplayed = false;
24673
+ this.dateRange = {};
24674
+ this.dateRangeChange = new EventEmitter();
24675
+ this.overlayPositions = [
24676
+ {
24677
+ originX: 'start',
24678
+ originY: 'bottom',
24679
+ overlayX: 'start',
24680
+ overlayY: 'top',
24681
+ offsetY: 8,
24682
+ },
24683
+ {
24684
+ originX: 'start',
24685
+ originY: 'top',
24686
+ overlayX: 'start',
24687
+ overlayY: 'bottom',
24688
+ offsetY: -8,
24689
+ },
24690
+ ];
24691
+ this.scrollStrategy = this.scrollStrategies.reposition();
24692
+ this.overlayOpen = false;
24693
+ this.expandedBound = 'start';
24694
+ this.invalidBounds = { start: false, end: false };
24695
+ // redraw when the lazily loaded UI locale reaches the adapter
24696
+ this.dateAdapter.localeChanges
24697
+ .pipe(takeUntilDestroyed())
24698
+ .subscribe(() => this.cdr.markForCheck());
24599
24699
  }
24600
- ngAfterViewChecked() {
24601
- this.checkPickerOverlay();
24700
+ get startDate() {
24701
+ return this.dateRange.start;
24602
24702
  }
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
24703
+ get endDate() {
24704
+ return this.dateRange.end;
24705
+ }
24706
+ get selectedDatesCount() {
24707
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0);
24708
+ }
24709
+ openOverlay() {
24710
+ this.expandedBound = 'start';
24711
+ this.overlayOpen = true;
24712
+ }
24713
+ closeOverlay() {
24714
+ this.overlayOpen = false;
24715
+ }
24716
+ toggleBound(bound) {
24717
+ this.expandedBound = this.expandedBound === bound ? null : bound;
24718
+ }
24719
+ formatDate(date) {
24720
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput);
24721
+ }
24722
+ onDateInput(bound, event) {
24723
+ const typedText = event.target.value.trim();
24724
+ const date = typedText
24725
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
24726
+ : null;
24727
+ if (!this.isAcceptable(bound, typedText, date)) {
24728
+ // keep the text as typed and flag it; the filter stays on its last value
24729
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true };
24730
+ return;
24731
+ }
24732
+ if (bound === 'start')
24733
+ this.setStartDate(date);
24734
+ else
24735
+ this.setEndDate(date);
24736
+ }
24737
+ normalizeDateInput(bound, event) {
24738
+ if (this.invalidBounds[bound])
24739
+ return;
24740
+ const date = bound === 'start' ? this.startDate : this.endDate;
24741
+ const input = event.target;
24742
+ input.value = date ? this.formatDate(date) : '';
24743
+ }
24744
+ isAcceptable(bound, typedText, date) {
24745
+ if (!typedText)
24746
+ return true;
24747
+ if (!date || !this.dateAdapter.isValid(date))
24748
+ return false;
24749
+ return bound === 'start'
24750
+ ? !this.endDate || date <= this.endDate
24751
+ : !this.startDate || date >= this.startDate;
24752
+ }
24753
+ setStartDate(date) {
24754
+ this.invalidBounds = { ...this.invalidBounds, start: false };
24755
+ this.applyDateRange({ ...this.dateRange, start: date });
24756
+ this.expandedBound = 'end';
24757
+ }
24758
+ setEndDate(date) {
24759
+ this.invalidBounds = { ...this.invalidBounds, end: false };
24760
+ this.applyDateRange({ ...this.dateRange, end: date });
24761
+ this.expandedBound = null;
24762
+ }
24763
+ clearDates(event) {
24764
+ this.invalidBounds = { start: false, end: false };
24765
+ this.expandedBound = 'start';
24766
+ this.applyDateRange({});
24767
+ propagateToDocumentOnly(event);
24768
+ }
24769
+ applyDateRange(dateRange) {
24770
+ this.dateRange = {
24771
+ ...(dateRange.start && { start: dateRange.start }),
24772
+ ...(dateRange.end && { end: dateRange.end }),
24773
+ };
24774
+ this.dateRangeChange.emit(this.dateRange);
24611
24775
  }
24612
24776
  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: [
24777
+ 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
24778
  provideIcons({
24615
- matExpandMore,
24779
+ iconoirCalendar,
24780
+ matClose,
24616
24781
  matExpandLess,
24782
+ matExpandMore,
24617
24783
  }),
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"] }] }); }
24784
+ provideNgIconsConfig({
24785
+ size: '1.5rem',
24786
+ }),
24787
+ provideLocalizedDateAdapter(),
24788
+ ], 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
24789
  }
24620
24790
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangeDropdownComponent, decorators: [{
24621
24791
  type: Component,
24622
24792
  args: [{ selector: 'gn-ui-date-range-dropdown', standalone: true, imports: [
24623
- NgIconComponent,
24624
- MatNativeDateModule,
24625
- MatDatepickerModule,
24793
+ AutofocusDirective,
24626
24794
  ButtonComponent,
24795
+ MatDatepickerModule,
24796
+ NgIcon,
24797
+ NgTemplateOutlet,
24798
+ OverlayModule,
24799
+ TranslateDirective,
24800
+ TranslatePipe,
24627
24801
  ], providers: [
24628
24802
  provideIcons({
24629
- matExpandMore,
24803
+ iconoirCalendar,
24804
+ matClose,
24630
24805
  matExpandLess,
24806
+ matExpandMore,
24631
24807
  }),
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: [{
24808
+ provideNgIconsConfig({
24809
+ size: '1.5rem',
24810
+ }),
24811
+ provideLocalizedDateAdapter(),
24812
+ ], 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"] }]
24813
+ }], ctorParameters: () => [], propDecorators: { title: [{
24636
24814
  type: Input
24637
- }], endDate: [{
24815
+ }], dateRange: [{
24638
24816
  type: Input
24639
- }], startDateChange: [{
24817
+ }], dateRangeChange: [{
24640
24818
  type: Output
24641
- }], endDateChange: [{
24642
- type: Output
24643
- }], picker: [{
24819
+ }], overlayOrigin: [{
24644
24820
  type: ViewChild,
24645
- args: ['picker']
24821
+ args: ['overlayOrigin']
24646
24822
  }] } });
24647
24823
 
24648
24824
  class DateRangePickerComponent {
@@ -24656,20 +24832,17 @@ class DateRangePickerComponent {
24656
24832
  provideNgIconsConfig({
24657
24833
  size: '1.5rem',
24658
24834
  }),
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 }); }
24835
+ provideLocalizedDateAdapter(),
24836
+ ], 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
24837
  }
24661
24838
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DateRangePickerComponent, decorators: [{
24662
24839
  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: [
24840
+ args: [{ selector: 'gn-ui-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MatDatepickerModule, ButtonComponent, NgIconComponent], providers: [
24669
24841
  provideIcons({ iconoirCalendar }),
24670
24842
  provideNgIconsConfig({
24671
24843
  size: '1.5rem',
24672
24844
  }),
24845
+ provideLocalizedDateAdapter(),
24673
24846
  ], 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
24847
  }], propDecorators: { startDate: [{
24675
24848
  type: Input
@@ -24687,28 +24860,68 @@ class DragAndDropFileInputComponent {
24687
24860
  constructor() {
24688
24861
  this.placeholder = placeholder;
24689
24862
  this.accept = '*';
24863
+ this.maxFileSizeMb = null;
24864
+ this.icon = null;
24865
+ this.dropzoneBackgroundColor = null;
24866
+ this.textClass = '';
24867
+ this.extraClass = '';
24868
+ this.showFileName = true;
24690
24869
  this.fileChange = new EventEmitter();
24870
+ this.errorChange = new EventEmitter();
24691
24871
  this.selectedFile = null;
24692
24872
  }
24693
24873
  get fileName() {
24694
24874
  return this.selectedFile && this.selectedFile.name;
24695
24875
  }
24876
+ get maxFileSizeBytes() {
24877
+ return typeof this.maxFileSizeMb === 'number'
24878
+ ? megabytesToBytes(this.maxFileSizeMb)
24879
+ : null;
24880
+ }
24696
24881
  selectFile(event) {
24882
+ if (event.rejectedFiles?.length) {
24883
+ const reason = event.rejectedFiles[0].reason;
24884
+ this.errorChange.emit(reason === 'size' ? 'file-too-large' : 'invalid-extension');
24885
+ return;
24886
+ }
24697
24887
  this.selectedFile = event.addedFiles[0];
24698
24888
  this.fileChange.emit(this.selectedFile);
24699
24889
  }
24890
+ openFileSelector() {
24891
+ this.dropzone.showFileSelector();
24892
+ }
24893
+ clear() {
24894
+ this.selectedFile = null;
24895
+ }
24700
24896
  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"] }] }); }
24897
+ 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
24898
  }
24703
24899
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DragAndDropFileInputComponent, decorators: [{
24704
24900
  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"] }]
24901
+ 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
24902
  }], propDecorators: { placeholder: [{
24707
24903
  type: Input
24708
24904
  }], accept: [{
24709
24905
  type: Input
24906
+ }], maxFileSizeMb: [{
24907
+ type: Input
24908
+ }], icon: [{
24909
+ type: Input
24910
+ }], dropzoneBackgroundColor: [{
24911
+ type: Input
24912
+ }], textClass: [{
24913
+ type: Input
24914
+ }], extraClass: [{
24915
+ type: Input
24916
+ }], showFileName: [{
24917
+ type: Input
24710
24918
  }], fileChange: [{
24711
24919
  type: Output
24920
+ }], errorChange: [{
24921
+ type: Output
24922
+ }], dropzone: [{
24923
+ type: ViewChild,
24924
+ args: [NgxDropzoneComponent]
24712
24925
  }] } });
24713
24926
 
24714
24927
  class DropdownMultiselectComponent {
@@ -24863,7 +25076,7 @@ class DropdownMultiselectComponent {
24863
25076
  matExpandMore,
24864
25077
  matExpandLess,
24865
25078
  }),
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 }); }
25079
+ ], 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
25080
  }
24868
25081
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownMultiselectComponent, decorators: [{
24869
25082
  type: Component,
@@ -24873,7 +25086,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
24873
25086
  matExpandMore,
24874
25087
  matExpandLess,
24875
25088
  }),
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" }]
25089
+ ], 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
25090
  }], propDecorators: { title: [{
24878
25091
  type: Input
24879
25092
  }], choices: [{
@@ -25045,7 +25258,7 @@ class DropdownSelectorComponent {
25045
25258
  provideNgIconsConfig({
25046
25259
  size: '1.5em',
25047
25260
  }),
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 }); }
25261
+ ], 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
25262
  }
25050
25263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DropdownSelectorComponent, decorators: [{
25051
25264
  type: Component,
@@ -25785,6 +25998,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
25785
25998
  type: Output
25786
25999
  }] } });
25787
26000
 
26001
+ marker('search.filters.spatialExtent.import');
26002
+ marker('search.filters.spatialExtent.helpText');
26003
+ marker('search.filters.spatialExtent.error.title');
26004
+ marker('search.filters.spatialExtent.bboxPrefix');
26005
+ marker('search.filters.spatialExtent.bboxDelete');
26006
+ class SpatialExtentDropdownComponent {
26007
+ constructor() {
26008
+ this.cd = inject(ChangeDetectorRef);
26009
+ this.scrollStrategies = inject(ScrollStrategyOptions);
26010
+ this.maxFileSizeMb = null;
26011
+ this.bboxChange = new EventEmitter();
26012
+ this.errorChange = new EventEmitter();
26013
+ this.bbox = null;
26014
+ this.fileName = '';
26015
+ this.overlayPositions = [
26016
+ {
26017
+ originX: 'start',
26018
+ originY: 'bottom',
26019
+ overlayX: 'start',
26020
+ overlayY: 'top',
26021
+ offsetY: 8,
26022
+ },
26023
+ {
26024
+ originX: 'start',
26025
+ originY: 'top',
26026
+ overlayX: 'start',
26027
+ overlayY: 'bottom',
26028
+ offsetY: -8,
26029
+ },
26030
+ ];
26031
+ this.scrollStrategy = this.scrollStrategies.reposition();
26032
+ this.overlayOpen = false;
26033
+ this.overlayMinWidth = 'none';
26034
+ this.errorKey = null;
26035
+ }
26036
+ get hasSelection() {
26037
+ return !!this.bbox;
26038
+ }
26039
+ openOverlay() {
26040
+ this.overlayMinWidth =
26041
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
26042
+ .width + 'px';
26043
+ this.overlayOpen = true;
26044
+ }
26045
+ closeOverlay() {
26046
+ this.overlayOpen = false;
26047
+ }
26048
+ toggleOverlay() {
26049
+ if (this.overlayOpen) {
26050
+ this.closeOverlay();
26051
+ }
26052
+ else {
26053
+ this.openOverlay();
26054
+ }
26055
+ }
26056
+ async handleFileSelected(file) {
26057
+ this.errorKey = null;
26058
+ let content;
26059
+ try {
26060
+ content = await readFileAsText(file);
26061
+ const parsed = JSON.parse(content);
26062
+ const geometry = getGeometryFromGeoJSON(parsed);
26063
+ if (!geometry) {
26064
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'));
26065
+ return;
26066
+ }
26067
+ const bbox = getGeometryBoundingBox(geometry);
26068
+ this.bbox = bbox;
26069
+ this.fileName = file.name;
26070
+ this.bboxChange.emit(bbox);
26071
+ this.cd.markForCheck();
26072
+ }
26073
+ catch {
26074
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26075
+ return;
26076
+ }
26077
+ }
26078
+ handleFileError(error) {
26079
+ if (error === 'file-too-large') {
26080
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
26081
+ maxSize: this.maxFileSizeMb,
26082
+ });
26083
+ }
26084
+ else {
26085
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'));
26086
+ }
26087
+ }
26088
+ setError(errorKey, params) {
26089
+ this.errorKey = errorKey;
26090
+ this.errorChange.emit({ key: errorKey, params });
26091
+ this.cd.markForCheck();
26092
+ }
26093
+ removeSelection(event) {
26094
+ this.bbox = null;
26095
+ this.fileName = '';
26096
+ this.errorKey = null;
26097
+ this.fileInput?.clear();
26098
+ this.bboxChange.emit(null);
26099
+ propagateToDocumentOnly(event);
26100
+ }
26101
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
26102
+ 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: [
26103
+ provideIcons({
26104
+ iconoirCheckCircle,
26105
+ iconoirImport,
26106
+ iconoirSquareDashed,
26107
+ iconoirTrash,
26108
+ matClose,
26109
+ matExpandLess,
26110
+ matExpandMore,
26111
+ }),
26112
+ ], 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 }); }
26113
+ }
26114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SpatialExtentDropdownComponent, decorators: [{
26115
+ type: Component,
26116
+ args: [{ selector: 'gn-ui-spatial-extent-dropdown', standalone: true, imports: [
26117
+ ButtonComponent,
26118
+ NgIcon,
26119
+ OverlayModule,
26120
+ TranslatePipe,
26121
+ DragAndDropFileInputComponent,
26122
+ ], providers: [
26123
+ provideIcons({
26124
+ iconoirCheckCircle,
26125
+ iconoirImport,
26126
+ iconoirSquareDashed,
26127
+ iconoirTrash,
26128
+ matClose,
26129
+ matExpandLess,
26130
+ matExpandMore,
26131
+ }),
26132
+ ], 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" }]
26133
+ }], propDecorators: { title: [{
26134
+ type: Input
26135
+ }], maxFileSizeMb: [{
26136
+ type: Input
26137
+ }], bboxChange: [{
26138
+ type: Output
26139
+ }], errorChange: [{
26140
+ type: Output
26141
+ }], overlayOrigin: [{
26142
+ type: ViewChild,
26143
+ args: ['overlayOrigin']
26144
+ }], overlay: [{
26145
+ type: ViewChild,
26146
+ args: [CdkConnectedOverlay]
26147
+ }], fileInput: [{
26148
+ type: ViewChild,
26149
+ args: [DragAndDropFileInputComponent]
26150
+ }] } });
26151
+
25788
26152
  class CellPopinComponent {
25789
26153
  constructor() {
25790
26154
  this.scrollDispatcher = inject(ScrollDispatcher);
@@ -25861,7 +26225,7 @@ class CellPopinComponent {
25861
26225
  this.isOpen = false;
25862
26226
  }
25863
26227
  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"] }] }); }
26228
+ 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
26229
  }
25866
26230
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CellPopinComponent, decorators: [{
25867
26231
  type: Component,
@@ -27951,6 +28315,18 @@ class DateRangeSearchField extends SimpleSearchField {
27951
28315
  return 'dateRange';
27952
28316
  }
27953
28317
  }
28318
+ class SpatialExtentSearchField extends SimpleSearchField {
28319
+ constructor(injector) {
28320
+ super('spatialExtent', injector, 'asc');
28321
+ }
28322
+ getAvailableValues() {
28323
+ // TODO: return an array of spatial extents to show which ones are available in the dropdown
28324
+ return of([]);
28325
+ }
28326
+ getType() {
28327
+ return 'spatialExtent';
28328
+ }
28329
+ }
27954
28330
  marker('search.filters.availableServices.view');
27955
28331
  marker('search.filters.availableServices.download');
27956
28332
  class AvailableServicesField extends SimpleSearchField {
@@ -28095,6 +28471,7 @@ marker('search.filters.producerOrg');
28095
28471
  marker('search.filters.publisherOrg');
28096
28472
  marker('search.filters.user');
28097
28473
  marker('search.filters.changeDate');
28474
+ marker('search.filters.spatialExtent');
28098
28475
  class FieldsService {
28099
28476
  constructor() {
28100
28477
  this.injector = inject(Injector);
@@ -28118,6 +28495,7 @@ class FieldsService {
28118
28495
  user: new UserSearchField(this.injector),
28119
28496
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
28120
28497
  availableServices: new AvailableServicesField(this.injector),
28498
+ spatialExtent: new SpatialExtentSearchField(this.injector),
28121
28499
  };
28122
28500
  }
28123
28501
  get supportedFields() {
@@ -29380,7 +29758,7 @@ class ContactPillComponent {
29380
29758
  this.overlayOpen = false;
29381
29759
  }
29382
29760
  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 }); }
29761
+ 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
29762
  }
29385
29763
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ContactPillComponent, decorators: [{
29386
29764
  type: Component,
@@ -31302,19 +31680,111 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31302
31680
  type: Output
31303
31681
  }] } });
31304
31682
 
31683
+ class NotificationsService {
31684
+ constructor() {
31685
+ this.notifications$ = new BehaviorSubject([]);
31686
+ }
31687
+ showNotification(content, timeoutMs, error) {
31688
+ error && console.error(error);
31689
+ const id = Math.floor(Math.random() * 1000000);
31690
+ this.notifications$.next([...this.notifications$.value, { ...content, id }]);
31691
+ if (typeof timeoutMs !== 'undefined') {
31692
+ setTimeout(() => {
31693
+ this.removeNotificationById(id);
31694
+ }, timeoutMs);
31695
+ }
31696
+ return id;
31697
+ }
31698
+ removeNotificationById(id) {
31699
+ this.notifications$.next(this.notifications$.value.filter((n) => n.id !== id));
31700
+ }
31701
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
31702
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, providedIn: 'root' }); }
31703
+ }
31704
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsService, decorators: [{
31705
+ type: Injectable,
31706
+ args: [{
31707
+ providedIn: 'root',
31708
+ }]
31709
+ }] });
31710
+
31711
+ class NotificationsContainerComponent {
31712
+ constructor() {
31713
+ this.notificationsService = inject(NotificationsService);
31714
+ }
31715
+ trackById(index, notification) {
31716
+ return notification.id;
31717
+ }
31718
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31719
+ 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: [
31720
+ trigger('enterExit', [
31721
+ transition(':enter', [
31722
+ animate('150ms', keyframes([
31723
+ style({ transform: 'scale(1)', opacity: 0 }),
31724
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31725
+ style({ transform: 'scale(1)', opacity: 1 }),
31726
+ ])),
31727
+ ]),
31728
+ transition(':leave', [
31729
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31730
+ ]),
31731
+ ]),
31732
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
31733
+ }
31734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NotificationsContainerComponent, decorators: [{
31735
+ type: Component,
31736
+ args: [{ selector: 'gn-ui-notifications-container', standalone: true, imports: [CommonModule, NotificationComponent], changeDetection: ChangeDetectionStrategy.OnPush, animations: [
31737
+ trigger('enterExit', [
31738
+ transition(':enter', [
31739
+ animate('150ms', keyframes([
31740
+ style({ transform: 'scale(1)', opacity: 0 }),
31741
+ style({ transform: 'scale(1.03)', opacity: 0.5 }),
31742
+ style({ transform: 'scale(1)', opacity: 1 }),
31743
+ ])),
31744
+ ]),
31745
+ transition(':leave', [
31746
+ animate('200ms', style({ transform: 'translateX(50px)', opacity: 0 })),
31747
+ ]),
31748
+ ]),
31749
+ ], 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" }]
31750
+ }] });
31751
+
31305
31752
  class FilterDropdownComponent {
31306
31753
  constructor() {
31307
31754
  this.searchFacade = inject(SearchFacade);
31308
31755
  this.searchService = inject(SearchService);
31309
31756
  this.fieldsService = inject(FieldsService);
31757
+ this.notificationsService = inject(NotificationsService);
31758
+ this.translateService = inject(TranslateService);
31759
+ this.spatialExtentMaxFileSize = getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE;
31310
31760
  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));
31761
+ this.selectedDateRange$ = this.selected$.pipe(map$1((selected) => (Array.isArray(selected) ? {} : selected)));
31762
+ this.spatialExtentErrorNotificationId = null;
31312
31763
  }
31313
31764
  onSelectedValues(values) {
31314
31765
  this.fieldsService
31315
31766
  .buildFiltersFromFieldValues({ [this.fieldName]: values })
31316
31767
  .subscribe((filters) => this.searchService.updateFilters(filters));
31317
31768
  }
31769
+ onBboxChange(bbox) {
31770
+ console.log(bbox);
31771
+ this.clearSpatialExtentErrorNotification();
31772
+ }
31773
+ onSpatialExtentError(error) {
31774
+ this.clearSpatialExtentErrorNotification();
31775
+ this.spatialExtentErrorNotificationId =
31776
+ this.notificationsService.showNotification({
31777
+ type: 'error',
31778
+ title: this.translateService.instant('search.filters.spatialExtent.error.title'),
31779
+ text: this.translateService.instant(error.key, error.params),
31780
+ });
31781
+ }
31782
+ clearSpatialExtentErrorNotification() {
31783
+ if (this.spatialExtentErrorNotificationId === null)
31784
+ return;
31785
+ this.notificationsService.removeNotificationById(this.spatialExtentErrorNotificationId);
31786
+ this.spatialExtentErrorNotificationId = null;
31787
+ }
31318
31788
  ngOnInit() {
31319
31789
  this.fieldType = this.fieldsService.getFieldType(this.fieldName);
31320
31790
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(startWith$1([]), map$1((values) => values.map((v) => ({
@@ -31322,27 +31792,15 @@ class FilterDropdownComponent {
31322
31792
  value: v.value.toString(), // converting to string for the dropdown
31323
31793
  }))), catchError(() => of([])));
31324
31794
  }
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
- }
31795
+ onDateRangeChange(dateRange) {
31796
+ this.fieldsService
31797
+ .buildFiltersFromFieldValues({
31798
+ [this.fieldName]: dateRange,
31799
+ })
31800
+ .subscribe((filters) => this.searchService.updateFilters(filters));
31343
31801
  }
31344
31802
  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 }); }
31803
+ 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
31804
  }
31347
31805
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilterDropdownComponent, decorators: [{
31348
31806
  type: Component,
@@ -31350,7 +31808,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31350
31808
  CommonModule,
31351
31809
  DateRangeDropdownComponent,
31352
31810
  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" }]
31811
+ SpatialExtentDropdownComponent,
31812
+ ], 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
31813
  }], propDecorators: { fieldName: [{
31355
31814
  type: Input
31356
31815
  }], title: [{
@@ -31886,74 +32345,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
31886
32345
  args: ['gnUiSearchStateContainer']
31887
32346
  }] } });
31888
32347
 
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
32348
  class ResultsTableContainerComponent {
31958
32349
  constructor() {
31959
32350
  this.searchFacade = inject(SearchFacade);
@@ -32038,12 +32429,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
32038
32429
 
32039
32430
  marker('search.filters.summaryLabel.user');
32040
32431
  marker('search.filters.summaryLabel.changeDate');
32432
+ const OPEN_BOUND = '…';
32041
32433
  class SearchFiltersSummaryItemComponent {
32042
32434
  constructor() {
32043
32435
  this.searchFacade = inject(SearchFacade);
32044
32436
  this.searchService = inject(SearchService);
32045
32437
  this.fieldsService = inject(FieldsService);
32046
- this.datePipe = inject(DatePipe);
32438
+ this.dateService = inject(DateService);
32047
32439
  this.translate = inject(TranslateService);
32048
32440
  this.fieldValues$ = this.searchFacade.searchFilters$.pipe(switchMap((filters) => this.fieldsService.readFieldValuesFromFilters(filters)), map$2((fieldValues) => Array.isArray(fieldValues[this.fieldName])
32049
32441
  ? fieldValues[this.fieldName]
@@ -32070,9 +32462,10 @@ class SearchFiltersSummaryItemComponent {
32070
32462
  getReadableValues(fieldValues) {
32071
32463
  return fieldValues.map((value) => {
32072
32464
  if (this.fieldType === 'dateRange') {
32465
+ const { start, end } = value;
32073
32466
  return {
32074
32467
  value,
32075
- label: `${this.datePipe.transform(value.start, 'dd.MM.yyyy')} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
32468
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
32076
32469
  };
32077
32470
  }
32078
32471
  else if (this.fieldName === 'user') {
@@ -32083,6 +32476,15 @@ class SearchFiltersSummaryItemComponent {
32083
32476
  }
32084
32477
  });
32085
32478
  }
32479
+ formatBound(date) {
32480
+ return date
32481
+ ? this.dateService.formatDate(date, {
32482
+ day: '2-digit',
32483
+ month: '2-digit',
32484
+ year: 'numeric',
32485
+ })
32486
+ : OPEN_BOUND;
32487
+ }
32086
32488
  async removeFilterValue(fieldValue) {
32087
32489
  const currentFieldValues = await firstValueFrom(this.fieldValues$);
32088
32490
  const updatedFieldValues = currentFieldValues
@@ -32095,11 +32497,11 @@ class SearchFiltersSummaryItemComponent {
32095
32497
  .subscribe((filters) => this.searchService.updateFilters(filters));
32096
32498
  }
32097
32499
  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" }] }); }
32500
+ 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
32501
  }
32100
32502
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SearchFiltersSummaryItemComponent, decorators: [{
32101
32503
  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" }]
32504
+ 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
32505
  }], propDecorators: { fieldName: [{
32104
32506
  type: Input
32105
32507
  }] } });
@@ -32343,11 +32745,11 @@ class AddLayerFromFileComponent {
32343
32745
  }, 5000);
32344
32746
  }
32345
32747
  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" }] }); }
32748
+ 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
32749
  }
32348
32750
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AddLayerFromFileComponent, decorators: [{
32349
32751
  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" }]
32752
+ 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
32753
  }] });
32352
32754
 
32353
32755
  class LayersPanelComponent {
@@ -32512,8 +32914,7 @@ const closeMetadata = createAction('[Metadata view] close');
32512
32914
  Related actions
32513
32915
  */
32514
32916
  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());
32917
+ const setLinkedRecords = createAction('[Metadata view] Set associated records', props());
32517
32918
  /*
32518
32919
  ChartConfig actions
32519
32920
  */
@@ -32545,9 +32946,8 @@ var mdview_actions = /*#__PURE__*/Object.freeze({
32545
32946
  loadUserFeedbacksSuccess: loadUserFeedbacksSuccess,
32546
32947
  setChartConfig: setChartConfig,
32547
32948
  setIncompleteMetadata: setIncompleteMetadata,
32548
- setRelated: setRelated,
32549
- setSourceOf: setSourceOf,
32550
- setSources: setSources
32949
+ setLinkedRecords: setLinkedRecords,
32950
+ setRelated: setRelated
32551
32951
  });
32552
32952
 
32553
32953
  const METADATA_VIEW_FEATURE_STATE_KEY = 'metadataView';
@@ -32590,12 +32990,9 @@ on(loadFullMetadata, (state) => ({
32590
32990
  on(setRelated, (state, { related }) => ({
32591
32991
  ...state,
32592
32992
  related,
32593
- })), on(setSources, (state, { sources }) => ({
32594
- ...state,
32595
- sources,
32596
- })), on(setSourceOf, (state, { sourceOf }) => ({
32993
+ })), on(setLinkedRecords, (state, { linkedRecords }) => ({
32597
32994
  ...state,
32598
- sourceOf,
32995
+ linkedRecords,
32599
32996
  })),
32600
32997
  /*
32601
32998
  ChartConfig reducers
@@ -32657,8 +33054,7 @@ const getMetadataError = createSelector(getMdViewState, (state) => state.error);
32657
33054
  Related selectors
32658
33055
  */
32659
33056
  const getRelated = createSelector(getMdViewState, (state) => state.related);
32660
- const getSources = createSelector(getMdViewState, (state) => state.sources);
32661
- const getSourceOf = createSelector(getMdViewState, (state) => state.sourceOf);
33057
+ const getLinkedRecords = createSelector(getMdViewState, (state) => state.linkedRecords);
32662
33058
  /*
32663
33059
  Metadata selectors
32664
33060
  */
@@ -32759,6 +33155,453 @@ function parseHeaders(httpHeaders) {
32759
33155
  return result;
32760
33156
  }
32761
33157
 
33158
+ const GEOMETRY_COLUMN_ALIAS = '__geometry__';
33159
+ function fieldToSql(name) {
33160
+ return `"${name.replace(/"/g, '""')}"`; // escape double quotes in field names
33161
+ }
33162
+ function filterToSql(filter) {
33163
+ const operator = filter[0];
33164
+ const args = filter.slice(1);
33165
+ function valueToSql(value) {
33166
+ return typeof value === 'number' ? value : `'${value}'`;
33167
+ }
33168
+ switch (operator) {
33169
+ case '<':
33170
+ case '<=':
33171
+ case '>':
33172
+ case '>=':
33173
+ case '=':
33174
+ case '!=':
33175
+ case 'like':
33176
+ return `${fieldToSql(args[0])} ${operator.toUpperCase()} ${valueToSql(args[1])}`;
33177
+ case 'in': {
33178
+ const values = args.slice(1);
33179
+ return `${fieldToSql(args[0])} IN (${values.map(valueToSql).join(', ')})`;
33180
+ }
33181
+ case 'and':
33182
+ case 'or': {
33183
+ const children = args
33184
+ .map(filterToSql)
33185
+ .join(` ${operator.toUpperCase()} `);
33186
+ return `(${children})`;
33187
+ }
33188
+ case 'not':
33189
+ return `NOT (${filterToSql(args[0])})`;
33190
+ }
33191
+ throw new Error(`Could not generate SQL query, operator not recognized: ${operator}`);
33192
+ }
33193
+ function aggregationToSql(aggregation) {
33194
+ const operation = aggregation[0];
33195
+ const field = aggregation[1];
33196
+ switch (operation) {
33197
+ case 'average':
33198
+ return `CAST(AVG(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`average(${field})`)}`;
33199
+ case 'sum':
33200
+ case 'max':
33201
+ case 'min':
33202
+ return `CAST(${operation.toUpperCase()}(${fieldToSql(field)}) AS DOUBLE) as ${fieldToSql(`${operation}(${field})`)}`;
33203
+ case 'count':
33204
+ return 'CAST(COUNT(*) AS INTEGER) as "count()"'; // we don't need Bigint precision here
33205
+ }
33206
+ }
33207
+ /**
33208
+ * Leave arguments at null if not used
33209
+ * @param tableName
33210
+ * @param selected
33211
+ * @param filter
33212
+ * @param sort
33213
+ * @param startIndex
33214
+ * @param count
33215
+ * @param groupBy
33216
+ * @param aggregations
33217
+ */
33218
+ function generateSqlQuery(tableName, selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null, geometryColumn = null) {
33219
+ let sqlSelect = 'SELECT *';
33220
+ const sqlFrom = ` FROM ${tableName}`;
33221
+ let sqlOrderBy = '';
33222
+ let sqlWhere = '';
33223
+ let sqlLimit = '';
33224
+ let sqlGroupBy = '';
33225
+ if (selected !== null) {
33226
+ sqlSelect = `SELECT ${selected.map(fieldToSql).join(', ')}`;
33227
+ }
33228
+ if (geometryColumn !== null) {
33229
+ sqlSelect += `, ST_AsGeoJSON(${fieldToSql(geometryColumn)}) as ${GEOMETRY_COLUMN_ALIAS}`;
33230
+ }
33231
+ if (filter !== null) {
33232
+ sqlWhere = ` WHERE ${filterToSql(filter)}`;
33233
+ }
33234
+ if (sort?.length) {
33235
+ sqlOrderBy = ` ORDER BY ${sort
33236
+ .map((sort) => `${fieldToSql(sort[1])} ${sort[0].toUpperCase()}`)
33237
+ .join(', ')}`;
33238
+ }
33239
+ if (startIndex !== null && count !== null) {
33240
+ sqlLimit = ` LIMIT ${count} OFFSET ${startIndex}`;
33241
+ }
33242
+ if (groupBy !== null && aggregations !== null) {
33243
+ sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33244
+ const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33245
+ const sqlGroupByFields = groupedByDistinct
33246
+ .map((group) => fieldToSql(group[1]))
33247
+ .join(', ');
33248
+ const sqlGroupBySelect = groupedByDistinct
33249
+ .map((group) => `${fieldToSql(group[1])} as ${fieldToSql(`distinct(${group[1]})`)}`)
33250
+ .join(', ');
33251
+ if (sqlGroupByFields && sqlGroupBySelect) {
33252
+ sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33253
+ sqlSelect += `, ${sqlGroupBySelect}`;
33254
+ }
33255
+ }
33256
+ return sqlSelect + sqlFrom + sqlGroupBy + sqlWhere + sqlOrderBy + sqlLimit;
33257
+ }
33258
+
33259
+ function arrowTableToDataItems(table) {
33260
+ const fields = table.schema.fields;
33261
+ return table.toArray().map((row) => {
33262
+ const rowJson = row.toJSON();
33263
+ const feature = {
33264
+ type: 'Feature',
33265
+ geometry: null,
33266
+ properties: {},
33267
+ };
33268
+ const keys = Object.keys(rowJson);
33269
+ for (let i = 0; i < keys.length; i++) {
33270
+ const key = keys[i];
33271
+ const dataType = fields[i].type;
33272
+ let value = rowJson[key];
33273
+ // this might happen if we get an array inside a field
33274
+ if (value instanceof Vector) {
33275
+ value = Array.from(value);
33276
+ }
33277
+ // cast bigints to ints
33278
+ if (typeof value === 'bigint') {
33279
+ value = Number(value);
33280
+ }
33281
+ // rename columns with empty name
33282
+ if (!key) {
33283
+ feature.properties['unknown'] = value;
33284
+ continue;
33285
+ }
33286
+ // assign properties that look like an id to the geojson `id` field
33287
+ if (/^(object|feature)?_?id$/.test(key.toLowerCase()) &&
33288
+ (typeof value == 'string' || typeof value === 'number')) {
33289
+ feature.id = value;
33290
+ }
33291
+ // if a date is in timestamp (number) format, convert it to native
33292
+ if (typeof value === 'number' &&
33293
+ (DataType.isTimestamp(dataType) || DataType.isDate(dataType))) {
33294
+ value = new Date(value);
33295
+ }
33296
+ // if a binary field (most likely a geometry column): skip
33297
+ if (DataType.isBinary(dataType)) {
33298
+ continue;
33299
+ }
33300
+ // geometry column
33301
+ if (key === GEOMETRY_COLUMN_ALIAS && DataType.isUtf8(dataType)) {
33302
+ feature.geometry = JSON.parse(value);
33303
+ continue;
33304
+ }
33305
+ feature.properties[key] = value;
33306
+ }
33307
+ return feature;
33308
+ });
33309
+ }
33310
+
33311
+ // init code taken from https://github.com/duckdb/duckdb-wasm/blob/main/packages/duckdb-wasm/README.md
33312
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
33313
+ // from https://duckdb.org/docs/current/sql/data_types/overview
33314
+ const typesMapping = {
33315
+ INTEGER: 'number',
33316
+ SMALLINT: 'number',
33317
+ TINYINT: 'number',
33318
+ BIGINT: 'number',
33319
+ HUGEINT: 'number',
33320
+ UINTEGER: 'number',
33321
+ USMALLINT: 'number',
33322
+ UTINYINT: 'number',
33323
+ UBIGINT: 'number',
33324
+ UHUGEINT: 'number',
33325
+ DOUBLE: 'number',
33326
+ BIGNUM: 'number',
33327
+ DECIMAL: 'number',
33328
+ NUMERIC: 'number',
33329
+ FLOAT: 'number',
33330
+ REAL: 'number',
33331
+ 'TIMESTAMP WITH TIME ZONE': 'date',
33332
+ TIMESTAMP: 'date',
33333
+ DATE: 'date',
33334
+ CHAR: 'string',
33335
+ VARCHAR: 'string',
33336
+ TEXT: 'string',
33337
+ UUID: 'string',
33338
+ BLOB: 'string',
33339
+ BIT: 'string',
33340
+ INTERVAL: 'string',
33341
+ LIST: 'string',
33342
+ BOOLEAN: 'boolean',
33343
+ };
33344
+ class Engine {
33345
+ async makeInit() {
33346
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
33347
+ let worker_url = bundle.mainWorker;
33348
+ // this is necessary to let browsers execute WASM code coming from a cross-origin host
33349
+ if (worker_url.startsWith('https://')) {
33350
+ worker_url = URL.createObjectURL(new Blob([`importScripts("${worker_url}");`], {
33351
+ type: 'text/javascript',
33352
+ }));
33353
+ }
33354
+ const worker = new Worker(worker_url);
33355
+ const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING);
33356
+ this.db = new duckdb.AsyncDuckDB(logger, worker);
33357
+ await this.db.instantiate(bundle.mainModule, bundle.pthreadWorker);
33358
+ // setup duckdb options
33359
+ const conn = await this.db.connect();
33360
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
33361
+ await conn.query(`INSTALL spatial; LOAD spatial;`);
33362
+ await conn.query(`SET TimeZone = '${timezone}';`);
33363
+ await conn.query(`SET threads = 1;`);
33364
+ // await conn.query(`SET memory_limit = '2GB';`) // used for experimenting when we encounter memory issues
33365
+ await conn.query(`SET preserve_insertion_order = false;`); // this reduces memory usage when loading a dataset
33366
+ conn.close();
33367
+ return this;
33368
+ }
33369
+ isReady() {
33370
+ if (!this.init_) {
33371
+ this.init_ = this.makeInit();
33372
+ }
33373
+ return this.init_;
33374
+ }
33375
+ /**
33376
+ * Returns information about a dataset once it's loaded:
33377
+ * - a list of properties description
33378
+ * - the name of the dataset geometry column (null if no geometry present)
33379
+ * @param datasetId name of the table under which the dataset will be stored
33380
+ * @param loadQuery duckdb-specific query for creating a table out of the data
33381
+ * @param forceReload if true, any existing data will be dropped and redownloaded
33382
+ */
33383
+ async loadFile(datasetId, loadQuery, forceReload = false) {
33384
+ const conn = await this.db.connect();
33385
+ let results;
33386
+ // either we want to recreate the table, or we keep it if it already exists
33387
+ const safeLoadQuery = forceReload
33388
+ ? `DROP TABLE IF EXISTS ${datasetId};
33389
+ ${loadQuery}`
33390
+ : loadQuery.replace(/CREATE TABLE(?! IF NOT EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS');
33391
+ // create the table
33392
+ try {
33393
+ results = await conn.query(safeLoadQuery);
33394
+ }
33395
+ catch (e) {
33396
+ throw new FetchError('parse', `DuckDB encountered an error when loading the data: ${e.message}`);
33397
+ }
33398
+ // read rows count
33399
+ results = await conn.query(`SELECT count(*) FROM ${datasetId}`);
33400
+ const { 'count_star()': recordsCount } = results.toArray()[0].toJSON();
33401
+ // read columns
33402
+ results = await conn.query(`SELECT * FROM information_schema.columns WHERE table_name = '${datasetId}';`);
33403
+ let geometryColumn = null;
33404
+ const properties = results
33405
+ .toArray()
33406
+ .map((row) => {
33407
+ const rowObj = row.toJSON();
33408
+ if (rowObj['data_type'] === 'GEOMETRY') {
33409
+ // the geometry is not part of the properties
33410
+ // note: right now we only keep one geometry column name, but if there are multiple they will
33411
+ // all get discarded
33412
+ geometryColumn = rowObj['column_name'];
33413
+ return null;
33414
+ }
33415
+ return {
33416
+ name: rowObj['column_name'],
33417
+ label: rowObj['column_name'],
33418
+ type: typesMapping[rowObj['data_type']] ?? 'other',
33419
+ };
33420
+ })
33421
+ .filter((prop) => prop !== null);
33422
+ conn.close();
33423
+ return {
33424
+ properties,
33425
+ geometryColumn,
33426
+ rowsCount: Number(recordsCount),
33427
+ };
33428
+ }
33429
+ // register a Uint8 buffer using a handle in the duckdb instance
33430
+ async registerData(name, buffer) {
33431
+ return this.db.registerFileBuffer(name, buffer);
33432
+ }
33433
+ /**
33434
+ * @param query duckdb-specific query for fetching items
33435
+ */
33436
+ async queryItems(query) {
33437
+ const conn = await this.db.connect();
33438
+ const results = await conn.query(query);
33439
+ conn.close();
33440
+ return arrowTableToDataItems(results);
33441
+ }
33442
+ close() {
33443
+ this.db?.terminate();
33444
+ }
33445
+ }
33446
+ let engine = null;
33447
+ async function getEngine() {
33448
+ if (!engine) {
33449
+ engine = new Engine();
33450
+ }
33451
+ return engine.isReady();
33452
+ }
33453
+
33454
+ class BaseReader {
33455
+ constructor(url) {
33456
+ this.url = url;
33457
+ this.selected = null;
33458
+ this.groupedBy = null;
33459
+ this.aggregations = null;
33460
+ this.filter = null;
33461
+ this.sort = null;
33462
+ this.startIndex = null;
33463
+ this.count = null;
33464
+ this.loadPromise_ = Promise.resolve();
33465
+ this.cacheEnabled = false;
33466
+ }
33467
+ enableCache(enabled) {
33468
+ this.cacheEnabled = enabled;
33469
+ }
33470
+ load() {
33471
+ throw new Error('not implemented');
33472
+ }
33473
+ get isLoaded() {
33474
+ return this.loadPromise_;
33475
+ }
33476
+ get properties() {
33477
+ throw new Error('not implemented');
33478
+ }
33479
+ get info() {
33480
+ throw new Error('not implemented');
33481
+ }
33482
+ read() {
33483
+ throw new Error('not implemented');
33484
+ }
33485
+ selectAll() {
33486
+ this.groupedBy = null;
33487
+ this.aggregations = null;
33488
+ this.selected = null;
33489
+ this.filter = null;
33490
+ this.startIndex = null;
33491
+ this.count = null;
33492
+ return this;
33493
+ }
33494
+ select(...selectedFields) {
33495
+ this.selected = selectedFields;
33496
+ this.aggregations = null; // clear aggregations & groups when selecting fields
33497
+ this.groupedBy = null;
33498
+ return this;
33499
+ }
33500
+ groupBy(...groupBy) {
33501
+ this.groupedBy = groupBy;
33502
+ this.selected = null; // clear normal field selection when aggregating
33503
+ return this;
33504
+ }
33505
+ aggregate(...aggregations) {
33506
+ this.aggregations = aggregations;
33507
+ return this;
33508
+ }
33509
+ where(filter) {
33510
+ this.filter = filter;
33511
+ return this;
33512
+ }
33513
+ orderBy(...fieldSorts) {
33514
+ this.sort = fieldSorts;
33515
+ return this;
33516
+ }
33517
+ limit(startIndex, count) {
33518
+ this.startIndex = startIndex;
33519
+ this.count = count;
33520
+ return this;
33521
+ }
33522
+ }
33523
+
33524
+ /**
33525
+ * This reader handles file formats supported natively by DuckDB
33526
+ */
33527
+ class BaseFileReader extends BaseReader {
33528
+ // a table id should not exceed 63 chars
33529
+ generateDatasetId() {
33530
+ // generate a hash out of the url
33531
+ let hash = 0;
33532
+ for (const char of this.url) {
33533
+ hash = (hash << 5) - hash + char.charCodeAt(0);
33534
+ hash = hash >>> 0; // make it unsigned
33535
+ }
33536
+ return `datafetcher_${hash.toString(16)}`;
33537
+ }
33538
+ async getLoadQuery() {
33539
+ throw new Error('not implemented');
33540
+ }
33541
+ async load() {
33542
+ this.datasetId = this.generateDatasetId();
33543
+ this.loadPromise_ = getEngine()
33544
+ .then((engine) => {
33545
+ this.engine = engine;
33546
+ return this.getLoadQuery();
33547
+ })
33548
+ .then((loadQuery) => this.engine.loadFile(this.datasetId, loadQuery, !this.cacheEnabled))
33549
+ .then((datasetInfo) => {
33550
+ this.properties_ = datasetInfo.properties;
33551
+ this.geometryColumn = datasetInfo.geometryColumn;
33552
+ this.rowsCount = datasetInfo.rowsCount;
33553
+ // returns void for the loaded promise
33554
+ });
33555
+ }
33556
+ get properties() {
33557
+ return this.isLoaded.then(() => this.properties_);
33558
+ }
33559
+ get info() {
33560
+ return this.isLoaded.then(() => ({
33561
+ itemsCount: this.rowsCount,
33562
+ hasGeometry: !!this.geometryColumn,
33563
+ }));
33564
+ }
33565
+ async read() {
33566
+ await this.isLoaded;
33567
+ // if only certain fields are selected, omit the geometry
33568
+ const geometryColumn = this.selected === null ? this.geometryColumn : null;
33569
+ const query = generateSqlQuery(this.datasetId, this.selected, this.filter, this.sort, this.startIndex, this.count, this.groupedBy, this.aggregations, geometryColumn);
33570
+ return this.engine.queryItems(query);
33571
+ }
33572
+ }
33573
+
33574
+ class CsvReader extends BaseFileReader {
33575
+ async getLoadQuery() {
33576
+ // first we get a list of columns hich have a detected type of VARCHAR
33577
+ // 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
33578
+ return `
33579
+ SET VARIABLE strColumns = (SELECT list(name) FROM (SELECT unnest(Columns, recursive := true) FROM sniff_csv("${this.url}")) WHERE type = 'VARCHAR');
33580
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_csv("${this.url}",
33581
+ force_not_null = getvariable('strColumns'),
33582
+ auto_type_candidates = ['NULL', 'BOOLEAN', 'INTEGER', 'DOUBLE', 'DATE', 'VARCHAR']
33583
+ );`;
33584
+ }
33585
+ }
33586
+
33587
+ class JsonReader extends BaseFileReader {
33588
+ async getLoadQuery() {
33589
+ return `
33590
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM read_json("${this.url}",
33591
+ maximum_object_size = 536870912 -- 500MB
33592
+ );`;
33593
+ }
33594
+ }
33595
+
33596
+ class GeojsonReader extends BaseFileReader {
33597
+ async getLoadQuery() {
33598
+ return `
33599
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33600
+ allowed_drivers = ['GeoJSON']
33601
+ );`;
33602
+ }
33603
+ }
33604
+
32762
33605
  async function inferDatasetType(url, typeHint) {
32763
33606
  const fileExtensionMatches = new URL(url, typeof window !== 'undefined' ? window.location.toString() : undefined).pathname.match(/\.(.+)$/);
32764
33607
  const fileExtension = fileExtensionMatches && fileExtensionMatches.length
@@ -32823,127 +33666,6 @@ function fetchDataAsArrayBuffer(url, cacheActive) {
32823
33666
  return new Uint8Array(array).buffer;
32824
33667
  });
32825
33668
  }
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
33669
  /**
32948
33670
  * This creates a Proxy that allows reading and writing to the data item properties
32949
33671
  * as if it was a simple array of JSON objects
@@ -32965,333 +33687,71 @@ function getJsonDataItemsProxy(items) {
32965
33687
  });
32966
33688
  }
32967
33689
 
32968
- function filterToSql(filter) {
32969
- const operator = filter[0];
32970
- const args = filter.slice(1);
32971
- function valueToSql(value) {
32972
- return typeof value === 'number' ? value : `'${value}'`;
32973
- }
32974
- switch (operator) {
32975
- case '<':
32976
- case '<=':
32977
- case '>':
32978
- case '>=':
32979
- case '=':
32980
- case '!=':
32981
- case 'like':
32982
- return `[${args[0]}] ${operator.toUpperCase()} ${valueToSql(args[1])}`;
32983
- case 'in': {
32984
- const values = args.slice(1);
32985
- return `[${args[0]}] IN (${values.map(valueToSql).join(', ')})`;
32986
- }
32987
- case 'and':
32988
- case 'or': {
32989
- const children = args
32990
- .map(filterToSql)
32991
- .join(` ${operator.toUpperCase()} `);
32992
- return `(${children})`;
32993
- }
32994
- case 'not':
32995
- return `NOT (${filterToSql(args[0])})`;
32996
- }
32997
- throw new Error(`Could not generate SQL query, operator not recognized: ${operator}`);
32998
- }
32999
- function aggregationToSql(aggregation) {
33000
- const operation = aggregation[0];
33001
- const field = aggregation[1];
33002
- switch (operation) {
33003
- case 'average':
33004
- return `AVG([${field}]) as [average(${field})]`;
33005
- case 'sum':
33006
- case 'max':
33007
- case 'min':
33008
- return `${operation.toUpperCase()}([${field}]) as [${operation}(${field})]`;
33009
- case 'count':
33010
- return 'COUNT(*) as [count()]';
33011
- }
33012
- }
33013
- /**
33014
- * Leave arguments at null if not used
33015
- * @param selected
33016
- * @param filter
33017
- * @param sort
33018
- * @param startIndex
33019
- * @param count
33020
- * @param groupBy
33021
- * @param aggregations
33022
- */
33023
- function generateSqlQuery(selected = null, filter = null, sort = null, startIndex = null, count = null, groupBy = null, aggregations = null) {
33024
- let sqlSelect = 'SELECT *';
33025
- const sqlFrom = ' FROM ?';
33026
- let sqlOrderBy = '';
33027
- let sqlWhere = '';
33028
- let sqlLimit = '';
33029
- let sqlGroupBy = '';
33030
- if (selected !== null) {
33031
- sqlSelect = `SELECT ${selected.map((name) => `[${name}]`).join(', ')}`;
33032
- }
33033
- if (filter !== null) {
33034
- sqlWhere = ` WHERE ${filterToSql(filter)}`;
33035
- }
33036
- if (sort?.length) {
33037
- sqlOrderBy = ` ORDER BY ${sort
33038
- .map((sort) => `[${sort[1]}] ${sort[0].toUpperCase()}`)
33039
- .join(', ')}`;
33040
- }
33041
- if (startIndex !== null && count !== null) {
33042
- sqlLimit = ` LIMIT ${count} OFFSET ${startIndex}`;
33043
- }
33044
- if (groupBy !== null && aggregations !== null) {
33045
- sqlSelect = `SELECT ${aggregations.map(aggregationToSql).join(', ')}`;
33046
- const groupedByDistinct = groupBy.filter((group) => group[0] === 'distinct');
33047
- const sqlGroupByFields = groupedByDistinct
33048
- .map((group) => `[${group[1]}]`)
33049
- .join(', ');
33050
- const sqlGroupBySelect = groupedByDistinct
33051
- .map((group) => `[${group[1]}] as [distinct(${group[1]})]`)
33052
- .join(', ');
33053
- if (sqlGroupByFields && sqlGroupBySelect) {
33054
- sqlGroupBy = ` GROUP BY ${sqlGroupByFields}`;
33055
- sqlSelect += `, ${sqlGroupBySelect}`;
33690
+ class ExcelReader extends BaseFileReader {
33691
+ async getLoadQuery() {
33692
+ // we download the file as an array buffer first, in order to be able to check if it's an XLS file
33693
+ let buffer = await fetchDataAsArrayBuffer(this.url, this.cacheEnabled);
33694
+ const bufferHandle = `B${this.datasetId}`;
33695
+ // checking against the magic number at the beginning of XLS files, see https://en.wikipedia.org/wiki/List_of_file_signatures
33696
+ const magicNumber = new Uint8Array(buffer, 0, 8); // first 8 bytes
33697
+ const isXls = Array.from(magicNumber)
33698
+ .map((n) => n.toString(16).toUpperCase())
33699
+ .join(' ') === 'D0 CF 11 E0 A1 B1 1A E1';
33700
+ // uh oh, this is an XLS file (not supported by duckdb); convert it to CSV using the xlsx package
33701
+ if (isXls) {
33702
+ buffer = await import('xlsx').then(({ read, utils }) => {
33703
+ const workbook = read(buffer);
33704
+ const json = utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
33705
+ return new TextEncoder().encode(JSON.stringify(json)).buffer;
33706
+ });
33056
33707
  }
33057
- }
33058
- return sqlSelect + sqlFrom + sqlGroupBy + sqlOrderBy + sqlWhere + sqlLimit;
33059
- }
33060
-
33061
- class BaseReader {
33062
- constructor(url) {
33063
- this.url = url;
33064
- this.selected = null;
33065
- this.groupedBy = null;
33066
- this.aggregations = null;
33067
- this.filter = null;
33068
- this.sort = null;
33069
- this.startIndex = null;
33070
- this.count = null;
33071
- }
33072
- load() {
33073
- throw new Error('not implemented');
33074
- }
33075
- get properties() {
33076
- throw new Error('not implemented');
33077
- }
33078
- get info() {
33079
- throw new Error('not implemented');
33080
- }
33081
- read() {
33082
- throw new Error('not implemented');
33083
- }
33084
- selectAll() {
33085
- this.groupedBy = null;
33086
- this.aggregations = null;
33087
- this.selected = null;
33088
- this.filter = null;
33089
- this.startIndex = null;
33090
- this.count = null;
33091
- return this;
33092
- }
33093
- select(...selectedFields) {
33094
- this.selected = selectedFields;
33095
- this.aggregations = null; // clear aggregations & groups when selecting fields
33096
- this.groupedBy = null;
33097
- return this;
33098
- }
33099
- groupBy(...groupBy) {
33100
- this.groupedBy = groupBy;
33101
- this.selected = null; // clear normal field selection when aggregating
33102
- return this;
33103
- }
33104
- aggregate(...aggregations) {
33105
- this.aggregations = aggregations;
33106
- return this;
33107
- }
33108
- where(filter) {
33109
- this.filter = filter;
33110
- return this;
33111
- }
33112
- orderBy(...fieldSorts) {
33113
- this.sort = fieldSorts;
33114
- return this;
33115
- }
33116
- limit(startIndex, count) {
33117
- this.startIndex = startIndex;
33118
- this.count = count;
33119
- return this;
33120
- }
33121
- }
33122
-
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() {
33136
- throw new Error('not implemented');
33137
- }
33138
- load() {
33139
- this.parseResult_ = this.getData();
33140
- }
33141
- get properties() {
33142
- return this.parseResult_.then((result) => result.properties);
33143
- }
33144
- get info() {
33145
- return this.parseResult_.then((result) => ({
33146
- itemsCount: result.items.length,
33147
- }));
33148
- }
33149
- 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');
33181
- }
33182
- const parsed = Papa.parse(text, {
33183
- header: true,
33184
- skipEmptyLines: true,
33185
- delimiter,
33186
- });
33187
- if (parsed.errors.length) {
33188
- throw new Error('CSV parsing failed for the following reasons:\n' +
33189
- parsed.errors
33190
- .map((error) => `* ${error.message} at row ${error.row}, column ${error.index}`)
33191
- .join('\n'));
33192
- }
33193
- const items = parsed.data.map(jsonToGeojsonFeature);
33194
- return processItemProperties(items, true);
33195
- }
33196
- class CsvReader extends BaseFileReader {
33197
- getData() {
33198
- return fetchDataAsText(this.url, this.cacheActive).then(parseCsv);
33708
+ const duckDbFn = isXls ? 'read_json' : 'read_xlsx';
33709
+ await this.engine.registerData(bufferHandle, new Uint8Array(buffer));
33710
+ return `
33711
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM ${duckDbFn}("${bufferHandle}", ignore_errors = true);`;
33199
33712
  }
33200
33713
  }
33201
33714
 
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
- class JsonReader extends BaseFileReader {
33214
- getData() {
33215
- return fetchDataAsText(this.url, this.cacheActive).then(parseJson);
33715
+ class GmlReader extends BaseFileReader {
33716
+ async getLoadQuery() {
33717
+ return `
33718
+ CREATE TABLE ${this.datasetId} AS SELECT * FROM st_read("${this.url}",
33719
+ allowed_drivers = ['GML']
33720
+ );`;
33216
33721
  }
33217
33722
  }
33218
33723
 
33219
- /**
33220
- * This parser supports both Geojson Feature collections or arrays
33221
- * of Features
33222
- * @param text
33223
- */
33724
+ const formatGeojson = new GeoJSON$1();
33224
33725
  function parseGeojson(text) {
33225
33726
  const parsed = JSON.parse(text);
33226
33727
  const features = parsed.type === 'FeatureCollection' ? parsed.features : parsed;
33227
33728
  if (!Array.isArray(features)) {
33228
33729
  throw new Error('Could not parse GeoJSON, expected a features collection or an array of features at root level');
33229
33730
  }
33230
- return processItemProperties(features);
33231
- }
33232
- class GeojsonReader extends BaseFileReader {
33233
- getData() {
33234
- return fetchDataAsText(this.url, this.cacheActive).then(parseGeojson);
33235
- }
33236
- }
33237
-
33238
- /**
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
33242
- */
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);
33252
- });
33253
- }
33254
- class ExcelReader extends BaseFileReader {
33255
- getData() {
33256
- return fetchDataAsArrayBuffer(this.url, this.cacheActive).then(parseExcel);
33257
- }
33731
+ return features;
33258
33732
  }
33259
-
33260
- function parseGml(text, namespace, version) {
33261
- const splittedNamespace = namespace.split(':');
33262
- const regex = new RegExp(`xmlns:${splittedNamespace[0]}=["']([^'"]*)["']`);
33733
+ function parseGml(text, featureType, version) {
33734
+ const parts = featureType.split(':');
33735
+ const regex = new RegExp(`xmlns:${parts[0]}=["']([^'"]*)["']`);
33263
33736
  const match = regex.exec(text);
33264
33737
  if (match && match.length >= 2) {
33265
- const wf = new WFS({
33738
+ const wfs = new WFS({
33266
33739
  featureNS: match[1],
33267
- featureType: splittedNamespace[1],
33740
+ featureType: parts[1],
33268
33741
  version: version,
33269
33742
  });
33270
33743
  let features;
33271
33744
  try {
33272
- features = wf.readFeatures(text);
33745
+ features = wfs.readFeatures(text);
33273
33746
  }
33274
33747
  catch (e) {
33275
- throw Error("Couldn't parse WFS with GML features");
33748
+ throw Error(`Couldn't parse WFS with GML features: ${e.message}`);
33276
33749
  }
33277
- const geojsonItem = new GeoJSON$1().writeFeaturesObject(features);
33278
- return processItemProperties(geojsonItem.features, true);
33750
+ const geojsonItem = formatGeojson.writeFeaturesObject(features);
33751
+ return geojsonItem.features;
33279
33752
  }
33280
33753
  throw Error("Couldn't retrieve namespace url");
33281
33754
  }
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
33755
  async function getWfsEndpoint(wfsUrl) {
33296
33756
  try {
33297
33757
  return await new WfsEndpoint(wfsUrl).isReady();
@@ -33319,21 +33779,65 @@ async function getWfsEndpoint(wfsUrl) {
33319
33779
  }
33320
33780
  }
33321
33781
  }
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();
33782
+ class WfsReader extends BaseReader {
33783
+ constructor(url, featureTypeName) {
33784
+ super(url);
33785
+ this.endpoint = getWfsEndpoint(url);
33786
+ this.featureType = this.endpoint
33787
+ .then((endpoint) => {
33788
+ const featureTypes = endpoint.getFeatureTypes();
33789
+ return endpoint.getFeatureTypeFull(featureTypes.length === 1 && !featureTypeName
33790
+ ? featureTypes[0].name
33791
+ : featureTypeName);
33792
+ })
33793
+ .then((featureType) => {
33794
+ if (!featureType) {
33795
+ throw new Error('wfs.featuretype.notfound');
33796
+ }
33797
+ return featureType;
33798
+ });
33799
+ }
33800
+ get backupReader() {
33801
+ if (this.backupReader_) {
33802
+ return this.backupReader_;
33803
+ }
33804
+ this.backupReader_ = Promise.all([this.endpoint, this.featureType]).then(([endpoint, featureType]) => {
33805
+ let reader;
33806
+ if (endpoint.supportsJson(featureType.name)) {
33807
+ reader = new GeojsonReader(endpoint.getFeatureUrl(featureType.name, {
33808
+ asJson: true,
33809
+ outputCrs: 'EPSG:4326',
33810
+ }));
33811
+ }
33812
+ else {
33813
+ if (featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')) &&
33814
+ (featureType.defaultCrs === 'EPSG:4326' ||
33815
+ featureType.otherCrs?.includes('EPSG:4326'))) {
33816
+ reader = new GmlReader(endpoint.getFeatureUrl(featureType.name, {
33817
+ outputFormat: featureType.outputFormats.find((f) => f.toLowerCase().includes('gml')),
33818
+ outputCrs: 'EPSG:4326',
33819
+ }));
33820
+ }
33821
+ }
33822
+ reader.enableCache(this.cacheEnabled);
33823
+ reader.load();
33824
+ return reader;
33825
+ });
33826
+ return this.backupReader_;
33328
33827
  }
33329
33828
  get properties() {
33330
- return this.endpoint
33331
- .getFeatureTypeFull(this.featureTypeName)
33332
- .then((featureType) => Object.keys(featureType.properties).map((prop) => {
33829
+ return this.featureType.then((featureType) => Object.keys(featureType.properties).map((prop) => {
33333
33830
  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
33831
+ let type;
33832
+ if (originalType === 'float' || originalType === 'integer') {
33833
+ type = 'number';
33834
+ }
33835
+ else if (originalType === 'boolean') {
33836
+ type = 'string'; // we don't handle booleans yet in the data fetcher
33837
+ }
33838
+ else {
33839
+ type = originalType;
33840
+ }
33337
33841
  return {
33338
33842
  name: prop,
33339
33843
  label: prop,
@@ -33342,83 +33846,63 @@ class WfsReader extends BaseCacheReader {
33342
33846
  }));
33343
33847
  }
33344
33848
  get info() {
33345
- return this.endpoint.getFeatureTypeFull(this.featureTypeName).then((result) => ({
33849
+ return this.featureType.then((result) => ({
33346
33850
  itemsCount: result.objectCount,
33851
+ hasGeometry: !!result.geometryName,
33347
33852
  }));
33348
33853
  }
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
- }
33854
+ load() {
33855
+ // Nothing to load for Wfs
33378
33856
  }
33379
- async getData(aggregation, groupedBy) {
33380
- if (aggregation || groupedBy) {
33381
- return { items: await this.getQueryData() };
33857
+ async read() {
33858
+ const endpoint = await this.endpoint;
33859
+ const featureType = await this.featureType;
33860
+ // if we can't use the WFS protocol we fall back to the backup reader
33861
+ if (this.aggregations || this.groupedBy || !endpoint.supportsStartIndex()) {
33862
+ const backupReader = await this.backupReader;
33863
+ backupReader.selectAll();
33864
+ if (this.selected) {
33865
+ backupReader.select(...this.selected);
33866
+ }
33867
+ if (this.aggregations) {
33868
+ backupReader.aggregate(...this.aggregations);
33869
+ }
33870
+ if (this.groupedBy) {
33871
+ backupReader.groupBy(...this.groupedBy);
33872
+ }
33873
+ if (this.sort) {
33874
+ backupReader.orderBy(...this.sort);
33875
+ }
33876
+ if (this.startIndex !== null && this.count !== null) {
33877
+ backupReader.limit(this.startIndex, this.count);
33878
+ }
33879
+ return backupReader.read();
33382
33880
  }
33383
- const asJson = this.endpoint.supportsJson(this.featureTypeName);
33881
+ const asJson = endpoint.supportsJson(featureType.name);
33384
33882
  const attributes = this.selected ?? undefined;
33385
- let url = this.endpoint.getFeatureUrl(this.featureTypeName, {
33883
+ let sortBy = null;
33884
+ if (this.sort) {
33885
+ const mapSort = (s) => [s[0] === 'desc' ? 'D' : 'A', s[1]];
33886
+ sortBy = Array.isArray(this.sort[0])
33887
+ ? this.sort.map(mapSort)
33888
+ : mapSort(this.sort);
33889
+ }
33890
+ const url = endpoint.getFeatureUrl(featureType.name, {
33386
33891
  ...(this.startIndex !== null && { startIndex: this.startIndex }),
33387
33892
  ...(this.count !== null && { maxFeatures: this.count }),
33388
33893
  asJson,
33389
33894
  outputCrs: 'EPSG:4326',
33390
33895
  attributes,
33391
- // sortBy: this.sort // TODO: no sort in ogc-client?
33896
+ sortBy,
33392
33897
  });
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
33898
+ return fetchDataAsText(url, this.cacheEnabled).then((text) => asJson
33402
33899
  ? 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;
33900
+ : parseGml(text, featureType.name, endpoint.getVersion()));
33417
33901
  }
33418
33902
  }
33419
33903
 
33420
- async function openDataset(url, typeHint, options, cacheActive) {
33421
- const fileType = await inferDatasetType(url, typeHint);
33904
+ async function openDataset(url, options) {
33905
+ const fileType = await inferDatasetType(url, options?.typeHint);
33422
33906
  let reader;
33423
33907
  try {
33424
33908
  switch (fileType) {
@@ -33435,18 +33919,18 @@ async function openDataset(url, typeHint, options, cacheActive) {
33435
33919
  reader = new ExcelReader(url);
33436
33920
  break;
33437
33921
  case 'gml':
33438
- reader = new GmlReader(url, options.namespace, options.wfsVersion);
33922
+ reader = new GmlReader(url);
33439
33923
  break;
33440
33924
  case 'wfs':
33441
- reader = await WfsReader.createReader(url, options.wfsFeatureType);
33925
+ reader = new WfsReader(url, options?.wfsFeatureType);
33442
33926
  break;
33443
33927
  }
33444
- reader.setCacheActive(cacheActive);
33928
+ reader.enableCache(options?.enableCache ?? true);
33445
33929
  reader.load();
33446
33930
  return reader;
33447
33931
  }
33448
33932
  catch (e) {
33449
- //WfsReader may already raise a FetchError
33933
+ // WfsReader may already raise a FetchError
33450
33934
  if (e instanceof FetchError)
33451
33935
  throw e;
33452
33936
  else
@@ -33457,13 +33941,13 @@ async function openDataset(url, typeHint, options, cacheActive) {
33457
33941
  * This fetches the full dataset at the given URL and parses it according to its mime type.
33458
33942
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
33459
33943
  * 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:
33944
+ * File type is determined like so:
33461
33945
  * 1. if a type hint is given, use it
33462
33946
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
33463
33947
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
33464
33948
  */
33465
- async function readDataset(url, typeHint, options, cacheActive = true) {
33466
- const reader = await openDataset(url, typeHint, options, cacheActive);
33949
+ async function readDataset(url, options) {
33950
+ const reader = await openDataset(url, options);
33467
33951
  try {
33468
33952
  return await reader.read();
33469
33953
  }
@@ -33694,9 +34178,11 @@ class DataService {
33694
34178
  getDataset(link, cacheActive) {
33695
34179
  if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
33696
34180
  const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString());
33697
- return from(openDataset(wfsUrlEndpoint, 'wfs', {
34181
+ return from(openDataset(wfsUrlEndpoint, {
34182
+ typeHint: 'wfs',
33698
34183
  wfsFeatureType: link.name,
33699
- }, cacheActive));
34184
+ enableCache: cacheActive,
34185
+ }));
33700
34186
  }
33701
34187
  else if (link.type === 'download') {
33702
34188
  const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString());
@@ -33704,12 +34190,15 @@ class DataService {
33704
34190
  const supportedType = SupportedTypes.indexOf(format) > -1
33705
34191
  ? format
33706
34192
  : undefined;
33707
- return from(openDataset(linkProxifiedUrl, supportedType, undefined, cacheActive)).pipe();
34193
+ return from(openDataset(linkProxifiedUrl, {
34194
+ typeHint: supportedType,
34195
+ enableCache: cacheActive,
34196
+ })).pipe();
33708
34197
  }
33709
34198
  else if (link.type === 'service' &&
33710
34199
  link.accessServiceProtocol === 'esriRest') {
33711
34200
  const url = this.getDownloadUrlFromEsriRest(link.url.toString(), 'geojson');
33712
- return from(openDataset(url, 'geojson', undefined, cacheActive)).pipe();
34201
+ return from(openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })).pipe();
33713
34202
  }
33714
34203
  else if (link.type === 'service' &&
33715
34204
  link.accessServiceProtocol === 'ogcFeatures') {
@@ -33723,7 +34212,10 @@ class DataService {
33723
34212
  }
33724
34213
  const urlWithoutLimit = new URL(geojsonUrl);
33725
34214
  urlWithoutLimit.searchParams.delete('limit');
33726
- return openDataset(urlWithoutLimit.toString(), 'geojson', undefined, cacheActive);
34215
+ return openDataset(urlWithoutLimit.toString(), {
34216
+ typeHint: 'geojson',
34217
+ enableCache: cacheActive,
34218
+ });
33727
34219
  }));
33728
34220
  }
33729
34221
  return throwError(() => 'protocol not supported');
@@ -33975,20 +34467,31 @@ class DataTableComponent {
33975
34467
  this.eltRef = inject(ElementRef);
33976
34468
  this.cdr = inject(ChangeDetectorRef);
33977
34469
  this.translateService = inject(TranslateService);
33978
- this._featureAttributes = [];
34470
+ this.columnsFromFeatureCatalog = null;
34471
+ this.columnsFromDataset = [];
33979
34472
  this.selected = new EventEmitter();
33980
- this.properties$ = new BehaviorSubject(null);
33981
34473
  this.loading$ = new BehaviorSubject(false);
33982
34474
  this.error = null;
33983
34475
  }
33984
34476
  set featureAttributes(value) {
33985
- this._featureAttributes = value;
33986
- this.properties$.next(value.map((attr) => attr.value));
34477
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
34478
+ name: attrs.value,
34479
+ label: attrs.label,
34480
+ }));
33987
34481
  }
33988
34482
  set dataset(value) {
33989
34483
  this.dataset_ = value;
33990
34484
  this.dataset_.load();
33991
- this.dataset_.info.then((info) => (this.count = info.itemsCount));
34485
+ this.dataset_.info.then((info) => {
34486
+ this.count = info.itemsCount;
34487
+ this.cdr.detectChanges();
34488
+ });
34489
+ }
34490
+ get columns() {
34491
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset;
34492
+ }
34493
+ get columnNames() {
34494
+ return this.columns.map((c) => c.name);
33992
34495
  }
33993
34496
  ngOnInit() {
33994
34497
  this.dataSource = new DataTableDataSource();
@@ -34023,11 +34526,12 @@ class DataTableComponent {
34023
34526
  }
34024
34527
  async readData() {
34025
34528
  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
34529
  try {
34530
+ // wait for properties to be read
34531
+ if (!this.columnsFromFeatureCatalog) {
34532
+ this.columnsFromDataset = await this.dataset_.properties;
34533
+ }
34534
+ this.dataset_.select(...this.columnNames);
34031
34535
  await this.dataSource.showData(this.dataset_.read());
34032
34536
  this.error = null;
34033
34537
  }
@@ -34057,7 +34561,7 @@ class DataTableComponent {
34057
34561
  }
34058
34562
  }
34059
34563
  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 }); }
34564
+ 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
34565
  }
34062
34566
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataTableComponent, decorators: [{
34063
34567
  type: Component,
@@ -34069,10 +34573,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
34069
34573
  CommonModule,
34070
34574
  LoadingMaskComponent,
34071
34575
  PopupAlertComponent,
34072
- LetDirective,
34073
34576
  TranslatePipe,
34074
34577
  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"] }]
34578
+ ], 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
34579
  }], propDecorators: { featureAttributes: [{
34077
34580
  type: Input
34078
34581
  }], dataset: [{
@@ -34175,7 +34678,7 @@ class ChartViewComponent {
34175
34678
  }), shareReplay$1(1));
34176
34679
  this.properties$ = combineLatest([this.dataset$, this.featureCatalog$]).pipe(switchMap$1(([dataset, catalog]) => this.setProperties(dataset, catalog)), shareReplay$1(1));
34177
34680
  this.yChoices$ = this.properties$.pipe(map$1((properties) => properties
34178
- .filter((prop) => prop.type === 'number' || prop.type === 'date')
34681
+ .filter((prop) => prop.type === 'number')
34179
34682
  .map((prop) => ({ value: prop.name, label: prop.label || prop.name }))), tap$1((choices) => {
34180
34683
  if (!choices.find((choice) => choice.value === this.yProperty$.value)) {
34181
34684
  const newProp = choices[0]?.value || '';
@@ -34202,6 +34705,7 @@ class ChartViewComponent {
34202
34705
  this.aggregation$,
34203
34706
  ]).pipe(filter$1(([_, x, y]) => !!x || !!y), switchMap$1(([dataset, xProp, yProp, aggregation]) => {
34204
34707
  const fieldAgg = aggregation === 'count' ? ['count'] : [aggregation, yProp];
34708
+ this.loading = true;
34205
34709
  return dataset
34206
34710
  .groupBy(['distinct', xProp])
34207
34711
  .aggregate(fieldAgg)
@@ -34443,6 +34947,7 @@ class GeoTableViewComponent {
34443
34947
  }));
34444
34948
  }
34445
34949
  async initMapContext() {
34950
+ this.dataset.load();
34446
34951
  this.dataset.selectAll();
34447
34952
  return {
34448
34953
  layers: [
@@ -34616,8 +35121,7 @@ class MdViewFacade {
34616
35121
  }));
34617
35122
  this.error$ = this.store.pipe(select(getMetadataError));
34618
35123
  this.related$ = this.store.pipe(select(getRelated));
34619
- this.sources$ = this.store.pipe(select(getSources));
34620
- this.sourceOf$ = this.store.pipe(select(getSourceOf));
35124
+ this.linkedRecords$ = this.store.pipe(select(getLinkedRecords));
34621
35125
  this.chartConfig$ = this.store.pipe(select(getChartConfig));
34622
35126
  this.allLinks$ = this.metadata$.pipe(map$1((record) => 'onlineResources' in record ? record.onlineResources : []), shareReplay$1(1));
34623
35127
  this.resourceDoi$ = this.metadata$.pipe(map$1((record) => {
@@ -34736,12 +35240,9 @@ class MdViewEffects {
34736
35240
  this.loadRelatedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getSimilarRecords(full)), map$1((related) => {
34737
35241
  return setRelated({ related });
34738
35242
  }), 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 })))));
35243
+ this.loadLinkedRecords$ = createEffect(() => this.actions$.pipe(ofType(loadFullMetadataSuccess), switchMap$1(({ full }) => this.recordsRepository.getLinkedRecords(full)), map$1((linkedRecords) => {
35244
+ return setLinkedRecords({ linkedRecords });
35245
+ }), catchError(() => of(setLinkedRecords({ linkedRecords: null })))));
34745
35246
  /*
34746
35247
  UserFeedback effects
34747
35248
  */
@@ -40020,7 +40521,7 @@ function flattenQueryParams(params) {
40020
40521
  const start = flattened[key].start;
40021
40522
  const end = flattened[key].end;
40022
40523
  flattened[key] = [
40023
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
40524
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
40024
40525
  ];
40025
40526
  }
40026
40527
  }
@@ -40377,5 +40878,5 @@ const CHART_TYPE_VALUES = [
40377
40878
  * Generated bundle index. Do not edit.
40378
40879
  */
40379
40880
 
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 };
40881
+ 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, 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, SpatialExtentSearchField, 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, 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
40882
  //# sourceMappingURL=geonetwork-ui.mjs.map