geonetwork-ui 2.11.0-dev.96d0c6ce2 → 2.11.0-dev.9bd41a961

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 (95) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1506 -938
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +295 -127
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +130 -37
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/map/src/index.ts +0 -1
  18. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  19. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  20. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  21. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  22. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  23. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  24. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  25. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  26. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  27. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  28. package/src/libs/feature/search/src/index.ts +2 -0
  29. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  30. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  31. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +5 -1
  32. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  33. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  34. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  35. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  36. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  37. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  38. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  40. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  41. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  42. package/src/libs/ui/inputs/src/index.ts +2 -0
  43. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  44. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  45. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  46. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  48. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  49. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  50. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  52. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  53. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  54. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  55. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  56. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  57. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  58. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
  59. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  60. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  61. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  62. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  63. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  64. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  65. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  66. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  67. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  68. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  69. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  70. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  71. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  72. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  73. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  74. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  75. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  76. package/src/libs/util/shared/src/index.ts +1 -0
  77. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  78. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  79. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  80. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  81. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  82. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  83. package/tailwind.base.css +5 -0
  84. package/translations/de.json +18 -4
  85. package/translations/en.json +17 -3
  86. package/translations/es.json +15 -1
  87. package/translations/fr.json +18 -4
  88. package/translations/it.json +16 -2
  89. package/translations/nl.json +15 -1
  90. package/translations/pt.json +15 -1
  91. package/translations/sk.json +16 -2
  92. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  93. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  94. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  95. /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
@@ -70,25 +70,16 @@ export class MdViewEffects {
70
70
  )
71
71
  )
72
72
 
