geonetwork-ui 2.11.0-dev.4976ded4f → 2.11.0-dev.57f4be358
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 +1291 -1283
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +116 -80
- package/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +90 -31
- package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +12 -2
- package/src/libs/feature/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +3 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +21 -4
- package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +2 -0
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +67 -9
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +5 -11
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -4
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/util/app-config/src/lib/app-config.ts +22 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +33 -1
- package/src/libs/util/app-config/src/lib/model.ts +14 -1
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
- package/translations/de.json +10 -3
- package/translations/en.json +11 -4
- package/translations/es.json +10 -3
- package/translations/fr.json +15 -8
- package/translations/it.json +10 -3
- package/translations/nl.json +10 -3
- package/translations/pt.json +10 -3
- package/translations/sk.json +10 -3
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.css +0 -0
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
- package/src/libs/feature/map/src/lib/style/index.ts +0 -2
- package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
- package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
|
@@ -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<
|
|
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:
|
|
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(
|
|
140
|
-
|
|
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) =>
|
|
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
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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':
|
|
@@ -21,7 +21,10 @@ export type QueryString = {
|
|
|
21
21
|
query_string: string
|
|
22
22
|
}
|
|
23
23
|
export type QueryRange = {
|
|
24
|
-
range: Record<
|
|
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
|
|
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 {}
|
package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts
CHANGED
|
@@ -33,7 +33,6 @@ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain
|
|
|
33
33
|
import { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
|
|
34
34
|
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
35
35
|
import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
|
|
36
|
-
import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
|
|
37
36
|
import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
|
|
38
37
|
import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
|
|
39
38
|
import { matCloseOutline } from '@ng-icons/material-icons/outline'
|
|
@@ -48,6 +47,10 @@ marker('notify.reuse.form.error.title')
|
|
|
48
47
|
marker('notify.reuse.form.error.body')
|
|
49
48
|
|
|
50
49
|
export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
|
|
50
|
+
// language assigned to the created reuse record; falls back to the current UI language
|
|
51
|
+
export const NEW_RECORD_DEFAULT_LANGUAGE = new InjectionToken<string>(
|
|
52
|
+
'newRecordDefaultLanguage'
|
|
53
|
+
)
|
|
51
54
|
|
|
52
55
|
@Component({
|
|
53
56
|
selector: 'gn-ui-notify-reuse-form',
|
|
@@ -86,6 +89,9 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
86
89
|
private readonly notificationsService = inject(NotificationsService)
|
|
87
90
|
private readonly platformServiceInterface = inject(PlatformServiceInterface)
|
|
88
91
|
reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
|
|
92
|
+
private newRecordDefaultLanguage = inject(NEW_RECORD_DEFAULT_LANGUAGE, {
|
|
93
|
+
optional: true,
|
|
94
|
+
})
|
|
89
95
|
private locationStrategy = inject(LocationStrategy)
|
|
90
96
|
|
|
91
97
|
me = toSignal(this.platformServiceInterface.getMe())
|
|
@@ -158,8 +164,7 @@ export class NotifyReuseFormComponent implements OnDestroy {
|
|
|
158
164
|
submit() {
|
|
159
165
|
if (!this.isFormValid()) return
|
|
160
166
|
const defaultLang =
|
|
161
|
-
|
|
162
|
-
this.translate.currentLang
|
|
167
|
+
this.newRecordDefaultLanguage ?? this.translate.currentLang
|
|
163
168
|
const onlineResource: OnlineLinkResource = {
|
|
164
169
|
type: 'link',
|
|
165
170
|
url: new URL(this.url()),
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
8
|
+
GeoplateformeOptions,
|
|
9
|
+
queryGeoplateforme,
|
|
10
10
|
} from '@geospatial-sdk/geocoding'
|
|
11
11
|
import { from, Observable, throwError } from 'rxjs'
|
|
12
12
|
import { catchError } from 'rxjs/operators'
|
|
13
13
|
|
|
14
14
|
type GeoadminGeocodingProvider = ['geoadmin', GeoadminOptions]
|
|
15
15
|
type GeonamesGeocodingProvider = ['geonames', GeonamesOptions]
|
|
16
|
-
type
|
|
16
|
+
type GeoplateformeGeocodingProvider = ['geoplateforme', GeoplateformeOptions]
|
|
17
17
|
export type GeocodingProvider =
|
|
18
18
|
| GeoadminGeocodingProvider
|
|
19
19
|
| GeonamesGeocodingProvider
|
|
20
|
-
|
|
|
20
|
+
| GeoplateformeGeocodingProvider
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_GEOCODING_PROVIDER = [
|
|
23
|
+
'geonames',
|
|
24
|
+
{ maxRows: 5 },
|
|
25
|
+
] as GeocodingProvider
|
|
21
26
|
|
|
22
27
|
export const GEOCODING_PROVIDER = new InjectionToken<GeocodingProvider>(
|
|
23
|
-
'geocoding-provider'
|
|
28
|
+
'geocoding-provider',
|
|
29
|
+
{
|
|
30
|
+
providedIn: 'root',
|
|
31
|
+
factory: (): GeocodingProvider => DEFAULT_GEOCODING_PROVIDER,
|
|
32
|
+
}
|
|
24
33
|
)
|
|
25
34
|
|
|
26
35
|
@Injectable({
|
|
@@ -42,9 +51,9 @@ export class GeocodingService {
|
|
|
42
51
|
queryGeonames(text, this.provider[1] as GeonamesOptions)
|
|
43
52
|
)
|
|
44
53
|
break
|
|
45
|
-
case '
|
|
54
|
+
case 'geoplateforme':
|
|
46
55
|
queryObservable = from(
|
|
47
|
-
|
|
56
|
+
queryGeoplateforme(text, this.provider[1] as GeoplateformeOptions)
|
|
48
57
|
)
|
|
49
58
|
break
|
|
50
59
|
default:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
EventEmitter,
|
|
5
|
+
inject,
|
|
6
|
+
Input,
|
|
7
|
+
Output,
|
|
8
|
+
TemplateRef,
|
|
9
|
+
} from '@angular/core'
|
|
10
|
+
import { Observable } from 'rxjs'
|
|
11
|
+
import { GeocodingResult } from '@geospatial-sdk/geocoding'
|
|
12
|
+
import {
|
|
13
|
+
AutocompleteComponent,
|
|
14
|
+
AutocompleteItem,
|
|
15
|
+
} from '../../../../../../libs/ui/inputs/src'
|
|
16
|
+
import { GeocodingService } from '../geocoding/geocoding.service'
|
|
17
|
+
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'gn-ui-location-search',
|
|
20
|
+
templateUrl: './location-search.component.html',
|
|
21
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
22
|
+
standalone: true,
|
|
23
|
+
imports: [AutocompleteComponent],
|
|
24
|
+
})
|
|
25
|
+
export class LocationSearchComponent {
|
|
26
|
+
private geocodingService = inject(GeocodingService)
|
|
27
|
+
|
|
28
|
+
@Input() placeholder = ''
|
|
29
|
+
@Input() displayWithTemplate?: TemplateRef<{ $implicit: GeocodingResult }>
|
|
30
|
+
@Output() resultSelected = new EventEmitter<GeocodingResult>()
|
|
31
|
+
|
|
32
|
+
displayWithFn = (item: AutocompleteItem) => (item as GeocodingResult).label
|
|
33
|
+
|
|
34
|
+
searchAction = (text: string): Observable<AutocompleteItem[]> =>
|
|
35
|
+
this.geocodingService.query(text)
|
|
36
|
+
|
|
37
|
+
handleItemSelected(item: AutocompleteItem) {
|
|
38
|
+
this.resultSelected.emit(item as GeocodingResult)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -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:
|
|
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,
|
|
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
|
-
|
|
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
|
-
}
|
|
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)
|