geonetwork-ui 2.11.0-dev.8442dae6e → 2.11.0-dev.9ba9c4d3d

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 (88) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1333 -691
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +228 -81
  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 +70 -10
  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 +13 -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.css +0 -0
  18. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  20. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  21. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  22. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  23. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  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/notifications/src/lib/notifications.service.ts +6 -4
  26. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  27. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  28. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  29. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  30. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  31. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  32. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  33. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  34. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  35. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  36. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  37. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  38. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  40. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  41. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  42. package/src/libs/ui/inputs/src/index.ts +2 -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 +1 -1
  53. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  54. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  55. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  56. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  57. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  58. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  59. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  60. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  61. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  62. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  63. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  64. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  65. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  66. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  67. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  68. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  69. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  70. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  71. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  72. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  73. package/src/libs/util/shared/src/index.ts +1 -0
  74. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  75. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  76. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  77. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  78. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  79. package/tailwind.base.css +5 -0
  80. package/translations/de.json +26 -3
  81. package/translations/en.json +25 -2
  82. package/translations/es.json +23 -0
  83. package/translations/fr.json +26 -3
  84. package/translations/it.json +24 -1
  85. package/translations/nl.json +23 -0
  86. package/translations/pt.json +23 -0
  87. package/translations/sk.json +24 -1
  88. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -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
 
@@ -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>
@@ -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,11 +1,16 @@
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)"
8
6
  ></gn-ui-date-range-dropdown>
7
+ } @else if (fieldType === 'spatialExtent') {
8
+ <gn-ui-spatial-extent-dropdown
9
+ [title]="title"
10
+ [maxFileSizeMb]="spatialExtentMaxFileSize"
11
+ (bboxChange)="onBboxChange($event)"
12
+ (errorChange)="onSpatialExtentError($event)"
13
+ ></gn-ui-spatial-extent-dropdown>
9
14
  } @else {
10
15
  <gn-ui-dropdown-multiselect
11
16
  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,7 +68,7 @@ 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
 
63
74
  onSelectedValues(values: unknown[]) {
@@ -66,6 +77,33 @@ export class FilterDropdownComponent implements OnInit {
66
77
  .subscribe((filters) => this.searchService.updateFilters(filters))
67
78
  }
68
79
 
80
+ private spatialExtentErrorNotificationId: number | null = null
81
+
82
+ onBboxChange(bbox: BoundingBox | null) {
83
+ console.log(bbox)
84
+ this.clearSpatialExtentErrorNotification()
85
+ }
86
+
87
+ onSpatialExtentError(error: SpatialExtentDropdownError) {
88
+ this.clearSpatialExtentErrorNotification()
89
+ this.spatialExtentErrorNotificationId =
90
+ this.notificationsService.showNotification({
91
+ type: 'error',
92
+ title: this.translateService.instant(
93
+ 'search.filters.spatialExtent.error.title'
94
+ ),
95
+ text: this.translateService.instant(error.key, error.params),
96
+ })
97
+ }
98
+
99
+ private clearSpatialExtentErrorNotification() {
100
+ if (this.spatialExtentErrorNotificationId === null) return
101
+ this.notificationsService.removeNotificationById(
102
+ this.spatialExtentErrorNotificationId
103
+ )
104
+ this.spatialExtentErrorNotificationId = null
105
+ }
106
+
69
107
  ngOnInit() {
70
108
  this.fieldType = this.fieldsService.getFieldType(this.fieldName)
71
109
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
@@ -80,22 +118,11 @@ export class FilterDropdownComponent implements OnInit {
80
118
  )
81
119
  }
82
120
 
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
- }
121
+ onDateRangeChange(dateRange: DateRange) {
122
+ this.fieldsService
123
+ .buildFiltersFromFieldValues({
124
+ [this.fieldName]: dateRange,
125
+ })
126
+ .subscribe((filters) => this.searchService.updateFilters(filters))
100
127
  }
101
128
  }
@@ -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,12 +15,14 @@ import { FieldType, FieldValue } from '../utils/service/fields'
15
15
  import { SearchFacade } from '../state/search.facade'
16
16
  import { SearchService } from '../utils/service/search.service'
17
17
  import { FieldsService } from '../utils/service/fields.service'
18
- import { formatUserInfo } from '../../../../../../libs/util/shared/src'
18
+ import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
19
19
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
20
20
 
21
21
  marker('search.filters.summaryLabel.user')
22
22
  marker('search.filters.summaryLabel.changeDate')
23
23
 
