geonetwork-ui 2.11.0-dev.1cf4ab0c6 → 2.11.0-dev.448254758
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 +1138 -1249
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +79 -67
- package/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +40 -9
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +8 -1
- 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.ts +8 -3
- 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/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +25 -7
- 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 +2 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +8 -2
- 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 +20 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
- package/src/libs/util/app-config/src/lib/model.ts +14 -1
- package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
- package/translations/de.json +2 -2
- package/translations/en.json +4 -4
- package/translations/es.json +2 -2
- package/translations/fr.json +5 -5
- package/translations/it.json +2 -2
- package/translations/nl.json +2 -2
- package/translations/pt.json +2 -2
- package/translations/sk.json +2 -2
- 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
|
@@ -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[]>
|
|
@@ -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
|
+
}
|
|
@@ -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,
|
|
@@ -435,17 +435,35 @@ export class DateRangeSearchField extends SimpleSearchField {
|
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
// treats a resource's creation/revision dates as an interval (its min and max), so a record matches
|
|
439
|
+
// when that interval intersects the filter range, not just when a single date falls inside it
|
|
438
440
|
export class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
|
|
439
441
|
constructor(injector: Injector, order: 'asc' | 'desc' = 'desc') {
|
|
440
442
|
super('resourceCreationRevisionDate', injector, order)
|
|
443
|
+
const dateSourceFields = [
|
|
444
|
+
'creationDateForResource',
|
|
445
|
+
'revisionDateForResource',
|
|
446
|
+
]
|
|
447
|
+
const collectSortedDates = dateSourceFields
|
|
448
|
+
.map(
|
|
449
|
+
(field) => `if (doc.containsKey('${field}')) {
|
|
450
|
+
for (def date : doc['${field}']) { dates.add(date.millis); }
|
|
451
|
+
}`
|
|
452
|
+
)
|
|
453
|
+
.join('\n')
|
|
454
|
+
|
|
441
455
|
this.esService.registerRuntimeField(
|
|
442
|
-
'
|
|
443
|
-
`
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
456
|
+
'resourceCreationRevisionDateMin',
|
|
457
|
+
`def dates = [];
|
|
458
|
+
${collectSortedDates}
|
|
459
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`,
|
|
460
|
+
'date'
|
|
461
|
+
)
|
|
462
|
+
this.esService.registerRuntimeField(
|
|
463
|
+
'resourceCreationRevisionDateMax',
|
|
464
|
+
`def dates = [];
|
|
465
|
+
${collectSortedDates}
|
|
466
|
+
if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`,
|
|
449
467
|
'date'
|
|
450
468
|
)
|
|
451
469
|
}
|
|
@@ -83,7 +83,16 @@
|
|
|
83
83
|
>
|
|
84
84
|
@for (suggestion of suggestions$ | async; track suggestion) {
|
|
85
85
|
<mat-option [value]="suggestion" class="p-2 suggestion text-start">
|
|
86
|
-
|
|
86
|
+
@if (displayWithTemplate) {
|
|
87
|
+
<ng-container
|
|
88
|
+
*ngTemplateOutlet="
|
|
89
|
+
displayWithTemplate;
|
|
90
|
+
context: { $implicit: suggestion }
|
|
91
|
+
"
|
|
92
|
+
></ng-container>
|
|
93
|
+
} @else {
|
|
94
|
+
{{ displayWithFnInternal(suggestion) }}
|
|
95
|
+
}
|
|
87
96
|
</mat-option>
|
|
88
97
|
}
|
|
89
98
|
</mat-autocomplete>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
OnInit,
|
|
12
12
|
Output,
|
|
13
13
|
SimpleChanges,
|
|
14
|
+
TemplateRef,
|
|
14
15
|
ViewChild,
|
|
15
16
|
inject,
|
|
16
17
|
} from '@angular/core'
|
|
@@ -97,6 +98,13 @@ export class AutocompleteComponent
|
|
|
97
98
|
@ViewChild(MatAutocompleteTrigger) triggerRef: MatAutocompleteTrigger
|
|
98
99
|
@ViewChild(MatAutocomplete) autocomplete: MatAutocomplete
|
|
99
100
|
@ViewChild('searchInput') inputRef: ElementRef<HTMLInputElement>
|
|
101
|
+
/**
|
|
102
|
+
* Custom template to render each suggestion. Takes precedence over
|
|
103
|
+
* `displayWithFn` when set.
|
|
104
|
+
*/
|
|
105
|
+
@Input() displayWithTemplate?: TemplateRef<{
|
|
106
|
+
$implicit: AutocompleteItem
|
|
107
|
+
}>
|
|
100
108
|
|
|
101
109
|
searching: boolean
|
|
102
110
|
control = new UntypedFormControl()
|
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
</div>
|
|
21
21
|
}
|
|
22
22
|
</div>
|
|
23
|
-
<button
|
|
23
|
+
<button
|
|
24
|
+
class="h-6 w-6 flex items-center justify-center"
|
|
25
|
+
data-test="dropdown-clear"
|
|
26
|
+
>
|
|
24
27
|
@if (hasSelectedChoices && !overlayOpen) {
|
|
25
28
|
<ng-icon
|
|
26
|
-
class="shrink-0 opacity-40
|
|
29
|
+
class="shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn"
|
|
27
30
|
(click)="clearSelection($event)"
|
|
28
31
|
name="matClose"
|
|
29
32
|
></ng-icon>
|
|
@@ -61,13 +64,13 @@
|
|
|
61
64
|
#overlayContainer
|
|
62
65
|
>
|
|
63
66
|
<div
|
|
64
|
-
class="border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-
|
|
67
|
+
class="border border-gray-300 rounded mb-2 mx-2 min-h-[44px] flex flex-row gap-1 flex-wrap p-2 focus-within:rounded focus-within:border-2 focus-within:border-primary"
|
|
65
68
|
>
|
|
66
69
|
@for (selected of selectedChoices; track selected) {
|
|
67
70
|
<button
|
|
68
71
|
type="button"
|
|
69
72
|
[title]="selected.label"
|
|
70
|
-
class="max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity
|
|
73
|
+
class="max-w-full bg-main text-white rounded pr-[7px] flex gap-1 items-center opacity-70 hover:opacity-100 focus:opacity-100 transition-opacity"
|
|
71
74
|
(click)="select(selected, false)"
|
|
72
75
|
>
|
|
73
76
|
<div class="text-sm truncate leading-[26px] px-2">
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
propagateToDocumentOnly,
|
|
25
25
|
} from '../../../../../../libs/util/shared/src'
|
|
26
26
|
import { ButtonComponent } from '../button/button.component'
|
|
27
|
-
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
27
|
+
import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
|
|
28
28
|
import { FormsModule } from '@angular/forms'
|
|
29
29
|
import { TranslatePipe } from '@ngx-translate/core'
|
|
30
30
|
|
|
@@ -46,19 +46,22 @@ import {
|
|
|
46
46
|
matExpandMore,
|
|
47
47
|
matExpandLess,
|
|
48
48
|
}),
|
|
49
|
+
provideNgIconsConfig({
|
|
50
|
+
size: '1.5rem',
|
|
51
|
+
}),
|
|
49
52
|
],
|
|
50
53
|
standalone: true,
|
|
51
54
|
})
|
|
52
|
-
export class DropdownMultiselectComponent {
|
|
55
|
+
export class DropdownMultiselectComponent<T = unknown> {
|
|
53
56
|
private scrollStrategies = inject(ScrollStrategyOptions)
|
|
54
57
|
|
|
55
58
|
@Input() title: string
|
|
56
|
-
@Input() choices: Choice[]
|
|
57
|
-
@Input() selected:
|
|
59
|
+
@Input() choices: Choice<T>[]
|
|
60
|
+
@Input() selected: T[] = []
|
|
58
61
|
@Input() allowSearch = true
|
|
59
62
|
@Input() maxRows: number
|
|
60
63
|
@Input() searchInputValue = ''
|
|
61
|
-
@Output() selectValues = new EventEmitter<
|
|
64
|
+
@Output() selectValues = new EventEmitter<T[]>()
|
|
62
65
|
@ViewChild('overlayOrigin') overlayOrigin: CdkOverlayOrigin
|
|
63
66
|
@ViewChild(CdkConnectedOverlay) overlay: CdkConnectedOverlay
|
|
64
67
|
@ViewChild('overlayContainer', { read: ElementRef })
|
|
@@ -115,7 +118,7 @@ export class DropdownMultiselectComponent {
|
|
|
115
118
|
|
|
116
119
|
private setFocus() {
|
|
117
120
|
setTimeout(() => {
|
|
118
|
-
this.searchFieldInput
|
|
121
|
+
this.searchFieldInput?.nativeElement.focus()
|
|
119
122
|
}, 0)
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -194,18 +197,18 @@ export class DropdownMultiselectComponent {
|
|
|
194
197
|
this.checkboxes.get(newIndex).nativeElement.focus()
|
|
195
198
|
}
|
|
196
199
|
|
|
197
|
-
isSelected(choice: Choice) {
|
|
200
|
+
isSelected(choice: Choice<T>) {
|
|
198
201
|
return this.selected.indexOf(choice.value) > -1
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
select(choice: Choice
|
|
204
|
+
select(choice: Choice<T>, selected: boolean) {
|
|
202
205
|
this.selected = selected
|
|
203
206
|
? [...this.selected.filter((v) => v !== choice.value), choice.value]
|
|
204
207
|
: this.selected.filter((v) => v !== choice.value)
|
|
205
208
|
this.selectValues.emit(this.selected)
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
toggle(choice: Choice) {
|
|
211
|
+
toggle(choice: Choice<T>) {
|
|
209
212
|
this.select(choice, !this.isSelected(choice))
|
|
210
213
|
}
|
|
211
214
|
|
package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
[class]="
|
|
59
59
|
(hasSelection
|
|
60
60
|
? 'border-b-[1px] border-gray-400 pb-[20px]'
|
|
61
|
-
: 'pb-[
|
|
61
|
+
: 'pb-[12px]') + ' text-black'
|
|
62
62
|
"
|
|
63
63
|
>
|
|
64
64
|
<gn-ui-drag-and-drop-file-input
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
[showFileName]="false"
|
|
69
69
|
textClass="text-black truncate"
|
|
70
70
|
dropzoneBackgroundColor="transparent"
|
|
71
|
-
icon="
|
|
71
|
+
icon="iconoirUpload"
|
|
72
72
|
extraClass="gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between"
|
|
73
73
|
(fileChange)="handleFileSelected($event)"
|
|
74
74
|
(errorChange)="handleFileError($event)"
|
package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts
CHANGED
|
@@ -20,9 +20,9 @@ import { ButtonComponent } from '../button/button.component'
|
|
|
20
20
|
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
21
21
|
import {
|
|
22
22
|
iconoirCheckCircle,
|
|
23
|
-
iconoirImport,
|
|
24
23
|
iconoirSquareDashed,
|
|
25
24
|
iconoirTrash,
|
|
25
|
+
iconoirUpload,
|
|
26
26
|
} from '@ng-icons/iconoir'
|
|
27
27
|
import {
|
|
28
28
|
matClose,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
BoundingBox,
|
|
36
36
|
getGeometryBoundingBox,
|
|
37
37
|
getGeometryFromGeoJSON,
|
|
38
|
+
isBoundingBoxWithinWorldExtent,
|
|
38
39
|
propagateToDocumentOnly,
|
|
39
40
|
readFileAsText,
|
|
40
41
|
} from '../../../../../../libs/util/shared/src'
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
marker('search.filters.spatialExtent.import')
|
|
47
48
|
marker('search.filters.spatialExtent.helpText')
|
|
48
49
|
marker('search.filters.spatialExtent.error.title')
|
|
50
|
+
marker('search.filters.spatialExtent.error.outOfBounds')
|
|
49
51
|
|
|
50
52
|
const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile')
|
|
51
53
|
const LABEL_FROM_FILE_DELETE = marker(
|
|
@@ -74,9 +76,9 @@ export interface SpatialExtentDropdownError {
|
|
|
74
76
|
providers: [
|
|
75
77
|
provideIcons({
|
|
76
78
|
iconoirCheckCircle,
|
|
77
|
-
iconoirImport,
|
|
78
79
|
iconoirSquareDashed,
|
|
79
80
|
iconoirTrash,
|
|
81
|
+
iconoirUpload,
|
|
80
82
|
matClose,
|
|
81
83
|
matExpandLess,
|
|
82
84
|
matExpandMore,
|
|
@@ -178,6 +180,10 @@ export class SpatialExtentDropdownComponent {
|
|
|
178
180
|
return
|
|
179
181
|
}
|
|
180
182
|
const bbox = getGeometryBoundingBox(geometry)
|
|
183
|
+
if (!isBoundingBoxWithinWorldExtent(bbox)) {
|
|
184
|
+
this.setError(marker('search.filters.spatialExtent.error.outOfBounds'))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
181
187
|
this.bbox = bbox
|
|
182
188
|
this.fileName = file.name
|
|
183
189
|
this.bboxChange.emit(bbox)
|
|
@@ -253,6 +253,8 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
253
253
|
'advanced_filters',
|
|
254
254
|
'limit',
|
|
255
255
|
'spatial_extent_max_file_size',
|
|
256
|
+
'geocoding_provider',
|
|
257
|
+
'geocoding_provider_options',
|
|
256
258
|
],
|
|
257
259
|
warnings,
|
|
258
260
|
errors
|
|
@@ -265,6 +267,14 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
265
267
|
warnings,
|
|
266
268
|
errors
|
|
267
269
|
)
|
|
270
|
+
const parsedCustomFilters = parseMultiConfigSection(
|
|
271
|
+
parsed,
|
|
272
|
+
'custom_filter',
|
|
273
|
+
['name', 'base_filter'],
|
|
274
|
+
['exclude_values', 'include_values', 'label_key'],
|
|
275
|
+
warnings,
|
|
276
|
+
errors
|
|
277
|
+
)
|
|
268
278
|
searchConfig =
|
|
269
279
|
parsedSearchSection === null
|
|
270
280
|
? null
|
|
@@ -281,9 +291,19 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
281
291
|
filters: param.filters,
|
|
282
292
|
})),
|
|
283
293
|
ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
|
|
294
|
+
CUSTOM_FILTERS: parsedCustomFilters.map((customFilter) => ({
|
|
295
|
+
name: customFilter.name,
|
|
296
|
+
baseFilter: customFilter.base_filter,
|
|
297
|
+
excludeValues: customFilter.exclude_values,
|
|
298
|
+
includeValues: customFilter.include_values,
|
|
299
|
+
labelKey: customFilter.label_key,
|
|
300
|
+
})),
|
|
284
301
|
LIMIT: parsedSearchSection.limit,
|
|
285
302
|
SPATIAL_EXTENT_MAX_FILE_SIZE:
|
|
286
303
|
parsedSearchSection.spatial_extent_max_file_size,
|
|
304
|
+
GEOCODING_PROVIDER: parsedSearchSection.geocoding_provider,
|
|
305
|
+
GEOCODING_PROVIDER_OPTIONS:
|
|
306
|
+
parsedSearchSection.geocoding_provider_options,
|
|
287
307
|
} as SearchConfig)
|
|
288
308
|
|
|
289
309
|
const parsedMetadataQualitySection = parseConfigSection(
|
|
@@ -41,7 +41,22 @@ fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans"
|
|
|
41
41
|
record_kind_quick_filter = false
|
|
42
42
|
filter_geometry_url = 'https://my.domain.org/geom.json'
|
|
43
43
|
do_not_use_default_search_preset = false
|
|
44
|
-
|
|
44
|
+
geocoding_provider = "geoplateforme"
|
|
45
|
+
geocoding_provider_options.category = "administratif"
|
|
46
|
+
geocoding_provider_options.limit = 5
|
|
47
|
+
advanced_filters = ['publicationYear', 'documentStandard', 'inspireKeyword', 'topic', 'license', 'myOrg:firstCustomFilter', 'myOrg:secondCustomFilter']
|
|
48
|
+
|
|
49
|
+
[[custom_filter]]
|
|
50
|
+
name = 'myOrg:firstCustomFilter'
|
|
51
|
+
base_filter = 'keyword'
|
|
52
|
+
exclude_values = ['my keyword 1', 'my keyword 2']
|
|
53
|
+
label_key = 'myOrg.firstCustomFilter'
|
|
54
|
+
|
|
55
|
+
[[custom_filter]]
|
|
56
|
+
name = 'myOrg:secondCustomFilter'
|
|
57
|
+
base_filter = 'keyword'
|
|
58
|
+
include_values = ['my keyword 3', 'my keyword 4']
|
|
59
|
+
label_key = 'myOrg.secondCustomFilter'
|
|
45
60
|
|
|
46
61
|
[[search_preset]]
|
|
47
62
|
sort = "-createDate"
|
|
@@ -55,15 +55,28 @@ export interface SearchPreset {
|
|
|
55
55
|
filters: Record<string, string[] | string>
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export type GeocodingProviderName = 'geoadmin' | 'geonames' | 'geoplateforme'
|
|
59
|
+
|
|
60
|
+
export interface CustomSearchFilterField {
|
|
61
|
+
name: string
|
|
62
|
+
baseFilter: string
|
|
63
|
+
excludeValues?: string[]
|
|
64
|
+
includeValues?: string[]
|
|
65
|
+
labelKey?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
export interface SearchConfig {
|
|
59
69
|
RECORD_KIND_QUICK_FILTER?: boolean
|
|
60
70
|
FILTER_GEOMETRY_URL?: string
|
|
61
71
|
FILTER_GEOMETRY_DATA?: string
|
|
62
72
|
DO_NOT_USE_DEFAULT_SEARCH_PRESET?: boolean
|
|
63
73
|
SEARCH_PRESET?: SearchPreset[]
|
|
64
|
-
ADVANCED_FILTERS?: []
|
|
74
|
+
ADVANCED_FILTERS?: string[]
|
|
75
|
+
CUSTOM_FILTERS?: CustomSearchFilterField[]
|
|
65
76
|
LIMIT?: number
|
|
66
77
|
SPATIAL_EXTENT_MAX_FILE_SIZE?: number
|
|
78
|
+
GEOCODING_PROVIDER?: GeocodingProviderName
|
|
79
|
+
GEOCODING_PROVIDER_OPTIONS?: Record<string, string | number>
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
export interface MetadataQualityConfig {
|
|
@@ -46,6 +46,22 @@ export function isBoundingBox(value: unknown): value is BoundingBox {
|
|
|
46
46
|
)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Checks that a bounding box's coordinates stay within the valid
|
|
51
|
+
* longitude (-180/180) and latitude (-90/90) ranges.
|
|
52
|
+
*/
|
|
53
|
+
export function isBoundingBoxWithinWorldExtent(bbox: BoundingBox): boolean {
|
|
54
|
+
const [minX, minY, maxX, maxY] = bbox
|
|
55
|
+
return (
|
|
56
|
+
minX >= -180 &&
|
|
57
|
+
maxX <= 180 &&
|
|
58
|
+
minY >= -90 &&
|
|
59
|
+
maxY <= 90 &&
|
|
60
|
+
minX <= maxX &&
|
|
61
|
+
minY <= maxY
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
export function getGeometryBoundingBox(geometry: Geometry): BoundingBox {
|
|
50
66
|
// use the bounding box if specified in the GeoJSON object
|
|
51
67
|
if (geometry.bbox) {
|
package/translations/de.json
CHANGED
|
@@ -425,7 +425,6 @@
|
|
|
425
425
|
"map.add.layer.wms": "Aus WMS",
|
|
426
426
|
"map.addFromFile.placeholder": "Klicke hier oder ziehe eine Datei herein",
|
|
427
427
|
"map.dropdown.placeholder": "Kein Vorschaulayer verfügbar",
|
|
428
|
-
"map.geocoding.placeholder": "Nach einem Ort suchen",
|
|
429
428
|
"map.help.addFromFile": "Klicke oder ziehe eine Datei herein um eine Karte hinzuzufügen (momentan wird nur das GeoJSON-Format unterstützt).",
|
|
430
429
|
"map.layer.add": "Hinzufügen",
|
|
431
430
|
"map.layers.available": "Verfügbare Layer",
|
|
@@ -692,7 +691,6 @@
|
|
|
692
691
|
"search.filters.representationType": "Repräsentationstyp",
|
|
693
692
|
"search.filters.resourceCreationRevisionDate": "Ressourcendatum",
|
|
694
693
|
"search.filters.resourceType": "Ressourcentyp",
|
|
695
|
-
"search.filters.spatialExtent": "",
|
|
696
694
|
"search.filters.spatialExtent.bboxFromFile": "",
|
|
697
695
|
"search.filters.spatialExtent.bboxFromFileDelete": "",
|
|
698
696
|
"search.filters.spatialExtent.bboxInitial": "",
|
|
@@ -700,7 +698,9 @@
|
|
|
700
698
|
"search.filters.spatialExtent.error.fileTooLarge": "",
|
|
701
699
|
"search.filters.spatialExtent.error.invalidFormat": "",
|
|
702
700
|
"search.filters.spatialExtent.error.noGeometry": "",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "",
|
|
703
702
|
"search.filters.spatialExtent.error.title": "",
|
|
703
|
+
"search.filters.spatialExtent": "",
|
|
704
704
|
"search.filters.spatialExtent.helpText": "",
|
|
705
705
|
"search.filters.spatialExtent.import": "",
|
|
706
706
|
"search.filters.standard": "Standard",
|
package/translations/en.json
CHANGED
|
@@ -425,7 +425,6 @@
|
|
|
425
425
|
"map.add.layer.wms": "From WMS",
|
|
426
426
|
"map.addFromFile.placeholder": "Click or drop a file here",
|
|
427
427
|
"map.dropdown.placeholder": "No preview layer available",
|
|
428
|
-
"map.geocoding.placeholder": "Search for a place",
|
|
429
428
|
"map.help.addFromFile": "Click or drag and drop a file to add to the map (currently supports GeoJSON format only).",
|
|
430
429
|
"map.layer.add": "Add",
|
|
431
430
|
"map.layers.available": "Available Layers",
|
|
@@ -692,16 +691,17 @@
|
|
|
692
691
|
"search.filters.representationType": "Representation type",
|
|
693
692
|
"search.filters.resourceCreationRevisionDate": "Resource date",
|
|
694
693
|
"search.filters.resourceType": "Resource type",
|
|
695
|
-
"search.filters.spatialExtent": "
|
|
696
|
-
"search.filters.spatialExtent.bboxFromFile": "extent from {fileName}",
|
|
694
|
+
"search.filters.spatialExtent.bboxFromFile": "Extent from {fileName}",
|
|
697
695
|
"search.filters.spatialExtent.bboxFromFileDelete": "Delete {fileName}",
|
|
698
696
|
"search.filters.spatialExtent.bboxInitial": "Pre-selected extent",
|
|
699
697
|
"search.filters.spatialExtent.bboxInitialDelete": "Delete selection",
|
|
700
698
|
"search.filters.spatialExtent.error.fileTooLarge": "The file exceeds the maximum allowed size ({maxSize} MB)",
|
|
701
699
|
"search.filters.spatialExtent.error.invalidFormat": "The file could not be read as a valid GeoJSON",
|
|
702
700
|
"search.filters.spatialExtent.error.noGeometry": "No geometry could be found in the file",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "The geometry of the GeoJSON file is not compliant; either some coordinates are out of bounds ([-180, 180] for longitude and [-90, 90] for latitude), or the file uses projected coordinates instead of longitude and latitude",
|
|
703
702
|
"search.filters.spatialExtent.error.title": "GeoJSON import error",
|
|
704
|
-
"search.filters.spatialExtent
|
|
703
|
+
"search.filters.spatialExtent": "Spatial extent",
|
|
704
|
+
"search.filters.spatialExtent.helpText": "The extent of the selected geometry will be used",
|
|
705
705
|
"search.filters.spatialExtent.import": "Import a GeoJSON",
|
|
706
706
|
"search.filters.standard": "Standard",
|
|
707
707
|
"search.filters.summaryLabel.changeDate": "Updated: ",
|
package/translations/es.json
CHANGED
|
@@ -425,7 +425,6 @@
|
|
|
425
425
|
"map.add.layer.wms": "",
|
|
426
426
|
"map.addFromFile.placeholder": "",
|
|
427
427
|
"map.dropdown.placeholder": "",
|
|
428
|
-
"map.geocoding.placeholder": "",
|
|
429
428
|
"map.help.addFromFile": "",
|
|
430
429
|
"map.layer.add": "",
|
|
431
430
|
"map.layers.available": "",
|
|
@@ -692,7 +691,6 @@
|
|
|
692
691
|
"search.filters.representationType": "",
|
|
693
692
|
"search.filters.resourceCreationRevisionDate": "",
|
|
694
693
|
"search.filters.resourceType": "",
|
|
695
|
-
"search.filters.spatialExtent": "",
|
|
696
694
|
"search.filters.spatialExtent.bboxFromFile": "",
|
|
697
695
|
"search.filters.spatialExtent.bboxFromFileDelete": "",
|
|
698
696
|
"search.filters.spatialExtent.bboxInitial": "",
|
|
@@ -700,7 +698,9 @@
|
|
|
700
698
|
"search.filters.spatialExtent.error.fileTooLarge": "",
|
|
701
699
|
"search.filters.spatialExtent.error.invalidFormat": "",
|
|
702
700
|
"search.filters.spatialExtent.error.noGeometry": "",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "",
|
|
703
702
|
"search.filters.spatialExtent.error.title": "",
|
|
703
|
+
"search.filters.spatialExtent": "",
|
|
704
704
|
"search.filters.spatialExtent.helpText": "",
|
|
705
705
|
"search.filters.spatialExtent.import": "",
|
|
706
706
|
"search.filters.standard": "",
|
package/translations/fr.json
CHANGED
|
@@ -425,7 +425,6 @@
|
|
|
425
425
|
"map.add.layer.wms": "Depuis un service WMS",
|
|
426
426
|
"map.addFromFile.placeholder": "Cliquez ou déposez un fichier ici",
|
|
427
427
|
"map.dropdown.placeholder": "Aucune couche d'aperçu disponible",
|
|
428
|
-
"map.geocoding.placeholder": "Rechercher un lieu",
|
|
429
428
|
"map.help.addFromFile": "Cliquez ou glissez-déposez un fichier pour l'ajouter à la carte (supporte actuellement uniquement le format GeoJSON).",
|
|
430
429
|
"map.layer.add": "Ajouter",
|
|
431
430
|
"map.layers.available": "Couches disponibles",
|
|
@@ -692,16 +691,17 @@
|
|
|
692
691
|
"search.filters.representationType": "Type de représentation",
|
|
693
692
|
"search.filters.resourceCreationRevisionDate": "Date de la ressource",
|
|
694
693
|
"search.filters.resourceType": "Type de ressource",
|
|
695
|
-
"search.filters.spatialExtent": "Étendue
|
|
696
|
-
"search.filters.spatialExtent.bboxFromFile": "étendue de {fileName}",
|
|
694
|
+
"search.filters.spatialExtent.bboxFromFile": "Étendue de {fileName}",
|
|
697
695
|
"search.filters.spatialExtent.bboxFromFileDelete": "Supprimer {fileName}",
|
|
698
|
-
"search.filters.spatialExtent.bboxInitial": "
|
|
696
|
+
"search.filters.spatialExtent.bboxInitial": "Étendue présélectionnée",
|
|
699
697
|
"search.filters.spatialExtent.bboxInitialDelete": "Supprimer la sélection",
|
|
700
698
|
"search.filters.spatialExtent.error.fileTooLarge": "Le fichier dépasse la taille maximale autorisée ({maxSize} Mo)",
|
|
701
699
|
"search.filters.spatialExtent.error.invalidFormat": "Le fichier n'a pas pu être lu comme un GeoJSON valide",
|
|
702
700
|
"search.filters.spatialExtent.error.noGeometry": "Aucune géométrie n'a pu être trouvée dans le fichier",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "La géométrie du fichier GeoJSON n'est pas conforme ; soit certaines coordonnées sont hors limites ([-180, 180] pour la longitude et [-90, 90] pour la latitude), soit le fichier utilise des coordonnées projetées au lieu de la longitude et de la latitude.",
|
|
703
702
|
"search.filters.spatialExtent.error.title": "Erreur d'importation GeoJSON",
|
|
704
|
-
"search.filters.spatialExtent
|
|
703
|
+
"search.filters.spatialExtent": "Étendue spatiale",
|
|
704
|
+
"search.filters.spatialExtent.helpText": "L'étendue de la géométrie sélectionnée sera utilisée",
|
|
705
705
|
"search.filters.spatialExtent.import": "Importer un GeoJSON",
|
|
706
706
|
"search.filters.standard": "Standard",
|
|
707
707
|
"search.filters.summaryLabel.changeDate": "Mise à jour : ",
|