geonetwork-ui 2.11.0-dev.8442dae6e → 2.11.0-dev.8aa6985e0

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 (104) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1572 -869
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +314 -128
  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 +22 -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/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  18. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  20. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  21. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  22. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  23. package/src/libs/feature/map/src/index.ts +0 -1
  24. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  25. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  26. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  27. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  28. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  29. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  30. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  31. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  32. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  33. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  34. package/src/libs/feature/search/src/index.ts +2 -0
  35. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  36. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  37. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  38. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  39. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  40. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  41. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  42. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  43. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  44. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  45. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  46. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  47. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  48. package/src/libs/ui/inputs/src/index.ts +2 -0
  49. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  50. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  51. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  52. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  53. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  54. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  55. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  56. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  57. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  58. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  59. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  60. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  61. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  62. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  63. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  64. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  65. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  66. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  67. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  68. package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
  69. package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
  70. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  71. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  72. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  73. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  74. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  75. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  76. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  77. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  78. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  79. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  80. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  81. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  82. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  83. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  84. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  85. package/src/libs/util/shared/src/index.ts +1 -0
  86. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  87. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  88. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  89. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  90. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  91. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  92. package/tailwind.base.css +5 -0
  93. package/translations/de.json +31 -4
  94. package/translations/en.json +30 -3
  95. package/translations/es.json +28 -1
  96. package/translations/fr.json +31 -4
  97. package/translations/it.json +29 -2
  98. package/translations/nl.json +28 -1
  99. package/translations/pt.json +28 -1
  100. package/translations/sk.json +29 -2
  101. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  102. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  103. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
  104. /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
