geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.80a8b0bfa
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 +624 -495
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +80 -48
- 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/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/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/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/translations/de.json +2 -1
- package/translations/en.json +2 -1
- package/translations/es.json +1 -0
- package/translations/fr.json +2 -1
- package/translations/it.json +2 -1
- package/translations/nl.json +1 -0
- package/translations/pt.json +1 -0
- package/translations/sk.json +2 -1
- 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
|
}
|
|
@@ -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
|
|
@@ -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) {
|
|
@@ -7,20 +7,19 @@ import { DataItem, DatasetHeaders, FetchError, SupportedType } from './model'
|
|
|
7
7
|
import { inferDatasetType } from './utils'
|
|
8
8
|
import { BaseReader } from './readers/base'
|
|
9
9
|
import { GmlReader } from './readers/gml'
|
|
10
|
-
import { WfsVersion } from '@camptocamp/ogc-client'
|
|
11
10
|
import { WfsReader } from './readers/wfs'
|
|
12
11
|
|
|
12
|
+
interface OpenDatasetOptions {
|
|
13
|
+
typeHint?: SupportedType
|
|
14
|
+
wfsFeatureType?: string
|
|
15
|
+
enableCache?: boolean // by default cache will be used if this is not set to false
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export async function openDataset(
|
|
14
19
|
url: string,
|
|
15
|
-
|
|
16
|
-
options?: {
|
|
17
|
-
namespace?: string
|
|
18
|
-
wfsVersion?: WfsVersion
|
|
19
|
-
wfsFeatureType?: string
|
|
20
|
-
},
|
|
21
|
-
cacheActive?: boolean
|
|
20
|
+
options?: OpenDatasetOptions
|
|
22
21
|
): Promise<BaseReader> {
|
|
23
|
-
const fileType = await inferDatasetType(url, typeHint)
|
|
22
|
+
const fileType = await inferDatasetType(url, options?.typeHint)
|
|
24
23
|
let reader:
|
|
25
24
|
| CsvReader
|
|
26
25
|
| JsonReader
|
|
@@ -43,19 +42,19 @@ export async function openDataset(
|
|
|
43
42
|
reader = new ExcelReader(url)
|
|
44
43
|
break
|
|
45
44
|
case 'gml':
|
|
46
|
-
reader = new GmlReader(url
|
|
45
|
+
reader = new GmlReader(url)
|
|
47
46
|
break
|
|
48
47
|
case 'wfs':
|
|
49
|
-
reader =
|
|
48
|
+
reader = new WfsReader(url, options?.wfsFeatureType)
|
|
50
49
|
break
|
|
51
50
|
}
|
|
52
|
-
reader.
|
|
51
|
+
reader.enableCache(options?.enableCache ?? true)
|
|
53
52
|
reader.load()
|
|
54
53
|
return reader
|
|
55
|
-
} catch (e:
|
|
56
|
-
//WfsReader may already raise a FetchError
|
|
54
|
+
} catch (e: unknown) {
|
|
55
|
+
// WfsReader may already raise a FetchError
|
|
57
56
|
if (e instanceof FetchError) throw e
|
|
58
|
-
else throw FetchError.parsingFailed(e.message)
|
|
57
|
+
else throw FetchError.parsingFailed((e as Error).message)
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -63,22 +62,20 @@ export async function openDataset(
|
|
|
63
62
|
* This fetches the full dataset at the given URL and parses it according to its mime type.
|
|
64
63
|
* All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
|
|
65
64
|
* File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
|
|
66
|
-
* File type is determined
|
|
65
|
+
* File type is determined like so:
|
|
67
66
|
* 1. if a type hint is given, use it
|
|
68
67
|
* 2. otherwise, look for a Content-Type header in the response with a supported mime type
|
|
69
68
|
* 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
|
|
70
69
|
*/
|
|
71
70
|
export async function readDataset(
|
|
72
71
|
url: string,
|
|
73
|
-
|
|
74
|
-
options?: any,
|
|
75
|
-
cacheActive = true
|
|
72
|
+
options?: OpenDatasetOptions
|
|
76
73
|
): Promise<DataItem[]> {
|
|
77
|
-
const reader = await openDataset(url,
|
|
74
|
+
const reader = await openDataset(url, options)
|
|
78
75
|
try {
|
|
79
76
|
return await reader.read()
|
|
80
|
-
} catch (e:
|
|
81
|
-
throw FetchError.parsingFailed(e.message)
|
|
77
|
+
} catch (e: unknown) {
|
|
78
|
+
throw FetchError.parsingFailed((e as Error).message)
|
|
82
79
|
}
|
|
83
80
|
}
|
|
84
81
|
|