geonetwork-ui 2.11.0-dev.5f9922c2e → 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.
Files changed (50) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1293 -1283
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +116 -80
  4. package/index.d.ts.map +1 -1
  5. package/package.json +2 -1
  6. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +90 -31
  7. package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
  8. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +12 -2
  9. package/src/libs/feature/map/src/index.ts +0 -2
  10. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  11. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  12. package/src/libs/feature/search/src/index.ts +2 -0
  13. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +3 -0
  14. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +21 -4
  15. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  16. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  17. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  18. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +2 -0
  19. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  20. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
  21. package/src/libs/feature/search/src/lib/utils/service/fields.ts +67 -9
  22. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  23. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  24. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  25. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  26. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  27. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  28. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +5 -11
  29. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -4
  30. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  31. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -0
  32. package/src/libs/util/app-config/src/lib/app-config.ts +22 -0
  33. package/src/libs/util/app-config/src/lib/fixtures.ts +33 -1
  34. package/src/libs/util/app-config/src/lib/model.ts +14 -1
  35. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  36. package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
  37. package/translations/de.json +10 -3
  38. package/translations/en.json +11 -4
  39. package/translations/es.json +10 -3
  40. package/translations/fr.json +15 -8
  41. package/translations/it.json +10 -3
  42. package/translations/nl.json +10 -3
  43. package/translations/pt.json +10 -3
  44. package/translations/sk.json +10 -3
  45. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.css +0 -0
  46. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  47. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  48. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  49. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  50. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