24
+ const OPEN_BOUND = '…'
25
+
24
26
  interface DisplayedValue {
25
27
  label: string
26
28
  value: FieldValue | DateRange
@@ -32,13 +34,12 @@ interface DisplayedValue {
32
34
  imports: [CommonModule, BadgeComponent],
33
35
  templateUrl: './search-filters-summary-item.component.html',
34
36
  styleUrls: ['./search-filters-summary-item.component.css'],
35
- providers: [DatePipe],
36
37
  })
37
38
  export class SearchFiltersSummaryItemComponent implements OnInit {
38
39
  private searchFacade = inject(SearchFacade)
39
40
  private searchService = inject(SearchService)
40
41
  private fieldsService = inject(FieldsService)
41
- private datePipe = inject(DatePipe)
42
+ private dateService = inject(DateService)
42
43
  private translate = inject(TranslateService)
43
44
 
44
45
  @Input() fieldName: string
@@ -82,12 +83,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
82
83
  getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
83
84
  return fieldValues.map((value) => {
84
85
  if (this.fieldType === 'dateRange') {
86
+ const { start, end } = value as DateRange
85
87
  return {
86
88
  value,
87
- label: `${this.datePipe.transform(
88
- value.start,
89
- 'dd.MM.yyyy'
90
- )} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
89
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
91
90
  }
92
91
  } else if (this.fieldName === 'user') {
93
92
  return { value, label: formatUserInfo(value) }
@@ -97,6 +96,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
97
96
  })
98
97
  }
99
98
 
99
+ private formatBound(date: Date | undefined): string {
100
+ return date
101
+ ? this.dateService.formatDate(date, {
102
+ day: '2-digit',
103
+ month: '2-digit',
104
+ year: 'numeric',
105
+ })
106
+ : OPEN_BOUND
107
+ }
108
+
100
109
  async removeFilterValue(fieldValue: FieldValue | DateRange) {
101
110
  const currentFieldValues: DisplayedValue[] = await firstValueFrom(
102
111
  this.fieldValues$
@@ -34,7 +34,7 @@ export class SortByComponent implements OnInit {
34
34
  },
35
35
  {
36
36
  label: marker('results.sortBy.dateStamp'),
37
- value: SortByEnum.RESOURCE_DATES,
37
+ value: SortByEnum.RESOURCE_DATE,
38
38
  },
39
39
  {
40
40
  label: marker('results.sortBy.popularity'),
@@ -2,6 +2,7 @@ import { Injectable, Injector, inject } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
5
+ BoundingBoxSearchField,
5
6
  DateRangeSearchField,
6
7
  FieldValue,
7
8
  FullTextSearchField,
@@ -42,7 +43,7 @@ marker('search.filters.producerOrg')
42
43
  marker('search.filters.publisherOrg')
43
44
  marker('search.filters.user')
44
45
  marker('search.filters.changeDate')
45
-
46
+ marker('search.filters.spatialExtent')
46
47
  @Injectable({
47
48
  providedIn: 'root',
48
49
  })
@@ -95,6 +96,7 @@ export class FieldsService {
95
96
  user: new UserSearchField(this.injector),
96
97
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
97
98
  availableServices: new AvailableServicesField(this.injector),
99
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
98
100
  } as Record<string, AbstractSearchField>
99
101
 
100
102
  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,27 @@ export class DateRangeSearchField extends SimpleSearchField {
431
435
  }
432
436
  }
433
437
 
438
+ export class BoundingBoxSearchField extends SimpleSearchField {
439
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
440
+ return of([])
441
+ }
442
+
443
+ getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
444
+ return of({
445
+ [this.esFieldName]: values.map(Number) as BoundingBox,
446
+ })
447
+ }
448
+
449
+ getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
450
+ const filter = filters[this.esFieldName]
451
+ return of(isBoundingBox(filter) ? filter : [])
452
+ }
453
+
454
+ getType(): FieldType {
455
+ return 'spatialExtent'
456
+ }
457
+ }
458
+
434
459
  marker('search.filters.availableServices.view')
435
460
  marker('search.filters.availableServices.download')
436
461
 
@@ -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
@@ -6,14 +6,14 @@ import {
6
6
  Component,
7
7
  ElementRef,
8
8
  EventEmitter,
9
+ inject,
9
10
  Input,
10
11
  OnChanges,
11
12
  OnInit,
12
13
  Output,
13
14
  ViewChild,
14
- inject,
15
15
  } from '@angular/core'
16
- import { MatSort, MatSortModule } from '@angular/material/sort'
16
+ import { MatSort, MatSortModule, Sort } from '@angular/material/sort'
17
17
  import { MatTableModule } from '@angular/material/table'
18
18
  import {
19
19
  TranslateDirective,
@@ -29,12 +29,11 @@ import {
29
29
  } from '@angular/material/paginator'
30
30
  import { CustomMatPaginatorIntl } from './custom.mat.paginator.intl'
31
31
  import { CommonModule } from '@angular/common'
32
- import { BehaviorSubject, filter, firstValueFrom } from 'rxjs'
32
+ import { BehaviorSubject } from 'rxjs'
33
33
  import {
34
34
  LoadingMaskComponent,
35
35
  PopupAlertComponent,
36
36
  } from '../../../../../../libs/ui/widgets/src'
37
- import { LetDirective } from '@ngrx/component'
38
37
 
39
38
  const rowIdPrefix = 'table-item-'
40
39
 
@@ -46,6 +45,11 @@ export interface TableItemModel {
46
45
  [key: string]: TableItemType
47
46
  }
48
47
 
48
+ interface TableColumn {
49
+ name: string
50
+ label: string
51
+ }
52
+
49
53
  @Component({
50
54
  standalone: true,
51
55
  imports: [
@@ -56,7 +60,6 @@ export interface TableItemModel {
56
60
  CommonModule,
57
61
  LoadingMaskComponent,
58
62
  PopupAlertComponent,
59
- LetDirective,
60
63
  TranslatePipe,
61
64
  TranslateDirective,
62
65
  ],
@@ -71,15 +74,21 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
71
74
  private cdr = inject(ChangeDetectorRef)
72
75
  private translateService = inject(TranslateService)
73
76
 
74
- _featureAttributes = []
77
+ columnsFromFeatureCatalog: TableColumn[] = null
75
78
  @Input() set featureAttributes(value: { value: string; label: string }[]) {
76
- this._featureAttributes = value
77
- this.properties$.next(value.map((attr) => attr.value))
79
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
80
+ name: attrs.value,
81
+ label: attrs.label,
82
+ }))
78
83
  }
84
+ columnsFromDataset: TableColumn[] = []
79
85
  @Input() set dataset(value: BaseReader) {
80
86
  this.dataset_ = value
81
87
  this.dataset_.load()
82
- this.dataset_.info.then((info) => (this.count = info.itemsCount))
88
+ this.dataset_.info.then((info) => {
89
+ this.count = info.itemsCount
90
+ this.cdr.detectChanges()
91
+ })
83
92
  }
84
93
  @Input() activeId: TableItemId
85
94
  @Output() selected = new EventEmitter<any>()
@@ -88,13 +97,19 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
88
97
  @ViewChild(MatPaginator) paginator: MatPaginator
89
98
 
90
99
  dataset_: BaseReader
91
- properties$ = new BehaviorSubject<string[]>(null)
92
100
  dataSource: DataTableDataSource
93
101
  headerHeight: number
94
102
  count: number
95
103
  loading$ = new BehaviorSubject<boolean>(false)
96
104
  error = null
97
105
 
106
+ get columns() {
107
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset
108
+ }
109
+ get columnNames() {
110
+ return this.columns.map((c) => c.name)
111
+ }
112
+
98
113
  ngOnInit() {
99
114
  this.dataSource = new DataTableDataSource()
100
115
  }
@@ -110,7 +125,7 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
110
125
  this.setPagination()
111
126
  }
112
127
 
113
- setSort(sort: MatSort) {
128
+ setSort(sort: Sort) {
114
129
  if (!this.dataset_) return
115
130
  if (!sort.active) {
116
131
  this.dataset_.orderBy()
@@ -132,15 +147,12 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
132
147
 
133
148
  async readData() {
134
149
  this.loading$.next(true)
135
- // wait for properties to be read
136
- const properties = await firstValueFrom(
137
- this.properties$.pipe(filter((p) => !!p))
138
- )
139
- const propsWithoutGeom = properties.filter(
140
- (p) => !p.toLowerCase().startsWith('geom')
141
- )
142
- this.dataset_.select(...propsWithoutGeom)
143
150
  try {
151
+ // wait for properties to be read
152
+ if (!this.columnsFromFeatureCatalog) {
153
+ this.columnsFromDataset = await this.dataset_.properties
154
+ }
155
+ this.dataset_.select(...this.columnNames)
144
156
  await this.dataSource.showData(this.dataset_.read())
145
157
  this.error = null
146
158
  } catch (error) {