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

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 (98) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2459 -1974
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +302 -153
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +121 -34
  8. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  9. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  10. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
  11. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  12. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  13. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  14. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  15. package/src/libs/feature/map/src/index.ts +0 -2
  16. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  17. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  18. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  19. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  20. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  21. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  22. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  23. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  24. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  25. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  26. package/src/libs/feature/search/src/index.ts +2 -0
  27. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  28. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +63 -19
  29. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  30. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  31. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  32. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
  33. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  34. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +15 -1
  35. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  36. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  37. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  38. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  39. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  40. package/src/libs/ui/inputs/src/index.ts +2 -0
  41. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  42. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  43. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  44. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  45. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  46. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  48. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  49. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  50. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  52. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  53. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  54. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  55. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  56. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  57. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  58. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  59. package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
  60. package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
  61. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  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 +24 -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 +20 -3
  85. package/translations/en.json +19 -2
  86. package/translations/es.json +18 -1
  87. package/translations/fr.json +20 -3
  88. package/translations/it.json +18 -1
  89. package/translations/nl.json +18 -1
  90. package/translations/pt.json +18 -1
  91. package/translations/sk.json +18 -1
  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/feature/map/src/lib/style/index.ts +0 -2
  95. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  96. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
  97. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  98. /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
@@ -3,6 +3,7 @@ import { createAction, props } from '@ngrx/store'
3
3
  import {
4
4
  CatalogRecord,
5
5
  DatasetFeatureCatalog,
6
+ LinkedRecord,
6
7
  UserFeedback,
7
8
  } from '../../../../../../libs/common/domain/src/lib/model/record'
8
9
 
@@ -54,14 +55,9 @@ export const setRelated = createAction(
54
55
  props<{ related: CatalogRecord[] }>()
55
56
  )
56
57
 