@@ -54,7 +54,12 @@ export class ElasticsearchService {
54
54
 
55
55
  // runtime fields are computed using a Painless script
56
56
  // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html
57
- private runtimeFields: Record<string, string> = {}
57
+ private runtimeFields: Record<
58
+ string,
59
+ { script: string; type: 'keyword' | 'date' }
60
+ > = {}
61
+
62
+ private fieldAliases: Record<string, string> = {}
58
63
 
59
64
  // we're using getters in case the defined languages change over time
60
65
  private get metadataLang(): LanguageCode {
@@ -96,8 +101,8 @@ export class ElasticsearchService {
96
101
  const addMapping = (fieldName: string) => {
97
102
  if (!payload.runtime_mappings) payload.runtime_mappings = {}
98
103
  payload.runtime_mappings[fieldName] = {
99
- type: 'keyword',
100
- script: this.runtimeFields[fieldName],
104
+ type: this.runtimeFields[fieldName].type,
105
+ script: this.runtimeFields[fieldName].script,
101
106
  }
102
107
  }
103
108
  const lookForField = (node: unknown) => {
@@ -118,6 +123,14 @@ export class ElasticsearchService {
118
123
  ) {
119
124
  addMapping(runtimeField)
120
125
  }
126
+ if (
127
+ runtimeField in node &&
128
+ typeof node[runtimeField] === 'object' &&
129
+ node[runtimeField] !== null &&
130
+ ('gte' in node[runtimeField] || 'lte' in node[runtimeField])
131
+ ) {
132
+ addMapping(runtimeField)
133
+ }
121
134
  if (
122
135
  'query' in node &&
123
136
  typeof node.query === 'string' &&
@@ -136,8 +149,16 @@ export class ElasticsearchService {
136
149
  return payload
137
150
  }
138
151
 
139
- registerRuntimeField(fieldName: string, expression: string) {
140
- this.runtimeFields[fieldName] = expression
152
+ registerRuntimeField(
153
+ fieldName: string,
154
+ expression: string,
155
+ type: 'keyword' | 'date' = 'keyword'
156
+ ) {
157
+ this.runtimeFields[fieldName] = { script: expression, type }
158
+ }
159
+
160
+ registerFieldAlias(fieldIdentifier: string, esFieldName: string) {
161
+ this.fieldAliases[fieldIdentifier] = esFieldName
141
162
  }
142
163
 
143
164
  getMetadataByIdsPayload(uuids: string[]): EsSearchParams {
@@ -278,6 +299,56 @@ export class ElasticsearchService {
278
299
  return Object.values(filters).find(isBoundingBox)
279
300
  }
280
301
 
302
+ // builds a query matching records whose dates intersect the filter range
303
+ private buildDateRangeQuery(searchField: string, dateRange: DateRange) {
304
+ // fields ending with 'DateRange' maps as ES range type
305
+ if (searchField.endsWith('DateRange')) {
306
+ return {
307
+ range: {
308
+ [searchField]: {
309
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
310
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
311
+ format: 'yyyy-MM-dd',
312
+ relation: 'intersects',
313
+ },
314
+ },
315
+ }
316
+ }
317
+ const minField = `${searchField}Min`
318
+ const maxField = `${searchField}Max`
319
+ if (minField in this.runtimeFields && maxField in this.runtimeFields) {
320
+ // min/max runtime fields form an interval per record; match if it intersects the filter range
321
+ const filter = [
322
+ dateRange.start && {
323
+ range: {
324
+ [maxField]: {
325
+ gte: formatDate(dateRange.start),
326
+ format: 'yyyy-MM-dd',
327
+ },
328
+ },
329
+ },
330
+ dateRange.end && {
331
+ range: {
332
+ [minField]: {
333
+ lte: formatDate(dateRange.end),
334
+ format: 'yyyy-MM-dd',
335
+ },
336
+ },
337
+ },
338
+ ].filter(Boolean)
339
+ return { bool: { filter } }
340
+ }
341
+ return {
342
+ range: {
343
+ [searchField]: {
344
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
345
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
346
+ format: 'yyyy-MM-dd',
347
+ },
348
+ },
349
+ }
350
+ }
351
+
281
352
  private filtersToQuery(
282
353
  filters: FieldFilters | FiltersAggregationParams | string,
283
354
  spatialFilterExtent = this.findSpatialFilterExtent(filters)
@@ -310,43 +381,25 @@ export class ElasticsearchService {
310
381
  JSON.stringify(filters[fieldname]) !== '{}'
311
382
  )
312
383
  .map(
313
- (fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`
384
+ (fieldname) =>
385
+ `${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
314
386
  )
315
387
  .join(' AND ')
316
388
  if (filters['gn-ui-crossFieldFilter']) {
317
389
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`
318
390
  }
319
- const queryRange = Object.entries(filters)
320
- .filter(([, value]) => isDateRange(value))
321
- .map(([searchField, dateRange]) => {
322
- return {
323
- searchField,
324
- dateRange,
325
- } as {
326
- searchField: string
327
- dateRange: DateRange
328
- }
329
- })[0]
391
+ const queryRanges = Object.entries(filters).filter(([, value]) =>
392
+ isDateRange(value)
393
+ ) as [string, DateRange][]
330
394
  const queryParts = [
331
395
  queryString && {
332
396
  query_string: {
333
397
  query: queryString,
334
398
  },
335
399
  },
336
- queryRange &&
337
- queryRange.dateRange && {
338
- range: {
339
- [queryRange.searchField]: {
340
- ...(queryRange.dateRange.start && {
341
- gte: formatDate(queryRange.dateRange.start),
342
- }),
343
- ...(queryRange.dateRange.end && {
344
- lte: formatDate(queryRange.dateRange.end),
345
- }),
346
- format: 'yyyy-MM-dd',
347
- },
348
- },
349
- },
400
+ ...queryRanges.map(([searchField, dateRange]) =>
401
+ this.buildDateRangeQuery(searchField, dateRange)
402
+ ),
350
403
  spatialFilterExtent && {
351
404
  geo_shape: {
352
405
  geom: {
@@ -675,6 +728,12 @@ export class ElasticsearchService {
675
728
  order: {
676
729
  [`_${aggregation.sort[1]}`]: aggregation.sort[0],
677
730
  },
731
+ ...(aggregation.includeValues && {
732
+ include: aggregation.includeValues,
733
+ }),
734
+ ...(aggregation.excludeValues && {
735
+ exclude: aggregation.excludeValues,
736
+ }),
678
737
  },
679
738
  }
680
739
  case 'histogram':
@@ -7,6 +7,8 @@ export interface TermsAggregationParams {
7
7
  limit: number
8
8
  sort: AggregationSort
9
9
  filter?: string
10
+ includeValues?: string[]
11
+ excludeValues?: string[]
10
12
  }
11
13
  export interface HistogramAggregationParams {
12
14
  type: 'histogram'
@@ -21,7 +21,10 @@ export type QueryString = {
21
21
  query_string: string
22
22
  }
23
23
  export type QueryRange = {
24
- range: Record<string, { format: string; gte: string; lte: string }>
24
+ range: Record<
25
+ string,
26
+ { format: string; gte?: string; lte?: string; relation?: string }
27
+ >
25
28
  }
26
29
  export type QueryGeoShape = {
27
30
  geo_shape: {
@@ -31,4 +34,11 @@ export type QueryGeoShape = {
31
34
  }
32
35
  }
33
36
  }
34
- export type FilterQuery = Array<QueryString | QueryRange | QueryGeoShape>
37
+ export type QueryBool = {
38
+ bool: {
39
+ filter: QueryRange[]
40
+ }
41
+ }
42
+ export type FilterQuery = Array<
43
+ QueryString | QueryRange | QueryGeoShape | QueryBool
44
+ >
@@ -6,8 +6,6 @@ export * from './lib/feature-map.module'
6
6
  export * from './lib/map-state-container/map-state-container.component'
7
7
  export * from './lib/constant'
8
8
  export * from './lib/utils'
9
- export * from './lib/style'
10
9
  export * from './lib/layers-panel/layers-panel.component'
11
10
  export * from './lib/add-layer-from-catalog/add-layer-from-catalog.component'
12
11
  export * from './lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component'
13
- export * from './lib/geocoding/geocoding.component'
@@ -2,18 +2,11 @@ import { NgModule } from '@angular/core'
2
2
  import { StoreModule } from '@ngrx/store'
3
3
  import * as fromMap from './+state/map.reducer'
4
4
  import { MapFacade } from './+state/map.facade'
5
- import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
6
5
 
7
6
  @NgModule({
8
7
  imports: [
9
8
  StoreModule.forFeature(fromMap.MAP_FEATURE_KEY, fromMap.mapReducer),
10
9
  ],
11
- providers: [
12
- MapFacade,
13
- {
14
- provide: GEOCODING_PROVIDER,
15
- useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
16
- },
17
- ],
10
+ providers: [MapFacade],
18
11
  })
19
12
  export class FeatureMapModule {}
@@ -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
- getOptionalEditorConfig()?.NEW_RECORD_DEFAULT_LANGUAGE ??
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()),
@@ -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'
@@ -3,13 +3,16 @@
3
3
  [title]="title"
4
4
  [dateRange]="(selectedDateRange$ | async) ?? {}"
5
5
  (dateRangeChange)="onDateRangeChange($event)"
6
+ [attr.data-cy-field]="fieldName"
6
7
  ></gn-ui-date-range-dropdown>
7
8
  } @else if (fieldType === 'spatialExtent') {
8
9
  <gn-ui-spatial-extent-dropdown
9
10
  [title]="title"
11
+ [initialBbox]="(selectedBoundingBox$ | async) ?? null"
10
12
  [maxFileSizeMb]="spatialExtentMaxFileSize"
11
13
  (bboxChange)="onBboxChange($event)"
12
14
  (errorChange)="onSpatialExtentError($event)"
15
+ [attr.data-cy-field]="fieldName"
13
16
  ></gn-ui-spatial-extent-dropdown>
14
17
  } @else {
15
18
  <gn-ui-dropdown-multiselect
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
+ InjectionToken,
4
5
  Input,
5
6
  OnInit,
6
7
  inject,
@@ -27,7 +28,12 @@ import { CommonModule } from '@angular/common'
27
28
  import { BoundingBox } from '../../../../../../libs/util/shared/src'
28
29
  import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
29
30
  import { TranslateService } from '@ngx-translate/core'
30
- import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/src'
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
+ )
31
37
 
32
38
  @Component({
33
39
  selector: 'gn-ui-filter-dropdown',
@@ -52,8 +58,7 @@ export class FilterDropdownComponent implements OnInit {
52
58
  @Input() fieldName: string
53
59
  @Input() title: string
54
60
 
55
- spatialExtentMaxFileSize =
56
- getOptionalSearchConfig()?.SPATIAL_EXTENT_MAX_FILE_SIZE
61
+ spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
57
62
 
58
63
  fieldType: FieldType
59
64
  choices$: Observable<Choice[]>
@@ -71,6 +76,14 @@ export class FilterDropdownComponent implements OnInit {
71
76
  map((selected) => (Array.isArray(selected) ? {} : (selected as DateRange)))
72
77
  ) as Observable<DateRange>
73
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
+
74
87
  onSelectedValues(values: unknown[]) {
75
88
  this.fieldsService
76
89
  .buildFiltersFromFieldValues({ [this.fieldName]: values as FieldValue[] })
@@ -80,7 +93,11 @@ export class FilterDropdownComponent implements OnInit {
80
93
  private spatialExtentErrorNotificationId: number | null = null
81
94
 
82
95
  onBboxChange(bbox: BoundingBox | null) {
83
- console.log(bbox)
96
+ this.fieldsService
97
+ .buildFiltersFromFieldValues({
98
+ [this.fieldName]: bbox,
99
+ })
100
+ .subscribe((filters) => this.searchService.updateFilters(filters))
84
101
  this.clearSpatialExtentErrorNotification()
85
102
  }
86
103
 
@@ -5,22 +5,31 @@ import {
5
5
  GeocodingResult,
6
6
  queryGeonames,
7
7
  GeonamesOptions,
8
- DataGouvFrOptions,
9
- queryDataGouvFr,
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 DataGouvFrGeocodingProvider = ['data-gouv-fr', DataGouvFrOptions]
16
+ type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
17
17
  export type GeocodingProvider =
18
18
  | GeoadminGeocodingProvider
19
19
  | GeonamesGeocodingProvider
20
- | DataGouvFrGeocodingProvider
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 'data-gouv-fr':
54
+ case 'geoplateforme':
46
55
  queryObservable = from(
47
- queryDataGouvFr(text, this.provider[1] as DataGouvFrOptions)
56
+ queryGeoplateforme(text, this.provider[1] as GeoplateformeOptions)
48
57
  )
49
58
  break
50
59
  default:
@@ -0,0 +1,8 @@
1
+ <gn-ui-autocomplete
2
+ [action]="searchAction"
3
+ [displayWithFn]="displayWithFn"
4
+ [placeholder]="placeholder"
5
+ [displayWithTemplate]="displayWithTemplate"
6
+ (itemSelected)="handleItemSelected($event)"
7
+ >
8
+ </gn-ui-autocomplete>
@@ -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
+ }
@@ -20,6 +20,8 @@ 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')
23
25
 
24
26
  const OPEN_BOUND = '…'
25
27
 
@@ -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: getOptionalSearchConfig()?.LIMIT
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,4 +1,4 @@
1
- import { Injectable, Injector, inject } from '@angular/core'
1
+ import { inject, Injectable, InjectionToken, Injector } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
@@ -11,10 +11,11 @@ import {
11
11
  MultilingualSearchField,
12
12
  OrganizationSearchField,
13
13
  OwnerSearchField,
14
+ RecordKindField,
15
+ ResourceCreationRevisionDateSearchField,
14
16
  ResourceTypeLegacyField,
15
17
  SimpleSearchField,
16
18
  TranslatedSearchField,
17
- RecordKindField,
18
19
  UserSearchField,
19
20
  } from './fields'
20
21
  import { forkJoin, Observable, of } from 'rxjs'
@@ -43,14 +44,30 @@ marker('search.filters.producerOrg')
43
44
  marker('search.filters.publisherOrg')
44
45
  marker('search.filters.user')
45
46
  marker('search.filters.changeDate')
47
+ marker('search.filters.resourceCreationRevisionDate')
48
+ marker('search.filters.temporalExtent')
46
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
+ }
58
+
59
+ export const CUSTOM_FIELDS = new InjectionToken<CustomSearchField[]>(
60
+ 'custom-fields'
61
+ )
47
62
  @Injectable({
48
63
  providedIn: 'root',
49
64
  })
50
65
  export class FieldsService {
51
66
  protected injector = inject(Injector)
67
+ private customFields =
68
+ inject<CustomSearchField[]>(CUSTOM_FIELDS, { optional: true }) ?? []
52
69
 
53
- protected fields = {
70
+ private baseFields: Record<string, AbstractSearchField> = {
54
71
  organization: new OrganizationSearchField(this.injector),
55
72
  format: new SimpleSearchField('format', this.injector, 'asc'),
56
73
  resourceType: new ResourceTypeLegacyField(this.injector), // Deprecated, use `recordKind` instead
@@ -95,9 +112,44 @@ export class FieldsService {
95
112
  ),
96
113
  user: new UserSearchField(this.injector),
97
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
+ ),
98
124
  availableServices: new AvailableServicesField(this.injector),
99
125
  spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
100
- } as Record<string, AbstractSearchField>
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
+ }
101
153
 
102
154
  get supportedFields() {
103
155
  return Object.keys(this.fields)