geonetwork-ui 2.10.3-dev.4154db07d → 2.11.0-dev.0559cfd9d

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 (128) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2983 -2095
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +408 -180
  4. package/index.d.ts.map +1 -1
  5. package/package.json +10 -8
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +5 -5
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/mel.records.dataset+featurecatalog.ts +395 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -5
  10. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +78 -21
  11. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  12. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +19 -0
  13. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +17 -6
  14. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +110 -0
  15. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +133 -2
  16. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +9 -0
  17. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +11 -6
  18. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  19. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +2 -2
  20. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +157 -38
  21. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +45 -50
  22. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +54 -20
  23. package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
  24. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
  25. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  26. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +3 -3
  27. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
  28. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  29. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  30. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +54 -41
  31. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  32. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  33. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  34. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  35. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  36. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  37. package/src/libs/feature/map/src/index.ts +0 -2
  38. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  39. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  40. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  41. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  42. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  43. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +10 -17
  44. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +2 -4
  45. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  46. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  47. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  48. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  49. package/src/libs/feature/search/src/index.ts +2 -0
  50. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +18 -4
  51. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +80 -19
  52. package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
  53. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
  54. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
  55. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
  56. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  57. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  58. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +64 -4
  59. package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
  60. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  61. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  62. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  63. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +3 -3
  64. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
  65. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  66. package/src/libs/ui/inputs/src/index.ts +2 -0
  67. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
  68. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
  69. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
  70. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  71. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  72. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  73. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  74. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  75. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  76. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  77. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  78. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  79. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  80. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  81. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  82. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
  83. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  84. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  85. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +244 -0
  86. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  87. package/src/libs/util/app-config/src/lib/app-config.ts +37 -0
  88. package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
  89. package/src/libs/util/app-config/src/lib/model.ts +25 -1
  90. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  91. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  92. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  93. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  94. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  95. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  96. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  97. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  98. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  99. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  100. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  101. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  102. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  103. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  104. package/src/libs/util/shared/src/index.ts +1 -0
  105. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  106. package/src/libs/util/shared/src/lib/gn-ui-version.ts +1 -1
  107. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  108. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  109. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  110. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  111. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  112. package/tailwind.base.css +5 -0
  113. package/translations/de.json +38 -5
  114. package/translations/en.json +37 -4
  115. package/translations/es.json +35 -2
  116. package/translations/fr.json +42 -9
  117. package/translations/it.json +36 -3
  118. package/translations/nl.json +35 -2
  119. package/translations/pt.json +35 -2
  120. package/translations/sk.json +36 -3
  121. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  122. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  123. package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
  124. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  125. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  126. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
  127. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  128. /package/src/libs/feature/{map/src/lib/geocoding/geocoding.component.css → editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css} +0 -0
