geonetwork-ui 2.11.0-dev.a83f0f51a → 2.11.0-dev.aed5b2e5b
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 +1248 -747
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +195 -76
- package/index.d.ts.map +1 -1
- package/package.json +11 -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 +1 -0
- 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/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/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
- 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/lib/filter-dropdown/filter-dropdown.component.html +9 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +16 -1
- 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/inputs/src/index.ts +2 -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 +1 -1
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -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/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/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +13 -2
- package/translations/en.json +12 -1
- package/translations/es.json +11 -0
- package/translations/fr.json +13 -2
- package/translations/it.json +11 -0
- package/translations/nl.json +11 -0
- package/translations/pt.json +11 -0
- package/translations/sk.json +11 -0
- package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
|
@@ -14,10 +14,13 @@ import {
|
|
|
14
14
|
} from '../../../../../../libs/api/metadata-converter/src'
|
|
15
15
|
import { PublicationVersionError } from '../../../../../../libs/common/domain/src/lib/model/error'
|
|
16
16
|
import {
|
|
17
|
+
AssociatedRecord,
|
|
17
18
|
CatalogRecord,
|
|
18
19
|
DatasetFeatureCatalog,
|
|
19
20
|
DatasetFeatureType,
|
|
20
21
|
LanguageCode,
|
|
22
|
+
LinkedRecord,
|
|
23
|
+
RecordRelation,
|
|
21
24
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
22
25
|
import {
|
|
23
26
|
Aggregations,
|
|
@@ -145,7 +148,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
145
148
|
return this.gn4SearchApi
|
|
146
149
|
.search(
|
|
147
150
|
'bucket',
|
|
148
|
-
['fcats', 'hassources'],
|
|
151
|
+
['fcats', 'hassources', 'siblings', 'associated'],
|
|
149
152
|
JSON.stringify(
|
|
150
153
|
this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])
|
|
151
154
|
)
|
|
@@ -249,22 +252,43 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
249
252
|
)
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
255
|
+
getLinkedRecords(record: CatalogRecord): Observable<LinkedRecord[]> {
|
|
256
|
+
const siblings = (record.extras?.['siblings'] ?? []) as AssociatedRecord[]
|
|
257
|
+
const relations: Array<[RecordRelation, string[]]> = [
|
|
258
|
+
['source', (record.extras?.['sourcesIdentifiers'] ?? []) as string[]],
|
|
259
|
+
['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? []) as string[]],
|
|
260
|
+
['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
|
|
261
|
+
[
|
|
262
|
+
'associated',
|
|
263
|
+
(record.extras?.['associatedIdentifiers'] ?? []) as string[],
|
|
264
|
+
],
|
|
265
|
+
]
|
|
266
|
+
const requested = relations.filter(
|
|
267
|
+
([, identifiers]) => identifiers.length > 0
|
|
268
|
+
)
|
|
269
|
+
if (requested.length === 0) {
|
|
270
|
+
return of([])
|
|
266
271
|
}
|
|
267
|
-
return
|
|
272
|
+
return forkJoin(
|
|
273
|
+
requested.map(([relation, identifiers]) =>
|
|
274
|
+
this.getMultipleRecords(identifiers).pipe(
|
|
275
|
+
map((records) =>
|
|
276
|
+
(records ?? []).map((record) => ({
|
|
277
|
+
record,
|
|
278
|
+
relation,
|
|
279
|
+
associationType:
|
|
280
|
+
relation === 'sibling'
|
|
281
|
+
? siblings.find(
|
|
282
|
+
({ uniqueIdentifier }) =>
|
|
283
|
+
uniqueIdentifier === record.uniqueIdentifier
|
|
284
|
+
)?.associationType
|
|
285
|
+
: undefined,
|
|
286
|
+
}))
|
|
287
|
+
),
|
|
288
|
+
catchError(() => of([]))
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
).pipe(map((groups) => groups.flat()))
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
aggregate(params: AggregationsParams): Observable<Aggregations> {
|
|
@@ -294,6 +294,18 @@ export interface AssociatedRecord {
|
|
|
294
294
|
associationType: AssociationType
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Nature of the link between a record and another one; the association type is
|
|
299
|
+
* only known for 'sibling' relations, as it is held by the declaring record.
|
|
300
|
+
*/
|
|
301
|
+
export type RecordRelation = 'source' | 'sourceOf' | 'sibling' | 'associated'
|
|
302
|
+
|
|
303
|
+
export interface LinkedRecord {
|
|
304
|
+
record: CatalogRecord
|
|
305
|
+
relation: RecordRelation
|
|
306
|
+
associationType?: AssociationType
|
|
307
|
+
}
|
|
308
|
+
|
|
297
309
|
export interface DatasetRecord extends BaseRecord {
|
|
298
310
|
kind: 'dataset'
|
|
299
311
|
status: RecordStatus
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CatalogRecord,
|
|
11
11
|
DatasetFeatureCatalog,
|
|
12
12
|
LanguageCode,
|
|
13
|
+
LinkedRecord,
|
|
13
14
|
} from '../model/record'
|
|
14
15
|
|
|
15
16
|
export abstract class RecordsRepositoryInterface {
|
|
@@ -23,8 +24,7 @@ export abstract class RecordsRepositoryInterface {
|
|
|
23
24
|
abstract getSimilarRecords(
|
|
24
25
|
similarTo: CatalogRecord
|
|
25
26
|
): Observable<CatalogRecord[]>
|
|
26
|
-
abstract
|
|
27
|
-
abstract getSourceOf(record: CatalogRecord): Observable<CatalogRecord[]>
|
|
27
|
+
abstract getLinkedRecords(record: CatalogRecord): Observable<LinkedRecord[]>
|
|
28
28
|
abstract fuzzySearch(query: string): Observable<SearchResults>
|
|
29
29
|
abstract canDuplicate(record: CatalogRecord): boolean
|
|
30
30
|
abstract canDelete(record: CatalogRecord): Observable<boolean>
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
ChangeDetectorRef,
|
|
4
4
|
Component,
|
|
5
|
+
inject,
|
|
5
6
|
Input,
|
|
6
7
|
Output,
|
|
7
|
-
inject,
|
|
8
8
|
} from '@angular/core'
|
|
9
9
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
10
10
|
import {
|
|
@@ -183,7 +183,7 @@ export class ChartViewComponent {
|
|
|
183
183
|
yChoices$ = this.properties$.pipe(
|
|
184
184
|
map((properties) =>
|
|
185
185
|
properties
|
|
186
|
-
.filter((prop) => prop.type === 'number'
|
|
186
|
+
.filter((prop) => prop.type === 'number')
|
|
187
187
|
.map((prop) => ({ value: prop.name, label: prop.label || prop.name }))
|
|
188
188
|
),
|
|
189
189
|
tap((choices) => {
|
|
@@ -222,6 +222,7 @@ export class ChartViewComponent {
|
|
|
222
222
|
switchMap(([dataset, xProp, yProp, aggregation]) => {
|
|
223
223
|
const fieldAgg: FieldAggregation =
|
|
224
224
|
aggregation === 'count' ? ['count'] : [aggregation, yProp]
|
|
225
|
+
this.loading = true
|
|
225
226
|
return dataset
|
|
226
227
|
.groupBy(['distinct', xProp])
|
|
227
228
|
.aggregate(fieldAgg)
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
ChangeDetectorRef,
|
|
4
4
|
Component,
|
|
5
|
+
inject,
|
|
5
6
|
Input,
|
|
6
7
|
OnDestroy,
|
|
7
8
|
OnInit,
|
|
8
9
|
ViewChild,
|
|
9
|
-
inject,
|
|
10
10
|
} from '@angular/core'
|
|
11
11
|
import {
|
|
12
12
|
DataTableComponent,
|
|
@@ -75,6 +75,7 @@ export class GeoTableViewComponent implements OnInit, OnDestroy {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
private async initMapContext(): Promise<MapContext> {
|
|
78
|
+
this.dataset.load()
|
|
78
79
|
this.dataset.selectAll()
|
|
79
80
|
return {
|
|
80
81
|
layers: [
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inject, Injectable } from '@angular/core'
|
|
2
2
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
3
3
|
import {
|
|
4
|
+
GetCollectionItemsOptions,
|
|
4
5
|
OgcApiCollectionInfo,
|
|
5
6
|
OgcApiEndpoint,
|
|
6
7
|
OgcApiRecord,
|
|
7
|
-
WfsEndpoint,
|
|
8
|
-
WfsVersion,
|
|
9
|
-
TmsEndpoint,
|
|
10
8
|
StacEndpoint,
|
|
11
|
-
GetCollectionItemsOptions,
|
|
12
9
|
StacItemsDocument,
|
|
10
|
+
TmsEndpoint,
|
|
11
|
+
WfsEndpoint,
|
|
12
|
+
WfsVersion,
|
|
13
13
|
} from '@camptocamp/ogc-client'
|
|
14
14
|
import {
|
|
15
15
|
BaseReader,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from '../../../../../../libs/util/shared/src'
|
|
27
27
|
import type { FeatureCollection } from 'geojson'
|
|
28
28
|
import { from, Observable, throwError } from 'rxjs'
|
|
29
|
-
import { catchError, map, switchMap
|
|
29
|
+
import { catchError, map, switchMap } from 'rxjs/operators'
|
|
30
30
|
import {
|
|
31
31
|
DatasetOnlineResource,
|
|
32
32
|
DatasetServiceDistribution,
|
|
@@ -347,14 +347,11 @@ export class DataService {
|
|
|
347
347
|
if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
|
|
348
348
|
const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString())
|
|
349
349
|
return from(
|
|
350
|
-
openDataset(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
},
|
|
356
|
-
cacheActive
|
|
357
|
-
)
|
|
350
|
+
openDataset(wfsUrlEndpoint, {
|
|
351
|
+
typeHint: 'wfs',
|
|
352
|
+
wfsFeatureType: link.name,
|
|
353
|
+
enableCache: cacheActive,
|
|
354
|
+
})
|
|
358
355
|
)
|
|
359
356
|
} else if (link.type === 'download') {
|
|
360
357
|
const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString())
|
|
@@ -364,7 +361,10 @@ export class DataService {
|
|
|
364
361
|
? (format as SupportedType)
|
|
365
362
|
: undefined
|
|
366
363
|
return from(
|
|
367
|
-
openDataset(linkProxifiedUrl,
|
|
364
|
+
openDataset(linkProxifiedUrl, {
|
|
365
|
+
typeHint: supportedType,
|
|
366
|
+
enableCache: cacheActive,
|
|
367
|
+
})
|
|
368
368
|
).pipe()
|
|
369
369
|
} else if (
|
|
370
370
|
link.type === 'service' &&
|
|
@@ -374,7 +374,9 @@ export class DataService {
|
|
|
374
374
|
link.url.toString(),
|
|
375
375
|
'geojson'
|
|
376
376
|
)
|
|
377
|
-
return from(
|
|
377
|
+
return from(
|
|
378
|
+
openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })
|
|
379
|
+
).pipe()
|
|
378
380
|
} else if (
|
|
379
381
|
link.type === 'service' &&
|
|
380
382
|
link.accessServiceProtocol === 'ogcFeatures'
|
|
@@ -395,12 +397,10 @@ export class DataService {
|
|
|
395
397
|
}
|
|
396
398
|
const urlWithoutLimit = new URL(geojsonUrl)
|
|
397
399
|
urlWithoutLimit.searchParams.delete('limit')
|
|
398
|
-
return openDataset(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
cacheActive
|
|
403
|
-
)
|
|
400
|
+
return openDataset(urlWithoutLimit.toString(), {
|
|
401
|
+
typeHint: 'geojson',
|
|
402
|
+
enableCache: cacheActive,
|
|
403
|
+
})
|
|
404
404
|
})
|
|
405
405
|
)
|
|
406
406
|
}
|
|
@@ -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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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,11 +1,16 @@
|
|
|
1
1
|
@if (fieldType === 'dateRange') {
|
|
2
2
|
<gn-ui-date-range-dropdown
|
|
3
3
|
[title]="title"
|
|
4
|
-
[
|
|
5
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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.
|
|
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$
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
TranslatedSearchField,
|
|
16
16
|
RecordKindField,
|
|
17
17
|
UserSearchField,
|
|
18
|
+
SpatialExtentSearchField,
|
|
18
19
|
} from './fields'
|
|
19
20
|
import { forkJoin, Observable, of } from 'rxjs'
|
|
20
21
|
import { map } from 'rxjs/operators'
|
|
@@ -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 SpatialExtentSearchField(this.injector),
|
|
98
100
|
} as Record<string, AbstractSearchField>
|
|
99
101
|
|
|
100
102
|
get supportedFields() {
|