geonetwork-ui 2.11.0-dev.e7b8ec331 → 2.11.0-dev.f5ee66b24
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.
- package/fesm2022/geonetwork-ui.mjs +1482 -928
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +280 -124
- package/index.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +108 -34
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
- package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
- package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
- package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
- package/src/libs/feature/map/src/index.ts +0 -1
- package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
- package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
- package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
- package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
- package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
- package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
- package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
- package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +5 -1
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/index.ts +2 -0
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
- package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
- package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +215 -0
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
- package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
- package/src/libs/util/app-config/src/lib/model.ts +1 -0
- package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
- package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
- package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
- package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
- package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
- package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
- package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
- package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
- package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
- package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
- package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
- package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
- package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
- package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
- package/src/libs/util/shared/src/index.ts +1 -0
- package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
- package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +17 -3
- package/translations/en.json +16 -2
- package/translations/es.json +15 -1
- package/translations/fr.json +17 -3
- package/translations/it.json +15 -1
- package/translations/nl.json +15 -1
- package/translations/pt.json +15 -1
- package/translations/sk.json +15 -1
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
- package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
- /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
|
@@ -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
|
-
|
|
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.
|
|
80
|
+
on(MetadataViewActions.setLinkedRecords, (state, { linkedRecords }) => ({
|
|
81
81
|
...state,
|
|
82
|
-
|
|
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
|
|
44
|
+
export const getLinkedRecords = createSelector(
|
|
45
45
|
getMdViewState,
|
|
46
|
-
(state: MetadataViewState) => state.
|
|
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
|
-
[
|
|
5
|
-
|
|
6
|
-
|
|
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((
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!end) return
|
|
90
|
-
this.dateRange = { ...this.dateRange, end }
|
|
91
|
-
if (this.dateRange.start && this.dateRange.end) {
|
|
92
|
-
this.fieldsService
|
|
93
|
-
.buildFiltersFromFieldValues({
|
|
94
|
-
[this.fieldName]: this.dateRange,
|
|
95
|
-
})
|
|
96
|
-
.subscribe((filters) => {
|
|
97
|
-
return this.searchService.updateFilters(filters)
|
|
98
|
-
})
|
|
99
|
-
}
|
|
133
|
+
onDateRangeChange(dateRange: DateRange) {
|
|
134
|
+
this.fieldsService
|
|
135
|
+
.buildFiltersFromFieldValues({
|
|
136
|
+
[this.fieldName]: dateRange,
|
|
137
|
+
})
|
|
138
|
+
.subscribe((filters) => this.searchService.updateFilters(filters))
|
|
100
139
|
}
|
|
101
140
|
}
|
|
@@ -20,7 +20,11 @@ export type GeocodingProvider =
|
|
|
20
20
|
| DataGouvFrGeocodingProvider
|
|
21
21
|
|
|
22
22
|
export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
|
|
23
|
-
'geocoding-provider'
|
|
23
|
+
'geocoding-provider',
|
|
24
|
+
{
|
|
25
|
+
providedIn: 'root',
|
|
26
|
+
factory: () => ['geonames', { maxRows: 5 }] as GeocodingProvider,
|
|
27
|
+
}
|
|
24
28
|
)
|
|
25
29
|
|
|
26
30
|
@Injectable({
|
|
@@ -0,0 +1,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
|
|
2
|
+
import { CommonModule } from '@angular/common'
|
|
3
3
|
import {
|
|
4
4
|
catchError,
|
|
5
5
|
firstValueFrom,
|
|
@@ -15,11 +15,14 @@ import { FieldType, FieldValue } from '../utils/service/fields'
|
|
|
15
15
|
import { SearchFacade } from '../state/search.facade'
|
|
16
16
|
import { SearchService } from '../utils/service/search.service'
|
|
17
17
|
import { FieldsService } from '../utils/service/fields.service'
|
|
18
|
-
import { formatUserInfo } from '../../../../../../libs/util/shared/src'
|
|
18
|
+
import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
|
|
19
19
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
20
20
|
|
|
21
21
|
marker('search.filters.summaryLabel.user')
|
|
22
22
|
marker('search.filters.summaryLabel.changeDate')
|
|
23
|
+
marker('search.filters.summaryLabel.resourceCreationRevisionDate')
|
|
24
|
+
|
|
25
|
+
const OPEN_BOUND = '…'
|
|
23
26
|
|
|
24
27
|
interface DisplayedValue {
|
|
25
28
|
label: string
|
|
@@ -32,13 +35,12 @@ interface DisplayedValue {
|
|
|
32
35
|
imports: [CommonModule, BadgeComponent],
|
|
33
36
|
templateUrl: './search-filters-summary-item.component.html',
|
|
34
37
|
styleUrls: ['./search-filters-summary-item.component.css'],
|
|
35
|
-
providers: [DatePipe],
|
|
36
38
|
})
|
|
37
39
|
export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
38
40
|
private searchFacade = inject(SearchFacade)
|
|
39
41
|
private searchService = inject(SearchService)
|
|
40
42
|
private fieldsService = inject(FieldsService)
|
|
41
|
-
private
|
|
43
|
+
private dateService = inject(DateService)
|
|
42
44
|
private translate = inject(TranslateService)
|
|
43
45
|
|
|
44
46
|
@Input() fieldName: string
|
|
@@ -82,12 +84,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
|
82
84
|
getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
|
|
83
85
|
return fieldValues.map((value) => {
|
|
84
86
|
if (this.fieldType === 'dateRange') {
|
|
87
|
+
const { start, end } = value as DateRange
|
|
85
88
|
return {
|
|
86
89
|
value,
|
|
87
|
-
label: `${this.
|
|
88
|
-
value.start,
|
|
89
|
-
'dd.MM.yyyy'
|
|
90
|
-
)} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
|
|
90
|
+
label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
|
|
91
91
|
}
|
|
92
92
|
} else if (this.fieldName === 'user') {
|
|
93
93
|
return { value, label: formatUserInfo(value) }
|
|
@@ -97,6 +97,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
|
97
97
|
})
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
private formatBound(date: Date | undefined): string {
|
|
101
|
+
return date
|
|
102
|
+
? this.dateService.formatDate(date, {
|
|
103
|
+
day: '2-digit',
|
|
104
|
+
month: '2-digit',
|
|
105
|
+
year: 'numeric',
|
|
106
|
+
})
|
|
107
|
+
: OPEN_BOUND
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
async removeFilterValue(fieldValue: FieldValue | DateRange) {
|
|
101
111
|
const currentFieldValues: DisplayedValue[] = await firstValueFrom(
|
|
102
112
|
this.fieldValues$
|
|
@@ -2,6 +2,7 @@ import { Injectable, Injector, inject } from '@angular/core'
|
|
|
2
2
|
import {
|
|
3
3
|
AbstractSearchField,
|
|
4
4
|
AvailableServicesField,
|
|
5
|
+
BoundingBoxSearchField,
|
|
5
6
|
DateRangeSearchField,
|
|
6
7
|
FieldValue,
|
|
7
8
|
FullTextSearchField,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
MultilingualSearchField,
|
|
11
12
|
OrganizationSearchField,
|
|
12
13
|
OwnerSearchField,
|
|
14
|
+
ResourceCreationRevisionDateSearchField,
|
|
13
15
|
ResourceTypeLegacyField,
|
|
14
16
|
SimpleSearchField,
|
|
15
17
|
TranslatedSearchField,
|
|
@@ -42,7 +44,8 @@ marker('search.filters.producerOrg')
|
|
|
42
44
|
marker('search.filters.publisherOrg')
|
|
43
45
|
marker('search.filters.user')
|
|
44
46
|
marker('search.filters.changeDate')
|
|
45
|
-
|
|
47
|
+
marker('search.filters.resourceCreationRevisionDate')
|
|
48
|
+
marker('search.filters.spatialExtent')
|
|
46
49
|
@Injectable({
|
|
47
50
|
providedIn: 'root',
|
|
48
51
|
})
|
|
@@ -94,7 +97,12 @@ export class FieldsService {
|
|
|
94
97
|
),
|
|
95
98
|
user: new UserSearchField(this.injector),
|
|
96
99
|
changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
|
|
100
|
+
resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
|
|
101
|
+
this.injector,
|
|
102
|
+
'desc'
|
|
103
|
+
),
|
|
97
104
|
availableServices: new AvailableServicesField(this.injector),
|
|
105
|
+
spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
|
|
98
106
|
} as Record<string, AbstractSearchField>
|
|
99
107
|
|
|
100
108
|
get supportedFields() {
|
|
@@ -20,10 +20,14 @@ import {
|
|
|
20
20
|
isDateRange,
|
|
21
21
|
METADATA_LANGUAGE,
|
|
22
22
|
} from '../../../../../../../libs/api/repository/src'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
BoundingBox,
|
|
25
|
+
formatUserInfo,
|
|
26
|
+
isBoundingBox,
|
|
27
|
+
} from '../../../../../../../libs/util/shared/src'
|
|
24
28
|
import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
|
|
25
29
|
|
|
26
|
-
export type FieldType = 'values' | 'dateRange'
|
|
30
|
+
export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
|
|
27
31
|
|
|
28
32
|
export type FieldValue = string | number
|
|
29
33
|
export interface FieldAvailableValue {
|
|
@@ -431,6 +435,61 @@ export class DateRangeSearchField extends SimpleSearchField {
|
|
|
431
435
|
}
|
|
432
436
|
}
|
|
433
437
|
|
|
438
|
+
// treats a resource's creation/revision dates as an interval (its min and max), so a record matches
|
|
439
|
+
// when that interval intersects the filter range, not just when a single date falls inside it
|
|
440
|
+
export class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
|
|
441
|
+
constructor(injector: Injector, order: 'asc' | 'desc' = 'desc') {
|
|
442
|
+
super('resourceCreationRevisionDate', injector, order)
|
|
443
|
+
const dateSourceFields = [
|
|
444
|
+
'creationDateForResource',
|
|
445
|
+
'revisionDateForResource',
|
|
446
|
+
]
|
|
447
|
+
const collectSortedDates = dateSourceFields
|
|
448
|
+
.map(
|
|
449
|
+
(field) => `if (doc.containsKey('${field}')) {
|
|
450
|
+
for (def date : doc['${field}']) { dates.add(date.millis); }
|
|
451
|
+
}`
|
|
452
|
+
)
|
|
453
|
+
.join('\n')
|
|
454
|
+
|
|
455
|
+
this.esService.registerRuntimeField(
|
|
456
|
+
'resourceCreationRevisionDateMin',
|
|
457
|
+
`def dates = [];
|
|
458
|
+
${collectSortedDates}
|
|
459
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`,
|
|
460
|
+
'date'
|
|
461
|
+
)
|
|
462
|
+
this.esService.registerRuntimeField(
|
|
463
|
+
'resourceCreationRevisionDateMax',
|
|
464
|
+
`def dates = [];
|
|
465
|
+
${collectSortedDates}
|
|
466
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`,
|
|
467
|
+
'date'
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export class BoundingBoxSearchField extends SimpleSearchField {
|
|
473
|
+
getAvailableValues(): Observable<FieldAvailableValue[]> {
|
|
474
|
+
return of([])
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
|
|
478
|
+
return of({
|
|
479
|
+
[this.esFieldName]: values.map(Number) as BoundingBox,
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
|
|
484
|
+
const filter = filters[this.esFieldName]
|
|
485
|
+
return of(isBoundingBox(filter) ? filter : [])
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
getType(): FieldType {
|
|
489
|
+
return 'spatialExtent'
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
434
493
|
marker('search.filters.availableServices.view')
|
|
435
494
|
marker('search.filters.availableServices.download')
|
|
436
495
|
|
|
@@ -6,33 +6,32 @@
|
|
|
6
6
|
matSort
|
|
7
7
|
(matSortChange)="setSort($event)"
|
|
8
8
|
[matSortDisableClear]="true"
|
|
9
|
-
*ngrxLet="properties$ as properties"
|
|
10
9
|
>
|
|
11
|
-
@for (
|
|
12
|
-
<ng-container [matColumnDef]="
|
|
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
|
-
{{
|
|
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[
|
|
25
|
+
{{ element[column.name] }}
|
|
27
26
|
</td>
|
|
28
27
|
</ng-container>
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
<tr mat-header-row *matHeaderRowDef="
|
|
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:
|
|
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
|
|
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
|
-
|
|
77
|
+
columnsFromFeatureCatalog: TableColumn[] = null
|
|
75
78
|
@Input() set featureAttributes(value: { value: string; label: string }[]) {
|
|
76
|
-
this.
|
|
77
|
-
|
|
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) =>
|
|
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:
|
|
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) {
|
|
@@ -128,7 +128,8 @@ export class ServiceCapabilitiesComponent implements OnInit {
|
|
|
128
128
|
this.loading = true
|
|
129
129
|
this.availableLayers = await getLayers(
|
|
130
130
|
this.apiLinks[0].url.href,
|
|
131
|
-
this.apiLinks[0].accessServiceProtocol
|
|
131
|
+
this.apiLinks[0].accessServiceProtocol,
|
|
132
|
+
true
|
|
132
133
|
)
|
|
133
134
|
this.loading = false
|
|
134
135
|
this.cdr.detectChanges()
|
|
@@ -4,6 +4,7 @@ export * from './lib/button/button.component'
|
|
|
4
4
|
export * from './lib/check-toggle/check-toggle.component'
|
|
5
5
|
export * from './lib/checkbox/checkbox.component'
|
|
6
6
|
export * from './lib/copy-text-button/copy-text-button.component'
|
|
7
|
+
export * from './lib/date-adapter.providers'
|
|
7
8
|
export * from './lib/date-picker/date-picker.component'
|
|
8
9
|
export * from './lib/date-range-dropdown/date-range-dropdown.component'
|
|
9
10
|
export * from './lib/date-range-picker/date-range-picker.component'
|
|
@@ -25,3 +26,4 @@ export * from './lib/url-input/url-input.component'
|
|
|
25
26
|
export * from './lib/viewport-intersector/viewport-intersector.component'
|
|
26
27
|
export * from './lib/search-feature-catalog/search-feature-catalog.component'
|
|
27
28
|
export * from './lib/date-range-inputs/date-range-inputs.component'
|
|
29
|
+
export * from './lib/spatial-extent-dropdown/spatial-extent-dropdown.component'
|