geonetwork-ui 2.11.0-dev.96d0c6ce2 → 2.11.0-dev.a5c259d9e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/fesm2022/geonetwork-ui.mjs +971 -585
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +165 -59
  4. package/index.d.ts.map +1 -1
  5. package/package.json +11 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +23 -3
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  11. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  12. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  13. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  14. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  15. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  16. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  17. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  18. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  19. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  20. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  21. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  22. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  23. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  24. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +7 -0
  25. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +39 -0
  26. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  27. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  28. package/src/libs/feature/search/src/lib/utils/service/fields.ts +16 -1
  29. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  30. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  31. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  32. package/src/libs/ui/inputs/src/index.ts +1 -0
  33. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  34. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  35. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  36. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  37. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  38. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  39. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -5
  40. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  41. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  42. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  43. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  44. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  45. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  46. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  47. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  48. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  49. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  50. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  51. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  52. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  53. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  54. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  55. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  56. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  57. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  58. package/tailwind.base.css +5 -0
  59. package/translations/de.json +11 -1
  60. package/translations/en.json +11 -1
  61. package/translations/es.json +10 -0
  62. package/translations/fr.json +11 -1
  63. package/translations/it.json +11 -1
  64. package/translations/nl.json +10 -0
  65. package/translations/pt.json +10 -0
  66. package/translations/sk.json +11 -1
  67. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -11,6 +11,7 @@ import {
11
11
  AggregationsParams,
12
12
  FieldFilter,
13
13
  FieldFilters,
14
+ FieldSort,
14
15
  FilterQuery,
15
16
  FiltersAggregationParams,
16
17
  SortByField,
@@ -141,6 +142,7 @@ export class ElasticsearchService {
141
142
  values: uuids,
142
143
  },
143
144
  },
145
+ size: uuids.length,
144
146
  }
145
147
  }
146
148
 
@@ -199,9 +201,27 @@ export class ElasticsearchService {
199
201
  }
200
202
 
201
203
  private buildPayloadSort(sortBy: SortByField): SortParams {
202
- if (sortBy === null) return undefined
203
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy]
204
- return fields.map((field) => ({ [field[1]]: field[0] }))
204
+ if (!sortBy || sortBy.length === 0) return undefined
205
+ const fields = Array.isArray(sortBy[0])
206
+ ? (sortBy as FieldSort[])
207
+ : [sortBy as FieldSort]
208
+ return fields.map((field) => {
209
+ // Sort by nested array of dates only works with the explicit syntax
210
+ if (field[1].endsWith('.date')) {
211
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'))
212
+ return {
213
+ [field[1]]: {
214
+ order: field[0] as 'desc' | 'asc',
215
+ mode: field[0] === 'desc' ? 'max' : 'min',
216
+ missing: '_last',
217
+ nested: {
218
+ path: nestedPath,
219
+ },
220
+ },
221
+ }
222
+ }
223
+ return { [field[1]]: field[0] }
224
+ })
205
225
  }
206
226
 
207
227
  private injectLangInQueryStringFields(
@@ -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
- getSources(record: CatalogRecord): Observable<CatalogRecord[]> {
253
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'] as string[]
254
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
255
- return this.getMultipleRecords(sourcesIdentifiers)
256
- }
257
- return of(null)
258
- }
259
-
260
- getSourceOf(record: CatalogRecord): Observable<CatalogRecord[]> {
261
- const sourceOfIdentifiers = record.extras?.[
262
- 'sourceOfIdentifiers'
263
- ] as string[]
264
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
265
- return this.getMultipleRecords(sourceOfIdentifiers)
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 of(null)
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
@@ -6,9 +6,5 @@ export const SortByEnum: Record<string, SortByField> = {
6
6
  RELEVANCY: ['desc', '_score'],
7
7
  QUALITY_SCORE: ['desc', 'qualityScore'],
8
8
  CHANGE_DATE: ['desc', 'changeDate'],
9
- RESOURCE_DATES: [
10
- ['desc', 'revisionDateForResource'],
11
- ['desc', 'publicationDateForResource'],
12
- ['desc', 'creationDateForResource'],
13
- ],
9
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
14
10
  }
@@ -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 getSources(record: CatalogRecord): Observable<CatalogRecord[]>
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' || prop.type === 'date')
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 { Injectable, inject } from '@angular/core'
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, tap } from 'rxjs/operators'
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
- wfsUrlEndpoint,
352
- 'wfs',
353
- {
354
- wfsFeatureType: link.name,
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, supportedType, undefined, cacheActive)
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(openDataset(url, 'geojson', undefined, cacheActive)).pipe()
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
- urlWithoutLimit.toString(),
400
- 'geojson',
401
- undefined,
402
- cacheActive
403
- )
400
+ return openDataset(urlWithoutLimit.toString(), {
401
+ typeHint: 'geojson',
402
+ enableCache: cacheActive,
403
+ })
404
404
  })
405
405
  )
406
406
  }
@@ -5,6 +5,7 @@
5
5
  (fileChange)="handleFileChange($event)"
6
6
  [accept]="acceptedMimeType.join(',')"
