geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.6804f3b43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/geonetwork-ui.mjs +2533 -1984
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +323 -153
- 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 +135 -35
- 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/aggregation.model.ts +2 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
- 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 -2
- 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/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
- 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 +63 -19
- package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
- 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 +19 -8
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +58 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
- 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 +221 -0
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -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 +25 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +33 -1
- package/src/libs/util/app-config/src/lib/model.ts +15 -1
- 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 +24 -0
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +22 -4
- package/translations/en.json +21 -3
- package/translations/es.json +19 -1
- package/translations/fr.json +26 -8
- package/translations/it.json +20 -2
- package/translations/nl.json +19 -1
- package/translations/pt.json +19 -1
- package/translations/sk.json +20 -2
- 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/feature/map/src/lib/style/index.ts +0 -2
- package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
- package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
- 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
package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts
CHANGED
|
@@ -33,7 +33,6 @@ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain
|
|
|
33
33
|
import { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
|
|
34
34
|
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
35
35
|
import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
|
|
36
|
-
import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
|
|
37
36
|
import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
|
|
38
37
|
import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
|
|
39
38
|
import { matCloseOutline } from '@ng-icons/material-icons/outline'
|
|
@@ -48,6 +47,10 @@ marker('notify.reuse.form.error.title')
|
|
|
48
47
|
marker('notify.reuse.form.error.body')
|
|
49
48
|
|
|
50
49
|
export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
|
|
50
|
+
// language assigned to the created reuse record; falls back to the current UI language
|
|
51
|
+
export const NEW_RECORD_DEFAULT_LANGUAGE = new InjectionToken<string>(
|
|
52
|
+
'newRecordDefaultLanguage'
|
|
53
|
+
)
|
|
51
54
|
|
|
52
55
|
@Component({
|
|
53
56
|
selector: 'gn-ui-notify-reuse-form',
|
|
@@ -86,6 +89,9 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
86
89
|
private readonly notificationsService = inject(NotificationsService)
|
|
87
90
|
private readonly platformServiceInterface = inject(PlatformServiceInterface)
|
|
88
91
|
reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
|
|
92
|
+
private newRecordDefaultLanguage = inject(NEW_RECORD_DEFAULT_LANGUAGE, {
|
|
93
|
+
optional: true,
|
|
94
|
+
})
|
|
89
95
|
private locationStrategy = inject(LocationStrategy)
|
|
90
96
|
|
|
91
97
|
me = toSignal(this.platformServiceInterface.getMe())
|
|
@@ -158,8 +164,7 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
158
164
|
submit() {
|
|
159
165
|
if (!this.isFormValid()) return
|
|
160
166
|
const defaultLang =
|
|
161
|
-
|
|
162
|
-
this.translate.currentLang
|
|
167
|
+
this.newRecordDefaultLanguage ?? this.translate.currentLang
|
|
163
168
|
const onlineResource: OnlineLinkResource = {
|
|
164
169
|
type: 'link',
|
|
165
170
|
url: new URL(this.url()),
|
|
@@ -3,6 +3,7 @@ import { createAction, props } from '@ngrx/store'
|
|
|
3
3
|
import {
|
|
4
4
|
CatalogRecord,
|
|
5
5
|
DatasetFeatureCatalog,
|
|
6
|
+
LinkedRecord,
|
|
6
7
|
UserFeedback,
|
|
7
8
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
8
9
|
|
|
@@ -54,14 +55,9 @@ export const setRelated = createAction(
|
|
|
54
55
|
props<{ related: CatalogRecord[] }>()
|
|
55
56
|
)
|
|
56
57
|
|
|
57
|
-
export const
|
|
58
|
-
'[Metadata view] Set
|
|
59
|
-
props<{
|
|
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
|
-
|
|
73
|
+
loadLinkedRecords$ = createEffect(() =>
|
|
74
74
|
this.actions$.pipe(
|
|
75
75
|
ofType(MdViewActions.loadFullMetadataSuccess),
|
|
76
|
-
switchMap(({ full }) => this.recordsRepository.
|
|
77
|
-
map((
|
|
78
|
-
return MdViewActions.
|
|
76
|
+
switchMap(({ full }) => this.recordsRepository.getLinkedRecords(full)),
|
|
77
|
+
map((linkedRecords) => {
|
|
78
|
+
return MdViewActions.setLinkedRecords({ linkedRecords })
|
|
79
79
|
}),
|
|
80
|
-
catchError(() =>
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
|
+
InjectionToken,
|
|
4
5
|
Input,
|
|
5
6
|
OnInit,
|
|
6
7
|
inject,
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
Choice,
|
|
10
11
|
DateRangeDropdownComponent,
|
|
11
12
|
DropdownMultiselectComponent,
|
|
13
|
+
SpatialExtentDropdownComponent,
|
|
14
|
+
SpatialExtentDropdownError,
|
|
12
15
|
} from '../../../../../../libs/ui/inputs/src'
|
|
13
16
|
import { Observable, of, switchMap } from 'rxjs'
|
|
14
17
|
import { catchError, filter, map, startWith } from 'rxjs/operators'
|
|
@@ -22,6 +25,15 @@ import {
|
|
|
22
25
|
} from '../utils/service/fields'
|
|
23
26
|
import { DateRange } from '../../../../../../libs/api/repository/src'
|
|
24
27
|
import { CommonModule } from '@angular/common'
|
|
28
|
+
import { BoundingBox } from '../../../../../../libs/util/shared/src'
|
|
29
|
+
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
30
|
+
import { TranslateService } from '@ngx-translate/core'
|
|
31
|
+
|
|
32
|
+
// in MB, null means no limit
|
|
33
|
+
export const SPATIAL_EXTENT_MAX_FILE_SIZE = new InjectionToken<number | null>(
|
|
34
|
+
'spatialExtentMaxFileSize',
|
|
35
|
+
{ factory: () => null }
|
|
36
|
+
)
|
|
25
37
|
|
|
26
38
|
@Component({
|
|
27
39
|
selector: 'gn-ui-filter-dropdown',
|
|
@@ -33,18 +45,22 @@ import { CommonModule } from '@angular/common'
|
|
|
33
45
|
CommonModule,
|
|
34
46
|
DateRangeDropdownComponent,
|
|
35
47
|
DropdownMultiselectComponent,
|
|
48
|
+
SpatialExtentDropdownComponent,
|
|
36
49
|
],
|
|
37
50
|
})
|
|
38
51
|
export class FilterDropdownComponent implements OnInit {
|
|
39
52
|
private searchFacade = inject(SearchFacade)
|
|
40
53
|
private searchService = inject(SearchService)
|
|
41
54
|
private fieldsService = inject(FieldsService)
|
|
55
|
+
private notificationsService = inject(NotificationsService)
|
|
56
|
+
private translateService = inject(TranslateService)
|
|
42
57
|
|
|
43
58
|
@Input() fieldName: string
|
|
44
59
|
@Input() title: string
|
|
45
60
|
|
|
61
|
+
spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
|
|
62
|
+
|
|
46
63
|
fieldType: FieldType
|
|
47
|
-
dateRange: DateRange
|
|
48
64
|
choices$: Observable<Choice[]>
|
|
49
65
|
selected$ = this.searchFacade.searchFilters$.pipe(
|
|
50
66
|
switchMap((filters) =>
|
|
@@ -57,15 +73,54 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
57
73
|
) as Observable<FieldValue[]>
|
|
58
74
|
|
|
59
75
|
selectedDateRange$ = this.selected$.pipe(
|
|
60
|
-
map((
|
|
76
|
+
map((selected) => (Array.isArray(selected) ? {} : (selected as DateRange)))
|
|
61
77
|
) as Observable<DateRange>
|
|
62
78
|
|
|
79
|
+
selectedBoundingBox$ = this.selected$.pipe(
|
|
80
|
+
map((selected) =>
|
|
81
|
+
Array.isArray(selected) && selected.length > 0
|
|
82
|
+
? (selected as BoundingBox)
|
|
83
|
+
: null
|
|
84
|
+
)
|
|
85
|
+
) as Observable<BoundingBox | null>
|
|
86
|
+
|
|
63
87
|
onSelectedValues(values: unknown[]) {
|
|
64
88
|
this.fieldsService
|
|
65
89
|
.buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
|
|
66
90
|
.subscribe((filters) => this.searchService.updateFilters(filters))
|
|
67
91
|
}
|
|
68
92
|
|
|
93
|
+
private spatialExtentErrorNotificationId: number | null = null
|
|
94
|
+
|
|
95
|
+
onBboxChange(bbox: BoundingBox | null) {
|
|
96
|
+
this.fieldsService
|
|
97
|
+
.buildFiltersFromFieldValues({
|
|
98
|
+
[this.fieldName]: bbox,
|
|
99
|
+
})
|
|
100
|
+
.subscribe((filters) => this.searchService.updateFilters(filters))
|
|
101
|
+
this.clearSpatialExtentErrorNotification()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onSpatialExtentError(error: SpatialExtentDropdownError) {
|
|
105
|
+
this.clearSpatialExtentErrorNotification()
|
|
106
|
+
this.spatialExtentErrorNotificationId =
|
|
107
|
+
this.notificationsService.showNotification({
|
|
108
|
+
type: 'error',
|
|
109
|
+
title: this.translateService.instant(
|
|
110
|
+
'search.filters.spatialExtent.error.title'
|
|
111
|
+
),
|
|
112
|
+
text: this.translateService.instant(error.key, error.params),
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private clearSpatialExtentErrorNotification() {
|
|
117
|
+
if (this.spatialExtentErrorNotificationId === null) return
|
|
118
|
+
this.notificationsService.removeNotificationById(
|
|
119
|
+
this.spatialExtentErrorNotificationId
|
|
120
|
+
)
|
|
121
|
+
this.spatialExtentErrorNotificationId = null
|
|
122
|
+
}
|
|
123
|
+
|
|
69
124
|
ngOnInit() {
|
|
70
125
|
this.fieldType = this.fieldsService.getFieldType(this.fieldName)
|
|
71
126
|
this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
|
|
@@ -80,22 +135,11 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
80
135
|
)
|
|
81
136
|
}
|
|
82
137
|
|
|
83
|
-
|
|
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
|
-
}
|
|
138
|
+
onDateRangeChange(dateRange: DateRange) {
|
|
139
|
+
this.fieldsService
|
|
140
|
+
.buildFiltersFromFieldValues({
|
|
141
|
+
[this.fieldName]: dateRange,
|
|
142
|
+
})
|
|
143
|
+
.subscribe((filters) => this.searchService.updateFilters(filters))
|
|
100
144
|
}
|
|
101
145
|
}
|
|
@@ -5,22 +5,31 @@ import {
|
|
|
5
5
|
GeocodingResult,
|
|
6
6
|
queryGeonames,
|
|
7
7
|
GeonamesOptions,
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
16
|
+
type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
|
|
17
17
|
export type GeocodingProvider =
|
|
18
18
|
| GeoadminGeocodingProvider
|
|
19
19
|
| GeonamesGeocodingProvider
|
|
20
|
-
|
|
|
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 '
|
|
54
|
+
case 'geoplateforme':
|
|
46
55
|
queryObservable = from(
|
|
47
|
-
|
|
56
|
+
queryGeoplateforme(text, this.provider[1] as GeoplateformeOptions)
|
|
48
57
|
)
|
|
49
58
|
break
|
|
50
59
|
default:
|
|
@@ -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,15 @@ import { FieldType, FieldValue } from '../utils/service/fields'
|
|
|
15
15
|
import { SearchFacade } from '../state/search.facade'
|
|
16
16
|
import { SearchService } from '../utils/service/search.service'
|
|
17
17
|
import { FieldsService } from '../utils/service/fields.service'
|
|
18
|
-
import { formatUserInfo } from '../../../../../../libs/util/shared/src'
|
|
18
|
+
import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
|
|
19
19
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
20
20
|
|
|
21
21
|
marker('search.filters.summaryLabel.user')
|
|
22
22
|
marker('search.filters.summaryLabel.changeDate')
|
|
23
|
+
marker('search.filters.summaryLabel.resourceCreationRevisionDate')
|
|
24
|
+
marker('search.filters.summaryLabel.temporalExtent')
|
|
25
|
+
|
|
26
|
+
const OPEN_BOUND = '…'
|
|
23
27
|
|
|
24
28
|
interface DisplayedValue {
|
|
25
29
|
label: string
|
|
@@ -32,13 +36,12 @@ interface DisplayedValue {
|
|
|
32
36
|
imports: [CommonModule, BadgeComponent],
|
|
33
37
|
templateUrl: './search-filters-summary-item.component.html',
|
|
34
38
|
styleUrls: ['./search-filters-summary-item.component.css'],
|
|
35
|
-
providers: [DatePipe],
|
|
36
39
|
})
|
|
37
40
|
export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
38
41
|
private searchFacade = inject(SearchFacade)
|
|
39
42
|
private searchService = inject(SearchService)
|
|
40
43
|
private fieldsService = inject(FieldsService)
|
|
41
|
-
private
|
|
44
|
+
private dateService = inject(DateService)
|
|
42
45
|
private translate = inject(TranslateService)
|
|
43
46
|
|
|
44
47
|
@Input() fieldName: string
|
|
@@ -82,12 +85,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
|
82
85
|
getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
|
|
83
86
|
return fieldValues.map((value) => {
|
|
84
87
|
if (this.fieldType === 'dateRange') {
|
|
88
|
+
const { start, end } = value as DateRange
|
|
85
89
|
return {
|
|
86
90
|
value,
|
|
87
|
-
label: `${this.
|
|
88
|
-
value.start,
|
|
89
|
-
'dd.MM.yyyy'
|
|
90
|
-
)} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
|
|
91
|
+
label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
|
|
91
92
|
}
|
|
92
93
|
} else if (this.fieldName === 'user') {
|
|
93
94
|
return { value, label: formatUserInfo(value) }
|
|
@@ -97,6 +98,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
|
|
|
97
98
|
})
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
private formatBound(date: Date | undefined): string {
|
|
102
|
+
return date
|
|
103
|
+
? this.dateService.formatDate(date, {
|
|
104
|
+
day: '2-digit',
|
|
105
|
+
month: '2-digit',
|
|
106
|
+
year: 'numeric',
|
|
107
|
+
})
|
|
108
|
+
: OPEN_BOUND
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
async removeFilterValue(fieldValue: FieldValue | DateRange) {
|
|
101
112
|
const currentFieldValues: DisplayedValue[] = await firstValueFrom(
|
|
102
113
|
this.fieldValues$
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
SortByField,
|
|
9
9
|
} from '../../../../../../libs/common/domain/src/lib/model/search'
|
|
10
10
|
import { DEFAULT_PAGE_SIZE, FIELDS_SUMMARY } from '../constants'
|
|
11
|
-
import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/src'
|
|
12
11
|
import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
13
12
|
|
|
14
13
|
export const SEARCH_FEATURE_KEY = 'searchState'
|
|
@@ -56,9 +55,7 @@ export const initSearch = (): SearchStateSearch => {
|
|
|
56
55
|
},
|
|
57
56
|
params: {
|
|
58
57
|
filters: {},
|
|
59
|
-
pageSize:
|
|
60
|
-
? getOptionalSearchConfig().LIMIT
|
|
61
|
-
: DEFAULT_PAGE_SIZE,
|
|
58
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
62
59
|
currentPage: 0,
|
|
63
60
|
favoritesOnly: false,
|
|
64
61
|
useSpatialFilter: true,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Injectable,
|
|
1
|
+
import { inject, Injectable, InjectionToken, Injector } from '@angular/core'
|
|
2
2
|
import {
|
|
3
3
|
AbstractSearchField,
|
|
4
4
|
AvailableServicesField,
|
|
5
|
+
BoundingBoxSearchField,
|
|
5
6
|
DateRangeSearchField,
|
|
6
7
|
FieldValue,
|
|
7
8
|
FullTextSearchField,
|
|
@@ -10,10 +11,11 @@ import {
|
|
|
10
11
|
MultilingualSearchField,
|
|
11
12
|
OrganizationSearchField,
|
|
12
13
|
OwnerSearchField,
|
|
14
|
+
RecordKindField,
|
|
15
|
+
ResourceCreationRevisionDateSearchField,
|
|
13
16
|
ResourceTypeLegacyField,
|
|
14
17
|
SimpleSearchField,
|
|
15
18
|
TranslatedSearchField,
|
|
16
|
-
RecordKindField,
|
|
17
19
|
UserSearchField,
|
|
18
20
|
} from './fields'
|
|
19
21
|
import { forkJoin, Observable, of } from 'rxjs'
|
|
@@ -42,14 +44,30 @@ marker('search.filters.producerOrg')
|
|
|
42
44
|
marker('search.filters.publisherOrg')
|
|
43
45
|
marker('search.filters.user')
|
|
44
46
|
marker('search.filters.changeDate')
|
|
47
|
+
marker('search.filters.resourceCreationRevisionDate')
|
|
48
|
+
marker('search.filters.temporalExtent')
|
|
49
|
+
marker('search.filters.spatialExtent')
|
|
50
|
+
marker('search.filters.documentStandard')
|
|
51
|
+
|
|
52
|
+
export interface CustomSearchField {
|
|
53
|
+
name: string
|
|
54
|
+
baseFilter: string
|
|
55
|
+
excludeValues?: string[]
|
|
56
|
+
includeValues?: string[]
|
|
57
|
+
}
|
|
45
58
|
|
|
59
|
+
export const CUSTOM_FIELDS = new InjectionToken<CustomSearchField[]>(
|
|
60
|
+
'custom-fields'
|
|
61
|
+
)
|
|
46
62
|
@Injectable({
|
|
47
63
|
providedIn: 'root',
|
|
48
64
|
})
|
|
49
65
|
export class FieldsService {
|
|
50
66
|
protected injector = inject(Injector)
|
|
67
|
+
private customFields =
|
|
68
|
+
inject<CustomSearchField[]>(CUSTOM_FIELDS, { optional: true }) ?? []
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
private baseFields: Record<string, AbstractSearchField> = {
|
|
53
71
|
organization: new OrganizationSearchField(this.injector),
|
|
54
72
|
format: new SimpleSearchField('format', this.injector, 'asc'),
|
|
55
73
|
resourceType: new ResourceTypeLegacyField(this.injector), // Deprecated, use `recordKind` instead
|
|
@@ -94,8 +112,44 @@ export class FieldsService {
|
|
|
94
112
|
),
|
|
95
113
|
user: new UserSearchField(this.injector),
|
|
96
114
|
changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
|
|
115
|
+
resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
|
|
116
|
+
this.injector,
|
|
117
|
+
'desc'
|
|
118
|
+
),
|
|
119
|
+
temporalExtent: new DateRangeSearchField(
|
|
120
|
+
'resourceTemporalExtentDateRange',
|
|
121
|
+
this.injector,
|
|
122
|
+
'desc'
|
|
123
|
+
),
|
|
97
124
|
availableServices: new AvailableServicesField(this.injector),
|
|
98
|
-
|
|
125
|
+
spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected fields: Record<string, AbstractSearchField>
|
|
129
|
+
|
|
130
|
+
constructor() {
|
|
131
|
+
this.fields = { ...this.baseFields }
|
|
132
|
+
for (const customField of this.customFields) {
|
|
133
|
+
const baseField = this.baseFields[customField.baseFilter]
|
|
134
|
+
if (!(baseField instanceof SimpleSearchField)) {
|
|
135
|
+
console.warn(
|
|
136
|
+
`The custom field '${customField.name}' relies on a base field '${
|
|
137
|
+
customField.baseFilter
|
|
138
|
+
}' that is not supported. This field will be ignored.`
|
|
139
|
+
)
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
const newField = baseField.clone()
|
|
143
|
+
newField.setFieldIdentifier(customField.name)
|
|
144
|
+
if (customField.includeValues) {
|
|
145
|
+
newField.includeValues = customField.includeValues
|
|
146
|
+
}
|
|
147
|
+
if (customField.excludeValues) {
|
|
148
|
+
newField.excludeValues = customField.excludeValues
|
|
149
|
+
}
|
|
150
|
+
this.fields[customField.name] = newField
|
|
151
|
+
}
|
|
152
|
+
}
|
|
99
153
|
|
|
100
154
|
get supportedFields() {
|
|
101
155
|
return Object.keys(this.fields)
|