@@ -40,7 +40,9 @@ export class MdViewEffects {
40
40
  this.actions$.pipe(
41
41
  ofType(MdViewActions.loadFullMetadataSuccess),
42
42
  filter(({ full }) => full !== undefined),
43
- switchMap(({ full }) => this.recordsRepository.getFeatureCatalog(full)),
43
+ switchMap(({ full }) =>
44
+ this.recordsRepository.queryLinkedFeatureCatalog(full)
45
+ ),
44
46
  map((featureCatalog) =>
45
47
  MdViewActions.loadFeatureCatalogSuccess({
46
48
  datasetCatalog: featureCatalog,
@@ -70,25 +72,16 @@ export class MdViewEffects {
70
72
  )
71
73
  )
72
74
 
73
- loadSources$ = createEffect(() =>
74
- this.actions$.pipe(
75
- ofType(MdViewActions.loadFullMetadataSuccess),
76
- switchMap(({ full }) => this.recordsRepository.getSources(full)),
77
- map((sources) => {
78
- return MdViewActions.setSources({ sources })
79
- }),
80
- catchError(() => of(MdViewActions.setSources({ sources: null })))
81
- )
82
- )
83
-
84
- loadSourceOf$ = createEffect(() =>
75
+ loadLinkedRecords$ = createEffect(() =>
85
76
  this.actions$.pipe(
86
77
  ofType(MdViewActions.loadFullMetadataSuccess),
87
- switchMap(({ full }) => this.recordsRepository.getSourceOf(full)),
88
- map((sourceOf) => {
89
- return MdViewActions.setSourceOf({ sourceOf })
78
+ switchMap(({ full }) => this.recordsRepository.getLinkedRecords(full)),
79
+ map((linkedRecords) => {
80
+ return MdViewActions.setLinkedRecords({ linkedRecords })
90
81
  }),
91
- catchError(() => of(MdViewActions.setSourceOf({ sourceOf: null })))
82
+ catchError(() =>
83
+ of(MdViewActions.setLinkedRecords({ linkedRecords: null }))
84
+ )
92
85
  )
93
86
  )
94
87
 
@@ -74,9 +74,7 @@ export class MdViewFacade {
74
74
 
75
75
  related$ = this.store.pipe(select(MdViewSelectors.getRelated))
76
76
 
77
- sources$ = this.store.pipe(select(MdViewSelectors.getSources))
78
-
79
- sourceOf$ = this.store.pipe(select(MdViewSelectors.getSourceOf))
77
+ linkedRecords$ = this.store.pipe(select(MdViewSelectors.getLinkedRecords))
80
78
 
81
79
  chartConfig$ = this.store.pipe(select(MdViewSelectors.getChartConfig))
82
80
 
@@ -164,7 +162,7 @@ export class MdViewFacade {
164
162
  link.accessServiceProtocol === 'ogcFeatures'
165
163
  ) {
166
164
  return from(
167
- this.dataService.getItemsFromOgcApi(link.url.href, 1)
165
+ this.dataService.getItemsFromOgcApi(link.url.href, link.name, 1)
168
166
  ).pipe(
169
167
  map((collectionRecords: OgcApiRecord[]) => {
170
168
  return collectionRecords &&
@@ -4,6 +4,7 @@ import { DatavizChartConfigModel } from '../../../../../../libs/common/domain/sr
4
4
  import {
5
5
  CatalogRecord,
6
6
  DatasetFeatureCatalog,
7
+ LinkedRecord,
7
8
  UserFeedback,
8
9
  } from '../../../../../../libs/common/domain/src/lib/model/record'
9
10
 
@@ -14,8 +15,7 @@ export interface MetadataViewState {
14
15
  error: { notFound?: boolean; otherError?: string } | null
15
16
  metadata?: Partial<CatalogRecord>
16
17
  related?: CatalogRecord[]
17
- sources?: CatalogRecord[]
18
- sourceOf?: CatalogRecord[]
18
+ linkedRecords?: LinkedRecord[]
19
19
  userFeedbacks?: UserFeedback[]
20
20
  allUserFeedbacksLoading: boolean
21
21
  addUserFeedbackLoading: boolean
@@ -77,14 +77,9 @@ const metadataViewReducer = createReducer(
77
77
  related,
78
78
  })),
79
79
 
80
- on(MetadataViewActions.setSources, (state, { sources }) => ({
80
+ on(MetadataViewActions.setLinkedRecords, (state, { linkedRecords }) => ({
81
81
  ...state,
82
- sources,
83
- })),
84
-
85
- on(MetadataViewActions.setSourceOf, (state, { sourceOf }) => ({
86
- ...state,
87
- sourceOf,
82
+ linkedRecords,
88
83
  })),
89
84
 
90
85
  /*
@@ -41,14 +41,9 @@ export const getRelated = createSelector(
41
41
  (state: MetadataViewState) => state.related
42
42
  )
43
43
 
44
- export const getSources = createSelector(
44
+ export const getLinkedRecords = createSelector(
45
45
  getMdViewState,
46
- (state: MetadataViewState) => state.sources
47
- )
48
-
49
- export const getSourceOf = createSelector(
50
- getMdViewState,
51
- (state: MetadataViewState) => state.sourceOf
46
+ (state: MetadataViewState) => state.linkedRecords
52
47
  )
53
48
  /*
54
49
  Metadata selectors
@@ -62,6 +62,6 @@ export class RouterService {
62
62
  }
63
63
 
64
64
  getDefaultSort(): SortByField {
65
- return SortByEnum.RESOURCE_DATES
65
+ return SortByEnum.RESOURCE_DATE
66
66
  }
67
67
  }
@@ -22,7 +22,7 @@ export function flattenQueryParams(
22
22
  const start = (flattened[key] as DateRange).start
23
23
  const end = (flattened[key] as DateRange).end
24
24
  flattened[key] = [
25
- `${start ? formatDate(start) : ''}..${formatDate(end) || ''}`,
25
+ `${start ? formatDate(start) : ''}..${end ? formatDate(end) : ''}`,
26
26
  ]
27
27
  }
28
28
  }
@@ -1,4 +1,6 @@
1
1
  export * from './lib/feature-search.module'
2
+ export * from './lib/geocoding/geocoding.service'
3
+ export * from './lib/location-search/location-search.component'
2
4
  export * from './lib/filter-geometry.token'
3
5
  export * from './lib/record-url.token'
4
6
  export * from './lib/state/actions'
@@ -1,11 +1,25 @@
1
1
  @if (fieldType === 'dateRange') {
2
2
  <gn-ui-date-range-dropdown
3
3
  [title]="title"
4
- [startDate]="(selectedDateRange$ | async)?.start"
5
- [endDate]="(selectedDateRange$ | async)?.end"
6
- (startDateChange)="onStartDateChange($event)"
7
- (endDateChange)="onEndDateChange($event)"
4
+ [dateRange]="(selectedDateRange$ | async) ?? {}"
5
+ (dateRangeChange)="onDateRangeChange($event)"
6
+ [attr.data-cy-field]="fieldName"
8
7
  ></gn-ui-date-range-dropdown>
8
+ } @else if (fieldType === 'spatialExtent') {
9
+ <gn-ui-spatial-extent-dropdown
10
+ [title]="title"
11
+ [initialBbox]="(selectedBoundingBox$ | async) ?? null"
12
+ [maxFileSizeMb]="spatialExtentMaxFileSize"
13
+ (bboxChange)="onBboxChange($event)"
14
+ (errorChange)="onSpatialExtentError($event)"
15
+ [attr.data-cy-field]="fieldName"
16
+ >
17
+ <gn-ui-location-search
18
+ [placeholder]="'search.filters.spatialExtent.searchLocation' | translate"
19
+ (bboxSelected)="onLocationSelected($event)"
20
+ data-cy="locationSearch"
21
+ ></gn-ui-location-search>
22
+ </gn-ui-spatial-extent-dropdown>
9
23
  } @else {
10
24
  <gn-ui-dropdown-multiselect
11
25
  class="w-full"
@@ -1,14 +1,18 @@
1
1
  import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
+ InjectionToken,
4
5
  Input,
5
6
  OnInit,
7
+ ViewChild,
6
8
  inject,
7
9
  } from '@angular/core'
8
10
  import {
9
11
  Choice,
10
12
  DateRangeDropdownComponent,
11
13
  DropdownMultiselectComponent,
14
+ SpatialExtentDropdownComponent,
15
+ SpatialExtentDropdownError,
12
16
  } from '../../../../../../libs/ui/inputs/src'
13
17
  import { Observable, of, switchMap } from 'rxjs'
14
18
  import { catchError, filter, map, startWith } from 'rxjs/operators'
@@ -22,6 +26,22 @@ import {
22
26
  } from '../utils/service/fields'
23
27
  import { DateRange } from '../../../../../../libs/api/repository/src'
24
28
  import { CommonModule } from '@angular/common'
29
+ import { BoundingBox } from '../../../../../../libs/util/shared/src'
30
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
31
+ import { TranslatePipe, TranslateService } from '@ngx-translate/core'
32
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
33
+ import {
34
+ LocationBbox,
35
+ LocationSearchComponent,
36
+ } from '../location-search/location-search.component'
37
+
38
+ marker('search.filters.spatialExtent.searchLocation')
39
+
40
+ // in MB, null means no limit
41
+ export const SPATIAL_EXTENT_MAX_FILE_SIZE = new InjectionToken<number | null>(
42
+ 'spatialExtentMaxFileSize',
43
+ { factory: () => null }
44
+ )
25
45
 
26
46
  @Component({
27
47
  selector: 'gn-ui-filter-dropdown',
@@ -33,18 +53,27 @@ import { CommonModule } from '@angular/common'
33
53
  CommonModule,
34
54
  DateRangeDropdownComponent,
35
55
  DropdownMultiselectComponent,
56
+ SpatialExtentDropdownComponent,
57
+ LocationSearchComponent,
58
+ TranslatePipe,
36
59
  ],
37
60
  })
38
61
  export class FilterDropdownComponent implements OnInit {
39
62
  private searchFacade = inject(SearchFacade)
40
63
  private searchService = inject(SearchService)
41
64
  private fieldsService = inject(FieldsService)
65
+ private notificationsService = inject(NotificationsService)
66
+ private translateService = inject(TranslateService)
42
67
 
43
68
  @Input() fieldName: string
44
69
  @Input() title: string
45
70
 
71
+ @ViewChild(SpatialExtentDropdownComponent)
72
+ spatialExtentDropdown: SpatialExtentDropdownComponent
73
+
74
+ spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
75
+
46
76
  fieldType: FieldType
47
- dateRange: DateRange
48
77
  choices$: Observable<Choice[]>
49
78
  selected$ = this.searchFacade.searchFilters$.pipe(
50
79
  switchMap((filters) =>
@@ -57,15 +86,58 @@ export class FilterDropdownComponent implements OnInit {
57
86
  ) as Observable<FieldValue[]>
58
87
 
59
88
  selectedDateRange$ = this.selected$.pipe(
60
- map((selectedDateRange) => selectedDateRange as DateRange)
89
+ map((selected) => (Array.isArray(selected) ? {} : (selected as DateRange)))
61
90
  ) as Observable<DateRange>
62
91
 
92
+ selectedBoundingBox$ = this.selected$.pipe(
93
+ map((selected) =>
94
+ Array.isArray(selected) && selected.length > 0
95
+ ? (selected as BoundingBox)
96
+ : null
97
+ )
98
+ ) as Observable<BoundingBox | null>
99
+
63
100
  onSelectedValues(values: unknown[]) {
64
101
  this.fieldsService
65
102
  .buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
66
103
  .subscribe((filters) => this.searchService.updateFilters(filters))
67
104
  }
68
105
 
106
+ private spatialExtentErrorNotificationId: number | null = null
107
+
108
+ onBboxChange(bbox: BoundingBox | null) {
109
+ this.fieldsService
110
+ .buildFiltersFromFieldValues({
111
+ [this.fieldName]: bbox,
112
+ })
113
+ .subscribe((filters) => this.searchService.updateFilters(filters))
114
+ this.clearSpatialExtentErrorNotification()
115
+ }
116
+
117
+ onLocationSelected({ bbox, label }: LocationBbox) {
118
+ this.spatialExtentDropdown.setLocationBbox(bbox, label)
119
+ }
120
+
121
+ onSpatialExtentError(error: SpatialExtentDropdownError) {
122
+ this.clearSpatialExtentErrorNotification()
123
+ this.spatialExtentErrorNotificationId =
124
+ this.notificationsService.showNotification({
125
+ type: 'error',
126
+ title: this.translateService.instant(
127
+ 'search.filters.spatialExtent.error.title'
128
+ ),
129
+ text: this.translateService.instant(error.key, error.params),
130
+ })
131
+ }
132
+
133
+ private clearSpatialExtentErrorNotification() {
134
+ if (this.spatialExtentErrorNotificationId === null) return
135
+ this.notificationsService.removeNotificationById(
136
+ this.spatialExtentErrorNotificationId
137
+ )
138
+ this.spatialExtentErrorNotificationId = null
139
+ }
140
+
69
141
  ngOnInit() {
70
142
  this.fieldType = this.fieldsService.getFieldType(this.fieldName)
71
143
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
@@ -80,22 +152,11 @@ export class FilterDropdownComponent implements OnInit {
80
152
  )
81
153
  }
82
154
 
83
- onStartDateChange(start: Date) {
84
- if (!start) return
85
- this.dateRange = { ...this.dateRange, start }
86
- }
87
-
88
- onEndDateChange(end: Date) {
89
- if (!end) return
90
- this.dateRange = { ...this.dateRange, end }
91
- if (this.dateRange.start && this.dateRange.end) {
92
- this.fieldsService
93
- .buildFiltersFromFieldValues({
94
- [this.fieldName]: this.dateRange,
95
- })
96
- .subscribe((filters) => {
97
- return this.searchService.updateFilters(filters)
98
- })
99
- }
155
+ onDateRangeChange(dateRange: DateRange) {
156
+ this.fieldsService
157
+ .buildFiltersFromFieldValues({
158
+ [this.fieldName]: dateRange,
159
+ })
160
+ .subscribe((filters) => this.searchService.updateFilters(filters))
100
161
  }
101
162
  }
@@ -0,0 +1,105 @@
1
+ import { Injectable, InjectionToken, inject } from '@angular/core'
2
+ import {
3
+ queryGeoadmin,
4
+ GeoadminOptions,
5
+ GeocodingResult,
6
+ queryGeonames,
7
+ GeonamesOptions,
8
+ GeoplateformeOptions,
9
+ queryGeoplateforme,
10
+ } from '@geospatial-sdk/geocoding'
11
+ import { from, Observable, throwError } from 'rxjs'
12
+ import { catchError } from 'rxjs/operators'
13
+
14
+ type GeoadminGeocodingProvider = ['geoadmin', GeoadminOptions]
15
+ type GeonamesGeocodingProvider = ['geonames', GeonamesOptions]
16
+ type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
17
+ export type GeocodingProvider =
18
+ | GeoadminGeocodingProvider
19
+ | GeonamesGeocodingProvider
20
+ | GeoplateformeGeocodingProvider
21
+
22
+ export const DEFAULT_GEOCODING_PROVIDER = [
23
+ 'geonames',
24
+ { maxRows: 5 },
25
+ ] as GeocodingProvider
26
+
27
+ export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
28
+ 'geocoding-provider',
29
+ {
30
+ providedIn: 'root',
31
+ factory: (): GeocodingProvider => DEFAULT_GEOCODING_PROVIDER,
32
+ }
33
+ )
34
+
35
+ // these options are arrays in the providers, but they can be configured
36
+ // with a single value for simplicity
37
+ const GEOCODING_PROVIDER_ARRAY_OPTIONS: {
38
+ geoadmin: (keyof GeoadminOptions)[]
39
+ geoplateforme: (keyof GeoplateformeOptions)[]
40
+ } = {
41
+ geoadmin: ['origins', 'features'],
42
+ geoplateforme: [
43
+ 'index',
44
+ 'category',
45
+ 'type',
46
+ 'postCode',
47
+ 'cityCode',
48
+ 'depCode',
49
+ ],
50
+ }
51
+
52
+ function wrapArrayOptions<T extends object>(options: T, keys: (keyof T)[]): T {
53
+ const wrapped = { ...options }
54
+ for (const key of keys) {
55
+ if (typeof wrapped[key] === 'string') {
56
+ wrapped[key] = [wrapped[key]] as T[keyof T]
57
+ }
58
+ }
59
+ return wrapped
60
+ }
61
+
62
+ @Injectable({
63
+ providedIn: 'root',
64
+ })
65
+ export class GeocodingService {
66
+ private provider = inject<GeocodingProvider>(GEOCODING_PROVIDER)
67
+
68
+ query(text: string): Observable<GeocodingResult[]> {
69
+ let queryObservable: Observable<GeocodingResult[]>
70
+ switch (this.provider[0]) {
71
+ case 'geoadmin':
72
+ queryObservable = from(
73
+ queryGeoadmin(
74
+ text,
75
+ wrapArrayOptions(
76
+ this.provider[1] as GeoadminOptions,
77
+ GEOCODING_PROVIDER_ARRAY_OPTIONS.geoadmin
78
+ )
79
+ )
80
+ )
81
+ break
82
+ case 'geonames':
83
+ queryObservable = from(
84
+ queryGeonames(text, this.provider[1] as GeonamesOptions)
85
+ )
86
+ break
87
+ case 'geoplateforme':
88
+ queryObservable = from(
89
+ queryGeoplateforme(text, {
90
+ ...wrapArrayOptions(
91
+ this.provider[1] as GeoplateformeOptions,
92
+ GEOCODING_PROVIDER_ARRAY_OPTIONS.geoplateforme
93
+ ),
94
+ returnTrueGeometry: true,
95
+ })
96
+ )
97
+ break
98
+ default:
99
+ return throwError(
100
+ () => new Error(`Unsupported geocoding provider: ${this.provider[0]}`)
101
+ )
102
+ }
103
+ return queryObservable.pipe(catchError((error) => throwError(error)))
104
+ }
105
+ }
@@ -0,0 +1,18 @@
1
+ <gn-ui-autocomplete
2
+ [action]="searchAction"
3
+ [displayWithFn]="displayWithFn"
4
+ [placeholder]="placeholder"
5
+ [displayWithTemplate]="defaultItemTpl"
6
+ [clearOnSelection]="true"
7
+ (itemSelected)="handleItemSelected($event)"
8
+ >
9
+ </gn-ui-autocomplete>
10
+
11
+ <ng-template #defaultItemTpl let-result>
12
+ <div class="py-2">
13
+ @if (getSecondaryLabel(result)) {
14
+ <div class="gn-ui-card-detail">{{ getSecondaryLabel(result) }}</div>
15
+ }
16
+ <div class="gn-ui-card-title">{{ getDisplayLabel(result) }}</div>
17
+ </div>
18
+ </ng-template>
@@ -0,0 +1,114 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ inject,
6
+ InjectionToken,
7
+ Input,
8
+ Output,
9
+ } from '@angular/core'
10
+ import { Observable } from 'rxjs'
11
+ import pointer from 'jsonpointer'
12
+ import { GeocodingResult } from '@geospatial-sdk/geocoding'
13
+ import { Geometry } from 'geojson'
14
+ import {
15
+ AutocompleteComponent,
16
+ AutocompleteItem,
17
+ } from '../../../../../../libs/ui/inputs/src'
18
+ import { BoundingBox, getGeometryBoundingBox } from '../../../../../../libs/util/shared/src'
19
+ import { GeocodingService } from '../geocoding/geocoding.service'
20
+
21
+ // JSON Pointers resolved against a geocoding result to build the labels shown in the dropdown
22
+ export interface GeocodingProviderLabels {
23
+ main?: string
24
+ secondary?: string
25
+ tertiary?: string
26
+ }
27
+
28
+ export interface LocationBbox {
29
+ bbox: BoundingBox
30
+ label: string
31
+ }
32
+
33
+ export const GEOCODING_PROVIDER_LABELS =
34
+ new InjectionToken<GeocodingProviderLabels>('geocodingProviderLabels', {
35
+ factory: () => ({}),
36
+ })
37
+
38
+ @Component({
39
+ selector: 'gn-ui-location-search',
40
+ templateUrl: './location-search.component.html',
41
+ changeDetection: ChangeDetectionStrategy.OnPush,
42
+ standalone: true,
43
+ imports: [AutocompleteComponent],
44
+ })
45
+ export class LocationSearchComponent {
46
+ private geocodingService = inject(GeocodingService)
47
+ private labels = inject(GEOCODING_PROVIDER_LABELS)
48
+
49
+ @Input() placeholder = ''
50
+ @Output() resultSelected = new EventEmitter<GeocodingResult>()
51
+ @Output() bboxSelected = new EventEmitter<LocationBbox>()
52
+
53
+ displayWithFn = (item: AutocompleteItem) => (item as GeocodingResult).label
54
+
55
+ searchAction = (text: string): Observable<AutocompleteItem[]> =>
56
+ this.geocodingService.query(text)
57
+
58
+ getSecondaryLabel(result: GeocodingResult): string | undefined {
59
+ return this.resolveLabel(result, this.labels.secondary)
60
+ }
61
+
62
+ getMainLabel(result: GeocodingResult): string {
63
+ return this.resolveLabel(result, this.labels.main) ?? result.label
64
+ }
65
+
66
+ getTertiaryLabel(result: GeocodingResult): string | undefined {
67
+ return this.resolveLabel(result, this.labels.tertiary)
68
+ }
69
+
70
+ getDisplayLabel(result: GeocodingResult): string {
71
+ const tertiary = this.getTertiaryLabel(result)
72
+ return this.getMainLabel(result) + (tertiary ? ', ' + tertiary : '')
73
+ }
74
+
75
+ private resolveLabel(
76
+ result: GeocodingResult,
77
+ path?: string
78
+ ): string | undefined {
79
+ if (!path) return undefined
80
+ let value: unknown
81
+ try {
82
+ value = pointer.get(result, path)
83
+ } catch {
84
+ return undefined
85
+ }
86
+ const values = Array.isArray(value) ? value : [value]
87
+ const label = values
88
+ .filter((v) => typeof v === 'string' || typeof v === 'number')
89
+ .join(', ')
90
+ return label || undefined
91
+ }
92
+
93
+ // geoplateforme keeps a simplified geometry in geom and puts the real one,
94
+ // as a JSON string, in properties.truegeometry when returnTrueGeometry is set
95
+ private getGeometry(result: GeocodingResult): Geometry | null {
96
+ const trueGeometry = result.properties?.['truegeometry']
97
+ if (typeof trueGeometry === 'string') {
98
+ return JSON.parse(trueGeometry)
99
+ }
100
+ return (trueGeometry as Geometry) ?? result.geom
101
+ }
102
+
103
+ handleItemSelected(item: AutocompleteItem) {
104
+ const result = item as GeocodingResult
105
+ this.resultSelected.emit(result)
106
+ const geometry = this.getGeometry(result)
107
+ if (geometry) {
108
+ this.bboxSelected.emit({
109
+ bbox: getGeometryBoundingBox(geometry),
110
+ label: this.getDisplayLabel(result),
111
+ })
112
+ }
113
+ }
114
+ }
@@ -1,5 +1,5 @@
1
1
  import { Component, Input, OnInit, inject } from '@angular/core'
2
- import { CommonModule, DatePipe } from '@angular/common'
2
+ import { CommonModule } from '@angular/common'
3
3
  import {
4
4
  catchError,
5
5
  firstValueFrom,
@@ -15,11 +15,15 @@ import { FieldType, FieldValue } from '../utils/service/fields'
15
15
  import { SearchFacade } from '../state/search.facade'
16
16
  import { SearchService } from '../utils/service/search.service'
17
17
  import { FieldsService } from '../utils/service/fields.service'
18
- import { formatUserInfo } from '../../../../../../libs/util/shared/src'
18
+ import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
19
19
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
20
20
 
21
21
  marker('search.filters.summaryLabel.user')
22
22
  marker('search.filters.summaryLabel.changeDate')
23
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate')
24
+ marker('search.filters.summaryLabel.temporalExtent')
25
+
26
+ const OPEN_BOUND = '…'
23
27
 
24
28
  interface DisplayedValue {
25
29
  label: string
@@ -32,13 +36,12 @@ interface DisplayedValue {
32
36
  imports: [CommonModule, BadgeComponent],
33
37
  templateUrl: './search-filters-summary-item.component.html',
34
38
  styleUrls: ['./search-filters-summary-item.component.css'],
35
- providers: [DatePipe],
36
39
  })
37
40
  export class SearchFiltersSummaryItemComponent implements OnInit {
38
41
  private searchFacade = inject(SearchFacade)
39
42
  private searchService = inject(SearchService)
40
43
  private fieldsService = inject(FieldsService)
41
- private datePipe = inject(DatePipe)
44
+ private dateService = inject(DateService)
42
45
  private translate = inject(TranslateService)
43
46
 
44
47
  @Input() fieldName: string
@@ -82,12 +85,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
82
85
  getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
83
86
  return fieldValues.map((value) => {
84
87
  if (this.fieldType === 'dateRange') {
88
+ const { start, end } = value as DateRange
85
89
  return {
86
90
  value,
87
- label: `${this.datePipe.transform(
88
- value.start,
89
- 'dd.MM.yyyy'
90
- )} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
91
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
91
92
  }
92
93
  } else if (this.fieldName === 'user') {
93
94
  return { value, label: formatUserInfo(value) }
@@ -97,6 +98,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
97
98
  })
98
99
  }
99
100
 
101
+ private formatBound(date: Date | undefined): string {
102
+ return date
103
+ ? this.dateService.formatDate(date, {
104
+ day: '2-digit',
105
+ month: '2-digit',
106
+ year: 'numeric',
107
+ })
108
+ : OPEN_BOUND
109
+ }
110
+
100
111
  async removeFilterValue(fieldValue: FieldValue | DateRange) {
101
112
  const currentFieldValues: DisplayedValue[] = await firstValueFrom(
102
113
  this.fieldValues$
@@ -34,7 +34,7 @@ export class SortByComponent implements OnInit {
34
34
  },
35
35
  {
36
36
  label: marker('results.sortBy.dateStamp'),
37
- value: SortByEnum.RESOURCE_DATES,
37
+ value: SortByEnum.RESOURCE_DATE,
38
38
  },
39
39
  {
40
40
  label: marker('results.sortBy.popularity'),
@@ -8,7 +8,6 @@ import {
8
8
  SortByField,
9
9
  } from '../../../../../../libs/common/domain/src/lib/model/search'
10
10
  import { DEFAULT_PAGE_SIZE, FIELDS_SUMMARY } from '../constants'
11
- import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/src'
12
11
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
13
12
 
14
13
  export const SEARCH_FEATURE_KEY = 'searchState'
@@ -56,9 +55,7 @@ export const initSearch = (): SearchStateSearch => {
56
55
  },
57
56
  params: {
58
57
  filters: {},
59
- pageSize: getOptionalSearchConfig()?.LIMIT
60
- ? getOptionalSearchConfig().LIMIT
61
- : DEFAULT_PAGE_SIZE,
58
+ pageSize: DEFAULT_PAGE_SIZE,
62
59
  currentPage: 0,
63
60
  favoritesOnly: false,
64
61
  useSpatialFilter: true,