7
7
  [placeholder]="'map.addFromFile.placeholder' | translate"
8
+ textClass="text-gray-900 pl-2 py-2"
8
9
  class="placeholder-grey"
9
10
  ></gn-ui-drag-and-drop-file-input>
10
11
  </div>
@@ -18,10 +18,12 @@ export class NotificationsService {
18
18
  error && console.error(error)
19
19
  const id = Math.floor(Math.random() * 1000000)
20
20
  this.notifications$.next([...this.notifications$.value, { ...content, id }])
21
- if (typeof timeoutMs === 'undefined') return
22
- setTimeout(() => {
23
- this.removeNotificationById(id)
24
- }, timeoutMs)
21
+ if (typeof timeoutMs !== 'undefined') {
22
+ setTimeout(() => {
23
+ this.removeNotificationById(id)
24
+ }, timeoutMs)
25
+ }
26
+ return id
25
27
  }
26
28
 
27
29
  removeNotificationById(id: number) {
@@ -3,6 +3,7 @@ import { createAction, props } from '@ngrx/store'
3
3
  import {
4
4
  CatalogRecord,
5
5
  DatasetFeatureCatalog,
6
+ LinkedRecord,
6
7
  UserFeedback,
7
8
  } from '../../../../../../libs/common/domain/src/lib/model/record'
8
9
 
@@ -54,14 +55,9 @@ export const setRelated = createAction(
54
55
  props<{ related: CatalogRecord[] }>()
55
56
  )
56
57
 
57
- export const setSources = createAction(
58
- '[Metadata view] Set sources',
59
- props<{ sources: CatalogRecord[] }>()
60
- )
61
-
62
- export const setSourceOf = createAction(
63
- '[Metadata view] Set has sources',
64
- props<{ sourceOf: CatalogRecord[] }>()
58
+ export const setLinkedRecords = createAction(
59
+ '[Metadata view] Set associated records',
60
+ props<{ linkedRecords: LinkedRecord[] }>()
65
61
  )
66
62
 
67
63
  /*
@@ -70,25 +70,16 @@ export class MdViewEffects {
70
70
  )
71
71
  )
72
72
 
73
- loadSources$ = createEffect(() =>
73
+ loadLinkedRecords$ = createEffect(() =>
74
74
  this.actions$.pipe(
75
75
  ofType(MdViewActions.loadFullMetadataSuccess),
76
- switchMap(({ full }) => this.recordsRepository.getSources(full)),
77
- map((sources) => {
78
- return MdViewActions.setSources({ sources })
76
+ switchMap(({ full }) => this.recordsRepository.getLinkedRecords(full)),
77
+ map((linkedRecords) => {
78
+ return MdViewActions.setLinkedRecords({ linkedRecords })
79
79
  }),
80
- catchError(() => of(MdViewActions.setSources({ sources: null })))
81
- )
82
- )
83
-
84
- loadSourceOf$ = createEffect(() =>
85
- this.actions$.pipe(
86
- ofType(MdViewActions.loadFullMetadataSuccess),
87
- switchMap(({ full }) => this.recordsRepository.getSourceOf(full)),
88
- map((sourceOf) => {
89
- return MdViewActions.setSourceOf({ sourceOf })
90
- }),
91
- catchError(() => of(MdViewActions.setSourceOf({ sourceOf: null })))
80
+ catchError(() =>
81
+ of(MdViewActions.setLinkedRecords({ linkedRecords: null }))
82
+ )
92
83
  )
93
84
  )
94
85
 
@@ -74,9 +74,7 @@ export class MdViewFacade {
74
74
 
75
75
  related$ = this.store.pipe(select(MdViewSelectors.getRelated))
76
76
 
77
- sources$ = this.store.pipe(select(MdViewSelectors.getSources))
78
-
79
- sourceOf$ = this.store.pipe(select(MdViewSelectors.getSourceOf))
77
+ linkedRecords$ = this.store.pipe(select(MdViewSelectors.getLinkedRecords))
80
78
 
81
79
  chartConfig$ = this.store.pipe(select(MdViewSelectors.getChartConfig))
82
80
 
@@ -4,6 +4,7 @@ import { DatavizChartConfigModel } from '../../../../../../libs/common/domain/sr
4
4
  import {
5
5
  CatalogRecord,
6
6
  DatasetFeatureCatalog,
7
+ LinkedRecord,
7
8
  UserFeedback,
8
9
  } from '../../../../../../libs/common/domain/src/lib/model/record'
9
10
 
@@ -14,8 +15,7 @@ export interface MetadataViewState {
14
15
  error: { notFound?: boolean; otherError?: string } | null
15
16
  metadata?: Partial<CatalogRecord>
16
17
  related?: CatalogRecord[]
17
- sources?: CatalogRecord[]
18
- sourceOf?: CatalogRecord[]
18
+ linkedRecords?: LinkedRecord[]
19
19
  userFeedbacks?: UserFeedback[]
20
20
  allUserFeedbacksLoading: boolean
21
21
  addUserFeedbackLoading: boolean
@@ -77,14 +77,9 @@ const metadataViewReducer = createReducer(
77
77
  related,
78
78
  })),
79
79
 
80
- on(MetadataViewActions.setSources, (state, { sources }) => ({
80
+ on(MetadataViewActions.setLinkedRecords, (state, { linkedRecords }) => ({
81
81
  ...state,
82
- sources,
83
- })),
84
-
85
- on(MetadataViewActions.setSourceOf, (state, { sourceOf }) => ({
86
- ...state,
87
- sourceOf,
82
+ linkedRecords,
88
83
  })),
89
84
 
90
85
  /*
@@ -41,14 +41,9 @@ export const getRelated = createSelector(
41
41
  (state: MetadataViewState) => state.related
42
42
  )
43
43
 
44
- export const getSources = createSelector(
44
+ export const getLinkedRecords = createSelector(
45
45
  getMdViewState,
46
- (state: MetadataViewState) => state.sources
47
- )
48
-
49
- export const getSourceOf = createSelector(
50
- getMdViewState,
51
- (state: MetadataViewState) => state.sourceOf
46
+ (state: MetadataViewState) => state.linkedRecords
52
47
  )
53
48
  /*
54
49
  Metadata selectors
@@ -62,6 +62,6 @@ export class RouterService {
62
62
  }
63
63
 
64
64
  getDefaultSort(): SortByField {
65
- return SortByEnum.RESOURCE_DATES
65
+ return SortByEnum.RESOURCE_DATE
66
66
  }
67
67
  }
@@ -6,6 +6,13 @@
6
6
  (startDateChange)="onStartDateChange($event)"
7
7
  (endDateChange)="onEndDateChange($event)"
8
8
  ></gn-ui-date-range-dropdown>
9
+ } @else if (fieldType === 'spatialExtent') {
10
+ <gn-ui-spatial-extent-dropdown
11
+ [title]="title"
12
+ [maxFileSizeMb]="spatialExtentMaxFileSize"
13
+ (bboxChange)="onBboxChange($event)"
14
+ (errorChange)="onSpatialExtentError($event)"
15
+ ></gn-ui-spatial-extent-dropdown>
9
16
  } @else {
10
17
  <gn-ui-dropdown-multiselect
11
18
  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,16 +39,22 @@ 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
59
  dateRange: DateRange
48
60
  choices$: Observable<Choice[]>
@@ -66,6 +78,33 @@ export class FilterDropdownComponent implements OnInit {
66
78
  .subscribe((filters) => this.searchService.updateFilters(filters))
67
79
  }
68
80
 
81
+ private spatialExtentErrorNotificationId: number | null = null
82
+
83
+ onBboxChange(bbox: BoundingBox | null) {
84
+ console.log(bbox)
85
+ this.clearSpatialExtentErrorNotification()
86
+ }
87
+
88
+ onSpatialExtentError(error: SpatialExtentDropdownError) {
89
+ this.clearSpatialExtentErrorNotification()
90
+ this.spatialExtentErrorNotificationId =
91
+ this.notificationsService.showNotification({
92
+ type: 'error',
93
+ title: this.translateService.instant(
94
+ 'search.filters.spatialExtent.error.title'
95
+ ),
96
+ text: this.translateService.instant(error.key, error.params),
97
+ })
98
+ }
99
+
100
+ private clearSpatialExtentErrorNotification() {
101
+ if (this.spatialExtentErrorNotificationId === null) return
102
+ this.notificationsService.removeNotificationById(
103
+ this.spatialExtentErrorNotificationId
104
+ )
105
+ this.spatialExtentErrorNotificationId = null
106
+ }
107
+
69
108
  ngOnInit() {
70
109
  this.fieldType = this.fieldsService.getFieldType(this.fieldName)
71
110
  this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
@@ -34,7 +34,7 @@ export class SortByComponent implements OnInit {
34
34
  },
35
35
  {
36
36
  label: marker('results.sortBy.dateStamp'),
37
- value: SortByEnum.RESOURCE_DATES,
37
+ value: SortByEnum.RESOURCE_DATE,
38
38
  },
39
39
  {
40
40
  label: marker('results.sortBy.popularity'),
@@ -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() {
@@ -23,7 +23,7 @@ import {
23
23
  import { formatUserInfo } from '../../../../../../../libs/util/shared/src'
24
24
  import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
25
25
 
26
- export type FieldType = 'values' | 'dateRange'
26
+ export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
27
27
 
28
28
  export type FieldValue = string | number
29
29
  export interface FieldAvailableValue {
@@ -431,6 +431,21 @@ export class DateRangeSearchField extends SimpleSearchField {
431
431
  }
432
432
  }
433
433
 
434
+ export class SpatialExtentSearchField extends SimpleSearchField {
435
+ constructor(injector: Injector) {
436
+ super('spatialExtent', injector, 'asc')
437
+ }
438
+
439
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
440
+ // TODO: return an array of spatial extents to show which ones are available in the dropdown
441
+ return of([])
442
+ }
443
+
444
+ getType(): FieldType {
445
+ return 'spatialExtent'
446
+ }
447
+ }
448
+
434
449
  marker('search.filters.availableServices.view')
435
450
  marker('search.filters.availableServices.download')
436
451