73
- loadSources$ = createEffect(() =>
73
+ loadLinkedRecords$ = createEffect(() =>
74
74
  this.actions$.pipe(
75
75
  ofType(MdViewActions.loadFullMetadataSuccess),
76
- switchMap(({ full }) => this.recordsRepository.getSources(full)),
77
- map((sources) => {
78
- return MdViewActions.setSources({ sources })
76
+ switchMap(({ full }) => this.recordsRepository.getLinkedRecords(full)),
77
+ map((linkedRecords) => {
78
+ return MdViewActions.setLinkedRecords({ linkedRecords })
79
79
  }),
80
- catchError(() => of(MdViewActions.setSources({ sources: null })))
81
- )
82
- )
83
-
84
- loadSourceOf$ = createEffect(() =>
85
- this.actions$.pipe(
86
- ofType(MdViewActions.loadFullMetadataSuccess),
87
- switchMap(({ full }) => this.recordsRepository.getSourceOf(full)),
88
- map((sourceOf) => {
89
- return MdViewActions.setSourceOf({ sourceOf })
90
- }),
91
- catchError(() => of(MdViewActions.setSourceOf({ sourceOf: null })))
80
+ catchError(() =>
81
+ of(MdViewActions.setLinkedRecords({ linkedRecords: null }))
82
+ )
92
83
  )
93
84
  )
94
85
 
@@ -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
 
@@ -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,19 @@
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
+ ></gn-ui-spatial-extent-dropdown>
9
17
  } @else {
10
18
  <gn-ui-dropdown-multiselect
11
19
  class="w-full"
@@ -9,6 +9,8 @@ import {
9
9
  Choice,
10
10
  DateRangeDropdownComponent,
11
11
  DropdownMultiselectComponent,
12
+ SpatialExtentDropdownComponent,
13
+ SpatialExtentDropdownError,
12
14
  } from '../../../../../../libs/ui/inputs/src'
13
15
  import { Observable, of, switchMap } from 'rxjs'
14
16
  import { catchError, filter, map, startWith } from 'rxjs/operators'
@@ -22,6 +24,10 @@ import {
22
24
  } from '../utils/service/fields'
23
25
  import { DateRange } from '../../../../../../libs/api/repository/src'
24
26
  import { CommonModule } from '@angular/common'
27
+ import { BoundingBox } from '../../../../../../libs/util/shared/src'
28
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
29
+ import { TranslateService } from '@ngx-translate/core'
30
+ import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/src'
25
31
 
26
32
  @Component({
27
33
  selector: 'gn-ui-filter-dropdown',
@@ -33,18 +39,23 @@ import { CommonModule } from '@angular/common'
33
39
  CommonModule,
34
40
  DateRangeDropdownComponent,
35
41
  DropdownMultiselectComponent,
42
+ SpatialExtentDropdownComponent,
36
43
  ],
37
44
  })
38
45
  export class FilterDropdownComponent implements OnInit {
39
46
  private searchFacade = inject(SearchFacade)
40
47
  private searchService = inject(SearchService)
41
48
  private fieldsService = inject(FieldsService)
49
+ private notificationsService = inject(NotificationsService)
50
+ private translateService = inject(TranslateService)
42
51
 
43
52
  @Input() fieldName: string
44
53
  @Input() title: string
45
54
 
55
+ spatialExtentMaxFileSize =
56
+ getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE
57
+
46
58
  fieldType: FieldType
47
- dateRange: DateRange
48
59
  choices$: Observable<Choice[]>
49
60
  selected$ = this.searchFacade.searchFilters$.pipe(
50
61
  switchMap((filters) =>
@@ -57,15 +68,54 @@ export class FilterDropdownComponent implements OnInit {
57
68
  ) as Observable<FieldValue[]>
58
69
 
59
70
  selectedDateRange$ = this.selected$.pipe(
60
- map((selectedDateRange) => selectedDateRange as DateRange)
71
+ map((selected) => (Array.isArray(selected) ? {} : (selected as DateRange)))
61
72
  ) as Observable<DateRange>
62
73
 
74
+ selectedBoundingBox$ = this.selected$.pipe(
75
+ map((selected) =>
76
+ Array.isArray(selected) && selected.length > 0
77
+ ? (selected as BoundingBox)
78
+ : null
79
+ )
80
+ ) as Observable<BoundingBox | null>
81
+
63
82
  onSelectedValues(values: unknown[]) {
64
83
  this.fieldsService
65
84
  .buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
66
85
  .subscribe((filters) => this.searchService.updateFilters(filters))
67
86
  }
68
87
 
88
+ private spatialExtentErrorNotificationId: number | null = null
89
+
90
+ onBboxChange(bbox: BoundingBox | null) {
91
+ this.fieldsService
92
+ .buildFiltersFromFieldValues({
93
+ [this.fieldName]: bbox,
94
+ })
95
+ .subscribe((filters) => this.searchService.updateFilters(filters))
96
+ this.clearSpatialExtentErrorNotification()
97
+ }
98
+
99
+ onSpatialExtentError(error: SpatialExtentDropdownError) {
100
+ this.clearSpatialExtentErrorNotification()
101
+ this.spatialExtentErrorNotificationId =
102
+ this.notificationsService.showNotification({
103
+ type: 'error',
104
+ title: this.translateService.instant(
105
+ 'search.filters.spatialExtent.error.title'
106
+ ),
107
+ text: this.translateService.instant(error.key, error.params),
108
+ })
109
+ }
110
+
111
+ private clearSpatialExtentErrorNotification() {
112
+ if (this.spatialExtentErrorNotificationId === null) return
113
+ this.notificationsService.removeNotificationById(
114
+ this.spatialExtentErrorNotificationId
115
+ )
116
+ this.spatialExtentErrorNotificationId = null
117
+ }
118
+
69
119
  ngOnInit() {
70
120
  this.fieldType = this.fieldsService.getFieldType(this.fieldName)
71
121
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
@@ -80,22 +130,11 @@ export class FilterDropdownComponent implements OnInit {
80
130
  )
81
131
  }
82
132
 
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
- }
133
+ onDateRangeChange(dateRange: DateRange) {
134
+ this.fieldsService
135
+ .buildFiltersFromFieldValues({
136
+ [this.fieldName]: dateRange,
137
+ })
138
+ .subscribe((filters) => this.searchService.updateFilters(filters))
100
139
  }
101
140
  }
@@ -20,7 +20,11 @@ export type GeocodingProvider =
20
20
  | DataGouvFrGeocodingProvider
21
21
 
22
22
  export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
23
- 'geocoding-provider'
23
+ 'geocoding-provider',
24
+ {
25
+ providedIn: 'root',
26
+ factory: () => ['geonames', { maxRows: 5 }] as GeocodingProvider,
27
+ }
24
28
  )
25
29
 
26
30
  @Injectable({
@@ -0,0 +1,8 @@
1
+ <gn-ui-autocomplete
2
+ [action]="searchAction"
3
+ [displayWithFn]="displayWithFn"
4
+ [placeholder]="placeholder"
5
+ [displayWithTemplate]="displayWithTemplate"
6
+ (itemSelected)="handleItemSelected($event)"
7
+ >
8
+ </gn-ui-autocomplete>
@@ -0,0 +1,40 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ inject,
6
+ Input,
7
+ Output,
8
+ TemplateRef,
9
+ } from '@angular/core'
10
+ import { Observable } from 'rxjs'
11
+ import { GeocodingResult } from '@geospatial-sdk/geocoding'
12
+ import {
13
+ AutocompleteComponent,
14
+ AutocompleteItem,
15
+ } from '../../../../../../libs/ui/inputs/src'
16
+ import { GeocodingService } from '../geocoding/geocoding.service'
17
+
18
+ @Component({
19
+ selector: 'gn-ui-location-search',
20
+ templateUrl: './location-search.component.html',
21
+ changeDetection: ChangeDetectionStrategy.OnPush,
22
+ standalone: true,
23
+ imports: [AutocompleteComponent],
24
+ })
25
+ export class LocationSearchComponent {
26
+ private geocodingService = inject(GeocodingService)
27
+
28
+ @Input() placeholder = ''
29
+ @Input() displayWithTemplate?: TemplateRef<{ $implicit: GeocodingResult }>
30
+ @Output() resultSelected = new EventEmitter<GeocodingResult>()
31
+
32
+ displayWithFn = (item: AutocompleteItem) => (item as GeocodingResult).label
33
+
34
+ searchAction = (text: string): Observable<AutocompleteItem[]> =>
35
+ this.geocodingService.query(text)
36
+
37
+ handleItemSelected(item: AutocompleteItem) {
38
+ this.resultSelected.emit(item as GeocodingResult)
39
+ }
40
+ }
@@ -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,14 @@ 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
+
25
+ const OPEN_BOUND = '…'
23
26
 
24
27
  interface DisplayedValue {
25
28
  label: string
@@ -32,13 +35,12 @@ interface DisplayedValue {
32
35
  imports: [CommonModule, BadgeComponent],
33
36
  templateUrl: './search-filters-summary-item.component.html',
34
37
  styleUrls: ['./search-filters-summary-item.component.css'],
35
- providers: [DatePipe],
36
38
  })
37
39
  export class SearchFiltersSummaryItemComponent implements OnInit {
38
40
  private searchFacade = inject(SearchFacade)
39
41
  private searchService = inject(SearchService)
40
42
  private fieldsService = inject(FieldsService)
41
- private datePipe = inject(DatePipe)
43
+ private dateService = inject(DateService)
42
44
  private translate = inject(TranslateService)
43
45
 
44
46
  @Input() fieldName: string
@@ -82,12 +84,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
82
84
  getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
83
85
  return fieldValues.map((value) => {
84
86
  if (this.fieldType === 'dateRange') {
87
+ const { start, end } = value as DateRange
85
88
  return {
86
89
  value,
87
- label: `${this.datePipe.transform(
88
- value.start,
89
- 'dd.MM.yyyy'
90
- )} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
90
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
91
91
  }
92
92
  } else if (this.fieldName === 'user') {
93
93
  return { value, label: formatUserInfo(value) }
@@ -97,6 +97,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
97
97
  })
98
98
  }
99
99
 
100
+ private formatBound(date: Date | undefined): string {
101
+ return date
102
+ ? this.dateService.formatDate(date, {
103
+ day: '2-digit',
104
+ month: '2-digit',
105
+ year: 'numeric',
106
+ })
107
+ : OPEN_BOUND
108
+ }
109
+
100
110
  async removeFilterValue(fieldValue: FieldValue | DateRange) {
101
111
  const currentFieldValues: DisplayedValue[] = await firstValueFrom(
102
112
  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'),
@@ -2,6 +2,7 @@ import { Injectable, Injector, inject } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
5
+ BoundingBoxSearchField,
5
6
  DateRangeSearchField,
6
7
  FieldValue,
7
8
  FullTextSearchField,
@@ -10,6 +11,7 @@ import {
10
11
  MultilingualSearchField,
11
12
  OrganizationSearchField,
12
13
  OwnerSearchField,
14
+ ResourceCreationRevisionDateSearchField,
13
15
  ResourceTypeLegacyField,
14
16
  SimpleSearchField,
15
17
  TranslatedSearchField,
@@ -42,7 +44,8 @@ marker('search.filters.producerOrg')
42
44
  marker('search.filters.publisherOrg')
43
45
  marker('search.filters.user')
44
46
  marker('search.filters.changeDate')
45
-
47
+ marker('search.filters.resourceCreationRevisionDate')
48
+ marker('search.filters.spatialExtent')
46
49
  @Injectable({
47
50
  providedIn: 'root',
48
51
  })
@@ -94,7 +97,12 @@ export class FieldsService {
94
97
  ),
95
98
  user: new UserSearchField(this.injector),
96
99
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
100
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
101
+ this.injector,
102
+ 'desc'
103
+ ),
97
104
  availableServices: new AvailableServicesField(this.injector),
105
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
98
106
  } as Record<string, AbstractSearchField>
99
107
 
100
108
  get supportedFields() {
@@ -20,10 +20,14 @@ import {
20
20
  isDateRange,
21
21
  METADATA_LANGUAGE,
22
22
  } from '../../../../../../../libs/api/repository/src'
23
- import { formatUserInfo } from '../../../../../../../libs/util/shared/src'
23
+ import {
24
+ BoundingBox,
25
+ formatUserInfo,
26
+ isBoundingBox,
27
+ } from '../../../../../../../libs/util/shared/src'
24
28
  import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
25
29
 
26
- export type FieldType = 'values' | 'dateRange'
30
+ export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
27
31
 
28
32
  export type FieldValue = string | number
29
33
  export interface FieldAvailableValue {
@@ -431,6 +435,61 @@ export class DateRangeSearchField extends SimpleSearchField {
431
435
  }
432
436
  }
433
437
 
438
+ // treats a resource's creation/revision dates as an interval (its min and max), so a record matches
439
+ // when that interval intersects the filter range, not just when a single date falls inside it
440
+ export class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
441
+ constructor(injector: Injector, order: 'asc' | 'desc' = 'desc') {
442
+ super('resourceCreationRevisionDate', injector, order)
443
+ const dateSourceFields = [
444
+ 'creationDateForResource',
445
+ 'revisionDateForResource',
446
+ ]
447
+ const collectSortedDates = dateSourceFields
448
+ .map(
449
+ (field) => `if (doc.containsKey('${field}')) {
450
+ for (def date : doc['${field}']) { dates.add(date.millis); }
451
+ }`
452
+ )
453
+ .join('\n')
454
+
455
+ this.esService.registerRuntimeField(
456
+ 'resourceCreationRevisionDateMin',
457
+ `def dates = [];
458
+ ${collectSortedDates}
459
+ if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`,
460
+ 'date'
461
+ )
462
+ this.esService.registerRuntimeField(
463
+ 'resourceCreationRevisionDateMax',
464
+ `def dates = [];
465
+ ${collectSortedDates}
466
+ if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`,
467
+ 'date'
468
+ )
469
+ }
470
+ }
471
+
472
+ export class BoundingBoxSearchField extends SimpleSearchField {
473
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
474
+ return of([])
475
+ }
476
+
477
+ getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
478
+ return of({
479
+ [this.esFieldName]: values.map(Number) as BoundingBox,
480
+ })
481
+ }
482
+
483
+ getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
484
+ const filter = filters[this.esFieldName]
485
+ return of(isBoundingBox(filter) ? filter : [])
486
+ }
487
+
488
+ getType(): FieldType {
489
+ return 'spatialExtent'
490
+ }
491
+ }
492
+
434
493
  marker('search.filters.availableServices.view')
435
494
  marker('search.filters.availableServices.download')
436
495
 
@@ -6,33 +6,32 @@
6
6
  matSort
7
7
  (matSortChange)="setSort($event)"
8
8
  [matSortDisableClear]="true"
9
- *ngrxLet="properties$ as properties"
10
9
  >
11
- @for (attr of _featureAttributes; track attr) {
12
- <ng-container [matColumnDef]="attr.value">
10
+ @for (column of columns; track column) {
11
+ <ng-container [matColumnDef]="column.name">
13
12
  <th
14
13
  mat-header-cell
15
14
  *matHeaderCellDef
16
15
  mat-sort-header
17
16
  class="text-sm text-black bg-white"
18
17
  >
19
- {{ attr.label }}
18
+ {{ column.label }}
20
19
  </th>
21
20
  <td
22
21
  mat-cell
23
22
  *matCellDef="let element"
24
23
  class="whitespace-nowrap pr-1 truncate"
25
24
  >
26
- {{ element[attr.value] }}
25
+ {{ element[column.name] }}
27
26
  </td>
28
27
  </ng-container>
29
28
  }
30
29
 
31
- <tr mat-header-row *matHeaderRowDef="properties; sticky: true"></tr>
30
+ <tr mat-header-row *matHeaderRowDef="columnNames; sticky: true"></tr>
32
31
  <tr
33
32
  [id]="getRowEltId(row.id)"
34
33
  mat-row
35
- *matRowDef="let row; columns: properties"
34
+ *matRowDef="let row; columns: columnNames"
36
35
  (click)="selected.emit(row)"
37
36
  [class.active]="
38
37
  activeId !== undefined && activeId !== null && row.id === activeId