57
- export const setSources = createAction(
58
- '[Metadata view] Set sources',
59
- props<{ sources: CatalogRecord[] }>()
60
- )
61
-
62
- export const setSourceOf = createAction(
63
- '[Metadata view] Set has sources',
64
- props<{ sourceOf: CatalogRecord[] }>()
58
+ export const setLinkedRecords = createAction(
59
+ '[Metadata view] Set associated records',
60
+ props<{ linkedRecords: LinkedRecord[] }>()
65
61
  )
66
62
 
67
63
  /*
@@ -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
@@ -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"
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
+ InjectionToken,
4
5
  Input,
5
6
  OnInit,
6
7
  inject,
@@ -9,6 +10,8 @@ import {
9
10
  Choice,
10
11
  DateRangeDropdownComponent,
11
12
  DropdownMultiselectComponent,
13
+ SpatialExtentDropdownComponent,
14
+ SpatialExtentDropdownError,
12
15
  } from '../../../../../../libs/ui/inputs/src'
13
16
  import { Observable, of, switchMap } from 'rxjs'
14
17
  import { catchError, filter, map, startWith } from 'rxjs/operators'
@@ -22,6 +25,15 @@ import {
22
25
  } from '../utils/service/fields'
23
26
  import { DateRange } from '../../../../../../libs/api/repository/src'
24
27
  import { CommonModule } from '@angular/common'
28
+ import { BoundingBox } from '../../../../../../libs/util/shared/src'
29
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
30
+ import { TranslateService } from '@ngx-translate/core'
31
+
32
+ // in MB, null means no limit
33
+ export const SPATIAL_EXTENT_MAX_FILE_SIZE = new InjectionToken<number | null>(
34
+ 'spatialExtentMaxFileSize',
35
+ { factory: () => null }
36
+ )
25
37
 
26
38
  @Component({
27
39
  selector: 'gn-ui-filter-dropdown',
@@ -33,18 +45,22 @@ import { CommonModule } from '@angular/common'
33
45
  CommonModule,
34
46
  DateRangeDropdownComponent,
35
47
  DropdownMultiselectComponent,
48
+ SpatialExtentDropdownComponent,
36
49
  ],
37
50
  })
38
51
  export class FilterDropdownComponent implements OnInit {
39
52
  private searchFacade = inject(SearchFacade)
40
53
  private searchService = inject(SearchService)
41
54
  private fieldsService = inject(FieldsService)
55
+ private notificationsService = inject(NotificationsService)
56
+ private translateService = inject(TranslateService)
42
57
 
43
58
  @Input() fieldName: string
44
59
  @Input() title: string
45
60
 
61
+ spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
62
+
46
63
  fieldType: FieldType
47
- dateRange: DateRange
48
64
  choices$: Observable<Choice[]>
49
65
  selected$ = this.searchFacade.searchFilters$.pipe(
50
66
  switchMap((filters) =>
@@ -57,15 +73,54 @@ export class FilterDropdownComponent implements OnInit {
57
73
  ) as Observable<FieldValue[]>
58
74
 
59
75
  selectedDateRange$ = this.selected$.pipe(
60
- map((selectedDateRange) => selectedDateRange as DateRange)
76
+ map((selected) => (Array.isArray(selected) ? {} : (selected as DateRange)))
61
77
  ) as Observable<DateRange>
62
78
 
79
+ selectedBoundingBox$ = this.selected$.pipe(
80
+ map((selected) =>
81
+ Array.isArray(selected) && selected.length > 0
82
+ ? (selected as BoundingBox)
83
+ : null
84
+ )
85
+ ) as Observable<BoundingBox | null>
86
+
63
87
  onSelectedValues(values: unknown[]) {
64
88
  this.fieldsService
65
89
  .buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
66
90
  .subscribe((filters) => this.searchService.updateFilters(filters))
67
91
  }
68
92
 
93
+ private spatialExtentErrorNotificationId: number | null = null
94
+
95
+ onBboxChange(bbox: BoundingBox | null) {
96
+ this.fieldsService
97
+ .buildFiltersFromFieldValues({
98
+ [this.fieldName]: bbox,
99
+ })
100
+ .subscribe((filters) => this.searchService.updateFilters(filters))
101
+ this.clearSpatialExtentErrorNotification()
102
+ }
103
+
104
+ onSpatialExtentError(error: SpatialExtentDropdownError) {
105
+ this.clearSpatialExtentErrorNotification()
106
+ this.spatialExtentErrorNotificationId =
107
+ this.notificationsService.showNotification({
108
+ type: 'error',
109
+ title: this.translateService.instant(
110
+ 'search.filters.spatialExtent.error.title'
111
+ ),
112
+ text: this.translateService.instant(error.key, error.params),
113
+ })
114
+ }
115
+
116
+ private clearSpatialExtentErrorNotification() {
117
+ if (this.spatialExtentErrorNotificationId === null) return
118
+ this.notificationsService.removeNotificationById(
119
+ this.spatialExtentErrorNotificationId
120
+ )
121
+ this.spatialExtentErrorNotificationId = null
122
+ }
123
+
69
124
  ngOnInit() {
70
125
  this.fieldType = this.fieldsService.getFieldType(this.fieldName)
71
126
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
@@ -80,22 +135,11 @@ export class FilterDropdownComponent implements OnInit {
80
135
  )
81
136
  }
82
137
 
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
- }
138
+ onDateRangeChange(dateRange: DateRange) {
139
+ this.fieldsService
140
+ .buildFiltersFromFieldValues({
141
+ [this.fieldName]: dateRange,
142
+ })
143
+ .subscribe((filters) => this.searchService.updateFilters(filters))
100
144
  }
101
145
  }
@@ -5,22 +5,31 @@ import {
5
5
  GeocodingResult,
6
6
  queryGeonames,
7
7
  GeonamesOptions,
8
- DataGouvFrOptions,
9
- queryDataGouvFr,
8
+ GeoplateformeOptions,
9
+ queryGeoplateforme,
10
10
  } from '@geospatial-sdk/geocoding'
11
11
  import { from, Observable, throwError } from 'rxjs'
12
12
  import { catchError } from 'rxjs/operators'
13
13
 
14
14
  type GeoadminGeocodingProvider = ['geoadmin', GeoadminOptions]
15
15
  type GeonamesGeocodingProvider = ['geonames', GeonamesOptions]
16
- type DataGouvFrGeocodingProvider = ['data-gouv-fr', DataGouvFrOptions]
16
+ type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
17
17
  export type GeocodingProvider =
18
18
  | GeoadminGeocodingProvider
19
19
  | GeonamesGeocodingProvider
20
- | DataGouvFrGeocodingProvider
20
+ | GeoplateformeGeocodingProvider
21
+
22
+ export const DEFAULT_GEOCODING_PROVIDER = [
23
+ 'geonames',
24
+ { maxRows: 5 },
25
+ ] as GeocodingProvider
21
26
 
22
27
  export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
23
- 'geocoding-provider'
28
+ 'geocoding-provider',
29
+ {
30
+ providedIn: 'root',
31
+ factory: (): GeocodingProvider => DEFAULT_GEOCODING_PROVIDER,
32
+ }
24
33
  )
25
34
 
26
35
  @Injectable({
@@ -42,9 +51,9 @@ export class GeocodingService {
42
51
  queryGeonames(text, this.provider[1] as GeonamesOptions)
43
52
  )
44
53
  break
45
- case 'data-gouv-fr':
54
+ case 'geoplateforme':
46
55
  queryObservable = from(
47
- queryDataGouvFr(text, this.provider[1] as DataGouvFrOptions)
56
+ queryGeoplateforme(text, this.provider[1] as GeoplateformeOptions)
48
57
  )
49
58
  break
50
59
  default:
@@ -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,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$
@@ -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,
@@ -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,9 @@ 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.temporalExtent')
49
+ marker('search.filters.spatialExtent')
46
50
  @Injectable({
47
51
  providedIn: 'root',
48
52
  })
@@ -94,7 +98,17 @@ export class FieldsService {
94
98
  ),
95
99
  user: new UserSearchField(this.injector),
96
100
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
101
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
102
+ this.injector,
103
+ 'desc'
104
+ ),
105
+ temporalExtent: new DateRangeSearchField(
106
+ 'resourceTemporalExtentDateRange',
107
+ this.injector,
108
+ 'desc'
109
+ ),
97
110
  availableServices: new AvailableServicesField(this.injector),
111
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
98
112
  } as Record<string, AbstractSearchField>
99
113
 
100
114
  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