@@ -0,0 +1,94 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ } from '@angular/core'
8
+ import {
9
+ AssociatedRecord,
10
+ AssociationType,
11
+ associationTypeValues,
12
+ } from '../../../../../../../../../libs/common/domain/src/lib/model/record'
13
+ import {
14
+ CheckToggleComponent,
15
+ DropdownChoice,
16
+ DropdownSelectorComponent,
17
+ TextInputComponent,
18
+ } from '../../../../../../../../../libs/ui/inputs/src'
19
+ import { FormFieldWrapperComponent } from '../../../../../../../../../libs/ui/layout/src'
20
+ import { TranslatePipe } from '@ngx-translate/core'
21
+
22
+ @Component({
23
+ selector: 'gn-ui-form-field-associated-records',
24
+ templateUrl: './form-field-associated-records.component.html',
25
+ styleUrls: ['./form-field-associated-records.component.css'],
26
+ changeDetection: ChangeDetectionStrategy.OnPush,
27
+ standalone: true,
28
+ imports: [
29
+ CheckToggleComponent,
30
+ TextInputComponent,
31
+ DropdownSelectorComponent,
32
+ FormFieldWrapperComponent,
33
+ TranslatePipe,
34
+ ],
35
+ })
36
+ export class FormFieldAssociatedRecordsComponent {
37
+ @Input() value?: AssociatedRecord[]
38
+ @Output() valueChange = new EventEmitter<AssociatedRecord[]>()
39
+
40
+ private get list() {
41
+ return this.value ?? []
42
+ }
43
+ private get first() {
44
+ return this.list[0]
45
+ }
46
+ private get rest() {
47
+ return this.list.slice(1)
48
+ }
49
+
50
+ get hasParentLink() {
51
+ return this.list.length > 0
52
+ }
53
+ get uniqueIdentifier() {
54
+ return this.first?.uniqueIdentifier ?? ''
55
+ }
56
+ get selectedType() {
57
+ return this.first?.associationType
58
+ }
59
+ get choices(): DropdownChoice[] {
60
+ return associationTypeValues.map((value) => ({
61
+ value,
62
+ label: `domain.record.associationType.${value}`,
63
+ }))
64
+ }
65
+
66
+ onToggle(checked: boolean) {
67
+ this.valueChange.emit(
68
+ checked
69
+ ? [
70
+ {
71
+ uniqueIdentifier: '',
72
+ associationType: associationTypeValues[0],
73
+ },
74
+ ...this.rest,
75
+ ]
76
+ : this.rest
77
+ )
78
+ }
79
+
80
+ onUniqueIdentifierChange(uniqueIdentifier: string) {
81
+ this.valueChange.emit(
82
+ uniqueIdentifier
83
+ ? [{ ...this.first, uniqueIdentifier }, ...this.rest]
84
+ : this.rest
85
+ )
86
+ }
87
+
88
+ onTypeChange(associationType: DropdownChoice['value']) {
89
+ this.valueChange.emit([
90
+ { ...this.first, associationType: associationType as AssociationType },
91
+ ...this.rest,
92
+ ])
93
+ }
94
+ }
@@ -1,17 +1,21 @@
1
- <div class="flex gap-2 mb-2">
2
- @for (addOption of addOptions$ | async; track addOption) {
3
- <gn-ui-button type="gray" (buttonClick)="onAdd(addOption.eventName)">
4
- <ng-icon name="iconoirPlus" class="text-primary"></ng-icon> &nbsp;{{
5
- addOption.buttonLabel
6
- }}</gn-ui-button
7
- >
1
+ <div class="flex flex-col gap-2">
2
+ <div class="flex gap-2">
3
+ @for (addOption of addOptions$ | async; track addOption) {
4
+ <gn-ui-button type="gray" (buttonClick)="onAdd(addOption.eventName)">
5
+ <ng-icon name="iconoirPlus" class="text-primary"></ng-icon> &nbsp;{{
6
+ addOption.buttonLabel
7
+ }}</gn-ui-button
8
+ >
9
+ }
10
+ </div>
11
+ @if (extents.length) {
12
+ <gn-ui-sortable-list
13
+ [items]="extents"
14
+ (itemsOrderChange)="onItemsOrderChange($event)"
15
+ [elementTemplate]="template"
16
+ ></gn-ui-sortable-list>
8
17
  }
9
18
  </div>
10
- <gn-ui-sortable-list
11
- [items]="extents"
12
- (itemsOrderChange)="onItemsOrderChange($event)"
13
- [elementTemplate]="template"
14
- ></gn-ui-sortable-list>
15
19
  <ng-template #template let-extent let-index="index">
16
20
  @if (extent.end === undefined) {
17
21
  <div class="border rounded-lg px-4 pb-4">
@@ -98,6 +98,12 @@
98
98
  [recordKind]="recordKind"
99
99
  ></gn-ui-form-field-update-frequency>
100
100
  </ng-container>
101
+ <ng-container *ngSwitchCase="'associatedRecords'">
102
+ <gn-ui-form-field-associated-records
103
+ [value]="valueAsAssociatedRecords"
104
+ (valueChange)="valueChange.emit($event)"
105
+ ></gn-ui-form-field-associated-records>
106
+ </ng-container>
101
107
  <ng-container *ngSwitchCase="'temporalExtents'">
102
108
  <gn-ui-form-field-temporal-extents
103
109
  [value]="valueAsTemporalExtents"
@@ -11,6 +11,7 @@ import {
11
11
  } from '@angular/core'
12
12
  import { MatTooltipModule } from '@angular/material/tooltip'
13
13
  import {
14
+ AssociatedRecord,
14
15
  CatalogRecordKeys,
15
16
  Constraint,
16
17
  DatasetTemporalExtent,
@@ -39,6 +40,7 @@ import {
39
40
  FormFieldComponentName,
40
41
  FormFieldConfig,
41
42
  } from '../../../models'
43
+ import { FormFieldAssociatedRecordsComponent } from './form-field-associated-records/form-field-associated-records.component'
42
44
  import { FormFieldContactsForResourceComponent } from './form-field-contacts-for-resource/form-field-contacts-for-resource.component'
43
45
  import { FormFieldContactsComponent } from './form-field-contacts/form-field-contacts.component'
44
46
  import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component'
@@ -70,6 +72,7 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
70
72
  FormFieldLicenseComponent,
71
73
  FormFieldDateComponent,
72
74
  FormFieldUpdateFrequencyComponent,
75
+ FormFieldAssociatedRecordsComponent,
73
76
  FormFieldTemporalExtentsComponent,
74
77
  FormFieldSimpleComponent,
75
78
  FormFieldRichComponent,
@@ -140,6 +143,9 @@ export class FormFieldComponent {
140
143
  get valueAsUpdateFrequency() {
141
144
  return this.value as UpdateFrequency
142
145
  }
146
+ get valueAsAssociatedRecords() {
147
+ return this.value as Array<AssociatedRecord>
148
+ }
143
149
  get valueAsTemporalExtents() {
144
150
  return this.value as Array<DatasetTemporalExtent>
145
151
  }
@@ -134,6 +134,11 @@ export const DEFAULT_CONFIGURATION: EditorConfig = {
134
134
  },
135
135
  hidden: '${record.kind == "service"}',
136
136
  },
137
+ {
138
+ model: 'associatedRecords',
139
+ formFieldConfig: {},
140
+ hidden: '${record.kind == "service"}',
141
+ },
137
142
  ],
138
143
  },
139
144
 
@@ -10,4 +10,3 @@ export * from './lib/style'
10
10
  export * from './lib/layers-panel/layers-panel.component'
11
11
  export * from './lib/add-layer-from-catalog/add-layer-from-catalog.component'
12
12
  export * from './lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component'
13
- export * from './lib/geocoding/geocoding.component'
@@ -5,6 +5,7 @@
5
5
  (fileChange)="handleFileChange($event)"
6
6
  [accept]="acceptedMimeType.join(',')"
7
7
  [placeholder]="'map.addFromFile.placeholder' | translate"
8
+ textClass="text-gray-900 pl-2 py-2"
8
9
  class="placeholder-grey"
9
10
  ></gn-ui-drag-and-drop-file-input>
10
11
  </div>
@@ -2,18 +2,11 @@ import { NgModule } from '@angular/core'
2
2
  import { StoreModule } from '@ngrx/store'
3
3
  import * as fromMap from './+state/map.reducer'
4
4
  import { MapFacade } from './+state/map.facade'
5
- import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
6
5
 
7
6
  @NgModule({
8
7
  imports: [
9
8
  StoreModule.forFeature(fromMap.MAP_FEATURE_KEY, fromMap.mapReducer),
10
9
  ],
11
- providers: [
12
- MapFacade,
13
- {
14
- provide: GEOCODING_PROVIDER,
15
- useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
16
- },
17
- ],
10
+ providers: [MapFacade],
18
11
  })
19
12
  export class FeatureMapModule {}
@@ -18,10 +18,12 @@ export class NotificationsService {
18
18
  error && console.error(error)
19
19
  const id = Math.floor(Math.random() * 1000000)
20
20
  this.notifications$.next([...this.notifications$.value, { ...content, id }])
21
- if (typeof timeoutMs === 'undefined') return
22
- setTimeout(() => {
23
- this.removeNotificationById(id)
24
- }, timeoutMs)
21
+ if (typeof timeoutMs !== 'undefined') {
22
+ setTimeout(() => {
23
+ this.removeNotificationById(id)
24
+ }, timeoutMs)
25
+ }
26
+ return id
25
27
  }
26
28
 
27
29
  removeNotificationById(id: number) {
@@ -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
@@ -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
  }
@@ -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
+ }