geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.6804f3b43

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 (99) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2533 -1984
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +323 -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 +135 -35
  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/aggregation.model.ts +2 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
  12. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  13. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  14. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  15. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  16. package/src/libs/feature/map/src/index.ts +0 -2
  17. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  18. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  19. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  20. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  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/state/query-params.utils.ts +1 -1
  27. package/src/libs/feature/search/src/index.ts +2 -0
  28. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  29. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +63 -19
  30. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  31. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  32. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  33. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
  34. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  35. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +58 -4
  36. package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
  37. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  38. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  40. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  41. package/src/libs/ui/inputs/src/index.ts +2 -0
  42. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  43. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  44. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  45. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  46. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  48. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  49. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  50. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  52. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  53. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  54. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  55. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  56. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  57. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  58. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -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 +25 -0
  61. package/src/libs/util/app-config/src/lib/fixtures.ts +33 -1
  62. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  63. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  64. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  65. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  66. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  67. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  68. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  69. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  70. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  71. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  72. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  73. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  74. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  75. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  76. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  77. package/src/libs/util/shared/src/index.ts +1 -0
  78. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  79. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  80. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  81. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  82. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  83. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  84. package/tailwind.base.css +5 -0
  85. package/translations/de.json +22 -4
  86. package/translations/en.json +21 -3
  87. package/translations/es.json +19 -1
  88. package/translations/fr.json +26 -8
  89. package/translations/it.json +20 -2
  90. package/translations/nl.json +19 -1
  91. package/translations/pt.json +19 -1
  92. package/translations/sk.json +20 -2
  93. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  94. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  95. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  96. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  97. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
  98. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  99. /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
@@ -33,7 +33,6 @@ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain
33
33
  import { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
34
34
  import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
35
35
  import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
36
- import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
37
36
  import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
38
37
  import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
39
38
  import { matCloseOutline } from '@ng-icons/material-icons/outline'
@@ -48,6 +47,10 @@ marker('notify.reuse.form.error.title')
48
47
  marker('notify.reuse.form.error.body')
49
48
 
50
49
  export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
50
+ // language assigned to the created reuse record; falls back to the current UI language
51
+ export const NEW_RECORD_DEFAULT_LANGUAGE = new InjectionToken<string>(
52
+ 'newRecordDefaultLanguage'
53
+ )
51
54
 
52
55
  @Component({
53
56
  selector: 'gn-ui-notify-reuse-form',
@@ -86,6 +89,9 @@ export class NotifyReuseFormComponent implements OnDestroy {
86
89
  private readonly notificationsService = inject(NotificationsService)
87
90
  private readonly platformServiceInterface = inject(PlatformServiceInterface)
88
91
  reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
92
+ private newRecordDefaultLanguage = inject(NEW_RECORD_DEFAULT_LANGUAGE, {
93
+ optional: true,
94
+ })
89
95
  private locationStrategy = inject(LocationStrategy)
90
96
 
91
97
  me = toSignal(this.platformServiceInterface.getMe())
@@ -158,8 +164,7 @@ export class NotifyReuseFormComponent implements OnDestroy {
158
164
  submit() {
159
165
  if (!this.isFormValid()) return
160
166
  const defaultLang =
161
- getOptionalEditorConfig()?.NEW_RECORD_DEFAULT_LANGUAGE ??
162
- this.translate.currentLang
167
+ this.newRecordDefaultLanguage ?? this.translate.currentLang
163
168
  const onlineResource: OnlineLinkResource = {
164
169
  type: 'link',
165
170
  url: new URL(this.url()),
@@ -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,
@@ -1,7 +1,8 @@
1
- import { Injectable, Injector, inject } from '@angular/core'
1
+ import { inject, Injectable, InjectionToken, Injector } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
5
+ BoundingBoxSearchField,
5
6
  DateRangeSearchField,
6
7
  FieldValue,
7
8
  FullTextSearchField,
@@ -10,10 +11,11 @@ import {
10
11
  MultilingualSearchField,
11
12
  OrganizationSearchField,
12
13
  OwnerSearchField,
14
+ RecordKindField,
15
+ ResourceCreationRevisionDateSearchField,
13
16
  ResourceTypeLegacyField,
14
17
  SimpleSearchField,
15
18
  TranslatedSearchField,
16
- RecordKindField,
17
19
  UserSearchField,
18
20
  } from './fields'
19
21
  import { forkJoin, Observable, of } from 'rxjs'
@@ -42,14 +44,30 @@ marker('search.filters.producerOrg')
42
44
  marker('search.filters.publisherOrg')
43
45
  marker('search.filters.user')
44
46
  marker('search.filters.changeDate')
47
+ marker('search.filters.resourceCreationRevisionDate')
48
+ marker('search.filters.temporalExtent')
49
+ marker('search.filters.spatialExtent')
50
+ marker('search.filters.documentStandard')
51
+
52
+ export interface CustomSearchField {
53
+ name: string
54
+ baseFilter: string
55
+ excludeValues?: string[]
56
+ includeValues?: string[]
57
+ }
45
58
 
59
+ export const CUSTOM_FIELDS = new InjectionToken<CustomSearchField[]>(
60
+ 'custom-fields'
61
+ )
46
62
  @Injectable({
47
63
  providedIn: 'root',
48
64
  })
49
65
  export class FieldsService {
50
66
  protected injector = inject(Injector)
67
+ private customFields =
68
+ inject<CustomSearchField[]>(CUSTOM_FIELDS, { optional: true }) ?? []
51
69
 
52
- protected fields = {
70
+ private baseFields: Record<string, AbstractSearchField> = {
53
71
  organization: new OrganizationSearchField(this.injector),
54
72
  format: new SimpleSearchField('format', this.injector, 'asc'),
55
73
  resourceType: new ResourceTypeLegacyField(this.injector), // Deprecated, use `recordKind` instead
@@ -94,8 +112,44 @@ export class FieldsService {
94
112
  ),
95
113
  user: new UserSearchField(this.injector),
96
114
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
115
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
116
+ this.injector,
117
+ 'desc'
118
+ ),
119
+ temporalExtent: new DateRangeSearchField(
120
+ 'resourceTemporalExtentDateRange',
121
+ this.injector,
122
+ 'desc'
123
+ ),
97
124
  availableServices: new AvailableServicesField(this.injector),
98
- } as Record<string, AbstractSearchField>
125
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
126
+ }
127
+
128
+ protected fields: Record<string, AbstractSearchField>
129
+
130
+ constructor() {
131
+ this.fields = { ...this.baseFields }
132
+ for (const customField of this.customFields) {
133
+ const baseField = this.baseFields[customField.baseFilter]
134
+ if (!(baseField instanceof SimpleSearchField)) {
135
+ console.warn(
136
+ `The custom field '${customField.name}' relies on a base field '${
137
+ customField.baseFilter
138
+ }' that is not supported. This field will be ignored.`
139
+ )
140
+ continue
141
+ }
142
+ const newField = baseField.clone()
143
+ newField.setFieldIdentifier(customField.name)
144
+ if (customField.includeValues) {
145
+ newField.includeValues = customField.includeValues
146
+ }
147
+ if (customField.excludeValues) {
148
+ newField.excludeValues = customField.excludeValues
149
+ }
150
+ this.fields[customField.name] = newField
151
+ }
152
+ }
99
153
 
100
154
  get supportedFields() {
101
155
  return Object.keys(this.fields)