geonetwork-ui 2.11.0-dev.80a8b0bfa → 2.11.0-dev.8aa6985e0
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 +836 -362
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +219 -78
- package/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +107 -34
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
- package/src/libs/feature/map/src/index.ts +0 -1
- package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
- package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
- 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 +18 -8
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/index.ts +2 -0
- 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/date-adapter.providers.ts +30 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
- package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
- 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 +111 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
- package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
- package/src/libs/util/app-config/src/lib/model.ts +15 -1
- package/src/libs/util/shared/src/index.ts +1 -0
- package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
- package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +17 -3
- package/translations/en.json +16 -2
- package/translations/es.json +15 -1
- package/translations/fr.json +17 -3
- package/translations/it.json +15 -1
- package/translations/nl.json +15 -1
- package/translations/pt.json +15 -1
- package/translations/sk.json +15 -1
- 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/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
ChangeDetectorRef,
|
|
4
|
+
Component,
|
|
5
|
+
EventEmitter,
|
|
6
|
+
Input,
|
|
7
|
+
Output,
|
|
8
|
+
ViewChild,
|
|
9
|
+
inject,
|
|
10
|
+
} from '@angular/core'
|
|
11
|
+
import {
|
|
12
|
+
CdkConnectedOverlay,
|
|
13
|
+
CdkOverlayOrigin,
|
|
14
|
+
ConnectedPosition,
|
|
15
|
+
OverlayModule,
|
|
16
|
+
ScrollStrategyOptions,
|
|
17
|
+
} from '@angular/cdk/overlay'
|
|
18
|
+
|
|
19
|
+
import { ButtonComponent } from '../button/button.component'
|
|
20
|
+
import { NgIcon, provideIcons } from '@ng-icons/core'
|
|
21
|
+
import {
|
|
22
|
+
iconoirCheckCircle,
|
|
23
|
+
iconoirSquareDashed,
|
|
24
|
+
iconoirTrash,
|
|
25
|
+
iconoirUpload,
|
|
26
|
+
} from '@ng-icons/iconoir'
|
|
27
|
+
import {
|
|
28
|
+
matClose,
|
|
29
|
+
matExpandLess,
|
|
30
|
+
matExpandMore,
|
|
31
|
+
} from '@ng-icons/material-icons/baseline'
|
|
32
|
+
import { TranslatePipe } from '@ngx-translate/core'
|
|
33
|
+
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
34
|
+
import {
|
|
35
|
+
BoundingBox,
|
|
36
|
+
getGeometryBoundingBox,
|
|
37
|
+
getGeometryFromGeoJSON,
|
|
38
|
+
isBoundingBoxWithinWorldExtent,
|
|
39
|
+
propagateToDocumentOnly,
|
|
40
|
+
readFileAsText,
|
|
41
|
+
} from '../../../../../../libs/util/shared/src'
|
|
42
|
+
import {
|
|
43
|
+
DragAndDropFileInputComponent,
|
|
44
|
+
DragAndDropFileInputError,
|
|
45
|
+
} from '../drag-and-drop-file-input/drag-and-drop-file-input.component'
|
|
46
|
+
|
|
47
|
+
marker('search.filters.spatialExtent.import')
|
|
48
|
+
marker('search.filters.spatialExtent.helpText')
|
|
49
|
+
marker('search.filters.spatialExtent.error.title')
|
|
50
|
+
marker('search.filters.spatialExtent.error.outOfBounds')
|
|
51
|
+
|
|
52
|
+
const LABEL_FROM_FILE = marker('search.filters.spatialExtent.bboxFromFile')
|
|
53
|
+
const LABEL_FROM_FILE_DELETE = marker(
|
|
54
|
+
'search.filters.spatialExtent.bboxFromFileDelete'
|
|
55
|
+
)
|
|
56
|
+
const LABEL_INITIAL = marker('search.filters.spatialExtent.bboxInitial')
|
|
57
|
+
const LABEL_INITIAL_DELETE = marker(
|
|
58
|
+
'search.filters.spatialExtent.bboxInitialDelete'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
export interface SpatialExtentDropdownError {
|
|
62
|
+
key: string
|
|
63
|
+
params?: Record<string, string | number>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Component({
|
|
67
|
+
selector: 'gn-ui-spatial-extent-dropdown',
|
|
68
|
+
standalone: true,
|
|
69
|
+
imports: [
|
|
70
|
+
ButtonComponent,
|
|
71
|
+
NgIcon,
|
|
72
|
+
OverlayModule,
|
|
73
|
+
TranslatePipe,
|
|
74
|
+
DragAndDropFileInputComponent,
|
|
75
|
+
],
|
|
76
|
+
providers: [
|
|
77
|
+
provideIcons({
|
|
78
|
+
iconoirCheckCircle,
|
|
79
|
+
iconoirSquareDashed,
|
|
80
|
+
iconoirTrash,
|
|
81
|
+
iconoirUpload,
|
|
82
|
+
matClose,
|
|
83
|
+
matExpandLess,
|
|
84
|
+
matExpandMore,
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
templateUrl: './spatial-extent-dropdown.component.html',
|
|
88
|
+
styleUrls: ['./spatial-extent-dropdown.component.css'],
|
|
89
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
90
|
+
})
|
|
91
|
+
export class SpatialExtentDropdownComponent {
|
|
92
|
+
private cd = inject(ChangeDetectorRef)
|
|
93
|
+
private scrollStrategies = inject(ScrollStrategyOptions)
|
|
94
|
+
|
|
95
|
+
@Input() title: string
|
|
96
|
+
@Input() maxFileSizeMb: number | null = null
|
|
97
|
+
@Input() set initialBbox(value: BoundingBox | null) {
|
|
98
|
+
if (!this.bbox && value) {
|
|
99
|
+
this.bbox = value
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Output() bboxChange = new EventEmitter<BoundingBox | null>()
|
|
104
|
+
@Output() errorChange = new EventEmitter<SpatialExtentDropdownError>()
|
|
105
|
+
|
|
106
|
+
bbox: BoundingBox | null = null
|
|
107
|
+
fileName = ''
|
|
108
|
+
|
|
109
|
+
@ViewChild('overlayOrigin') overlayOrigin: CdkOverlayOrigin
|
|
110
|
+
@ViewChild(CdkConnectedOverlay) overlay: CdkConnectedOverlay
|
|
111
|
+
@ViewChild(DragAndDropFileInputComponent)
|
|
112
|
+
fileInput: DragAndDropFileInputComponent
|
|
113
|
+
|
|
114
|
+
overlayPositions: ConnectedPosition[] = [
|
|
115
|
+
{
|
|
116
|
+
originX: 'start',
|
|
117
|
+
originY: 'bottom',
|
|
118
|
+
overlayX: 'start',
|
|
119
|
+
overlayY: 'top',
|
|
120
|
+
offsetY: 8,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
originX: 'start',
|
|
124
|
+
originY: 'top',
|
|
125
|
+
overlayX: 'start',
|
|
126
|
+
overlayY: 'bottom',
|
|
127
|
+
offsetY: -8,
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
scrollStrategy = this.scrollStrategies.reposition()
|
|
131
|
+
overlayOpen = false
|
|
132
|
+
overlayMinWidth = 'none'
|
|
133
|
+
|
|
134
|
+
errorKey: string | null = null
|
|
135
|
+
|
|
136
|
+
get hasSelection() {
|
|
137
|
+
return !!this.bbox
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get selectionLabelKey() {
|
|
141
|
+
return this.fileName ? LABEL_FROM_FILE : LABEL_INITIAL
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get selectionDeleteLabelKey() {
|
|
145
|
+
return this.fileName ? LABEL_FROM_FILE_DELETE : LABEL_INITIAL_DELETE
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get selectionLabelParams() {
|
|
149
|
+
return { fileName: this.fileName }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
openOverlay() {
|
|
153
|
+
this.overlayMinWidth =
|
|
154
|
+
this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
|
|
155
|
+
.width + 'px'
|
|
156
|
+
this.overlayOpen = true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
closeOverlay() {
|
|
160
|
+
this.overlayOpen = false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
toggleOverlay() {
|
|
164
|
+
if (this.overlayOpen) {
|
|
165
|
+
this.closeOverlay()
|
|
166
|
+
} else {
|
|
167
|
+
this.openOverlay()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async handleFileSelected(file: File) {
|
|
172
|
+
this.errorKey = null
|
|
173
|
+
let content: string
|
|
174
|
+
try {
|
|
175
|
+
content = await readFileAsText(file)
|
|
176
|
+
const parsed = JSON.parse(content)
|
|
177
|
+
const geometry = getGeometryFromGeoJSON(parsed)
|
|
178
|
+
if (!geometry) {
|
|
179
|
+
this.setError(marker('search.filters.spatialExtent.error.noGeometry'))
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const bbox = getGeometryBoundingBox(geometry)
|
|
183
|
+
if (!isBoundingBoxWithinWorldExtent(bbox)) {
|
|
184
|
+
this.setError(marker('search.filters.spatialExtent.error.outOfBounds'))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
this.bbox = bbox
|
|
188
|
+
this.fileName = file.name
|
|
189
|
+
this.bboxChange.emit(bbox)
|
|
190
|
+
this.cd.markForCheck()
|
|
191
|
+
} catch {
|
|
192
|
+
this.setError(marker('search.filters.spatialExtent.error.invalidFormat'))
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
handleFileError(error: DragAndDropFileInputError) {
|
|
198
|
+
if (error === 'file-too-large') {
|
|
199
|
+
this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
|
|
200
|
+
maxSize: this.maxFileSizeMb,
|
|
201
|
+
})
|
|
202
|
+
} else {
|
|
203
|
+
this.setError(marker('search.filters.spatialExtent.error.invalidFormat'))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private setError(errorKey: string, params?: Record<string, string | number>) {
|
|
208
|
+
this.errorKey = errorKey
|
|
209
|
+
this.errorChange.emit({ key: errorKey, params })
|
|
210
|
+
this.cd.markForCheck()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
removeSelection(event: Event) {
|
|
214
|
+
this.bbox = null
|
|
215
|
+
this.fileName = ''
|
|
216
|
+
this.errorKey = null
|
|
217
|
+
this.fileInput?.clear()
|
|
218
|
+
this.bboxChange.emit(null)
|
|
219
|
+
propagateToDocumentOnly(event)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -29,11 +29,11 @@ import {
|
|
|
29
29
|
MapExtentChangeEvent,
|
|
30
30
|
MapExtentChangeEventType,
|
|
31
31
|
MapContext,
|
|
32
|
-
MapContextLayerXyz,
|
|
33
32
|
MapContextView,
|
|
34
33
|
MapEventsByType,
|
|
35
34
|
SourceLoadErrorEvent,
|
|
36
35
|
SourceLoadErrorType,
|
|
36
|
+
MapContextLayerMapLibreStyle,
|
|
37
37
|
} from '@geospatial-sdk/core'
|
|
38
38
|
import {
|
|
39
39
|
applyContextDiffToMap,
|
|
@@ -55,10 +55,11 @@ import {
|
|
|
55
55
|
import { matSwipeOutline } from '@ng-icons/material-icons/outline'
|
|
56
56
|
import { transformExtent } from 'ol/proj.js'
|
|
57
57
|
|
|
58
|
-
const DEFAULT_BASEMAP_LAYER:
|
|
59
|
-
type: '
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
export const DEFAULT_BASEMAP_LAYER: MapContextLayerMapLibreStyle = {
|
|
59
|
+
type: 'maplibre-style',
|
|
60
|
+
styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
|
|
61
|
+
clickable: false,
|
|
62
|
+
hoverable: false,
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const DEFAULT_VIEW: MapContextView = {
|
|
@@ -252,6 +252,9 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
252
252
|
'search_preset',
|
|
253
253
|
'advanced_filters',
|
|
254
254
|
'limit',
|
|
255
|
+
'spatial_extent_max_file_size',
|
|
256
|
+
'geocoding_provider',
|
|
257
|
+
'geocoding_provider_options',
|
|
255
258
|
],
|
|
256
259
|
warnings,
|
|
257
260
|
errors
|
|
@@ -264,6 +267,14 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
264
267
|
warnings,
|
|
265
268
|
errors
|
|
266
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
|
+
)
|
|
267
278
|
searchConfig =
|
|
268
279
|
parsedSearchSection === null
|
|
269
280
|
? null
|
|
@@ -280,7 +291,19 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
|
|
|
280
291
|
filters: param.filters,
|
|
281
292
|
})),
|
|
282
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
|
+
})),
|
|
283
301
|
LIMIT: parsedSearchSection.limit,
|
|
302
|
+
SPATIAL_EXTENT_MAX_FILE_SIZE:
|
|
303
|
+
parsedSearchSection.spatial_extent_max_file_size,
|
|
304
|
+
GEOCODING_PROVIDER: parsedSearchSection.geocoding_provider,
|
|
305
|
+
GEOCODING_PROVIDER_OPTIONS:
|
|
306
|
+
parsedSearchSection.geocoding_provider_options,
|
|
284
307
|
} as SearchConfig)
|
|
285
308
|
|
|
286
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,14 +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
|
|
77
|
+
SPATIAL_EXTENT_MAX_FILE_SIZE?: number
|
|
78
|
+
GEOCODING_PROVIDER?: GeocodingProviderName
|
|
79
|
+
GEOCODING_PROVIDER_OPTIONS?: Record<string, string | number>
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
export interface MetadataQualityConfig {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Directive,
|
|
3
|
+
ElementRef,
|
|
4
|
+
Injector,
|
|
5
|
+
Input,
|
|
6
|
+
afterNextRender,
|
|
7
|
+
booleanAttribute,
|
|
8
|
+
inject,
|
|
9
|
+
} from '@angular/core'
|
|
10
|
+
|
|
11
|
+
@Directive({
|
|
12
|
+
selector: '[gnUiAutofocus]',
|
|
13
|
+
standalone: true,
|
|
14
|
+
})
|
|
15
|
+
export class AutofocusDirective {
|
|
16
|
+
private el = inject<ElementRef<HTMLElement>>(ElementRef)
|
|
17
|
+
private injector = inject(Injector)
|
|
18
|
+
|
|
19
|
+
@Input({ alias: 'gnUiAutofocus', transform: booleanAttribute })
|
|
20
|
+
set autofocus(active: boolean) {
|
|
21
|
+
if (!active) return
|
|
22
|
+
afterNextRender(() => this.el.nativeElement.focus(), {
|
|
23
|
+
injector: this.injector,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -344,7 +344,11 @@ export function getLinkLabel(
|
|
|
344
344
|
return format ? `${label} (${format})` : label
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
export async function getLayers(
|
|
347
|
+
export async function getLayers(
|
|
348
|
+
url: string,
|
|
349
|
+
serviceProtocol: ServiceProtocol,
|
|
350
|
+
deep = false
|
|
351
|
+
) {
|
|
348
352
|
switch (serviceProtocol) {
|
|
349
353
|
case 'ogcFeatures': {
|
|
350
354
|
const layers = await new OgcApiEndpoint(url).allCollections
|
|
@@ -352,7 +356,10 @@ export async function getLayers(url: string, serviceProtocol: ServiceProtocol) {
|
|
|
352
356
|
}
|
|
353
357
|
case 'wfs': {
|
|
354
358
|
const endpointWfs = await new WfsEndpoint(url).isReady()
|
|
355
|
-
const featureTypes =
|
|
359
|
+
const featureTypes = endpointWfs.getFeatureTypes()
|
|
360
|
+
if (!deep) {
|
|
361
|
+
return featureTypes
|
|
362
|
+
}
|
|
356
363
|
const layers = (
|
|
357
364
|
await Promise.allSettled(
|
|
358
365
|
featureTypes.map((collection) => {
|
|
@@ -366,14 +373,16 @@ export async function getLayers(url: string, serviceProtocol: ServiceProtocol) {
|
|
|
366
373
|
}
|
|
367
374
|
case 'wms': {
|
|
368
375
|
const endpointWms = await new WmsEndpoint(url).isReady()
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
376
|
+
const layersSummary = endpointWms
|
|
377
|
+
.getLayers()
|
|
378
|
+
.flatMap(wmsLayerFlatten)
|
|
379
|
+
.filter((l) => l.name)
|
|
380
|
+
if (!deep) {
|
|
381
|
+
return layersSummary
|
|
382
|
+
}
|
|
383
|
+
const layers = layersSummary.map((collection) =>
|
|
384
|
+
endpointWms.getLayerByName(collection.name)
|
|
385
|
+
)
|
|
377
386
|
return layers
|
|
378
387
|
}
|
|
379
388
|
case 'wmts': {
|
|
@@ -35,10 +35,10 @@ export class DateService {
|
|
|
35
35
|
return { locale, dateObj }
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
async getDateFnsLocale(): Promise<Locale> {
|
|
39
39
|
const lang = this.translateService.getCurrentLang() || DEFAULT_LANGUAGE
|
|
40
40
|
const locales = await this.dateLocales
|
|
41
|
-
return locales[lang]
|
|
41
|
+
return locales[lang] ?? locales[DEFAULT_LANGUAGE]
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
formatDate(
|
|
@@ -61,7 +61,7 @@ export class DateService {
|
|
|
61
61
|
const dateObj = this.getDateObject(date)
|
|
62
62
|
|
|
63
63
|
const now = new Date()
|
|
64
|
-
const locale = await this.
|
|
64
|
+
const locale = await this.getDateFnsLocale()
|
|
65
65
|
|
|
66
66
|
return formatDistance(dateObj, now, {
|
|
67
67
|
addSuffix: true,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isFileExtensionValid(
|
|
2
|
+
fileName: string,
|
|
3
|
+
acceptedExtensions: string[]
|
|
4
|
+
): boolean {
|
|
5
|
+
return acceptedExtensions.some((ext) => fileName.toLowerCase().endsWith(ext))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function readFileAsText(file: File): Promise<string> {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const reader = new FileReader()
|
|
11
|
+
reader.onload = () => resolve(reader.result as string)
|
|
12
|
+
reader.onerror = () => reject(reader.error)
|
|
13
|
+
reader.readAsText(file)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -38,6 +38,30 @@ export function getGeometryFromGeoJSON(
|
|
|
38
38
|
// FIXME: this type should be more generic across the project
|
|
39
39
|
export type BoundingBox = [number, number, number, number]
|
|
40
40
|
|
|
41
|
+
export function isBoundingBox(value: unknown): value is BoundingBox {
|
|
42
|
+
return (
|
|
43
|
+
Array.isArray(value) &&
|
|
44
|
+
value.length === 4 &&
|
|
45
|
+
value.every((item) => typeof item === 'number')
|
|
46
|
+
)
|
|
47
|
+
}
|
|
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
|
+
|
|
41
65
|
export function getGeometryBoundingBox(geometry: Geometry): BoundingBox {
|
|
42
66
|
// use the bounding box if specified in the GeoJSON object
|
|
43
67
|
if (geometry.bbox) {
|
package/tailwind.base.css
CHANGED
|
@@ -224,6 +224,11 @@
|
|
|
224
224
|
disabled:cursor-not-allowed disabled:border-gray-100 disabled:placeholder-gray-300 disabled:text-gray-700 disabled:bg-white;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
.gn-ui-gray-outline-input {
|
|
228
|
+
@apply bg-white rounded-lg
|
|
229
|
+
border border-gray-400 hover:border-neutral-300 hover:bg-black/10;
|
|
230
|
+
}
|
|
231
|
+
|
|
227
232
|
/* GENERIC CARD */
|
|
228
233
|
.gn-ui-card {
|
|
229
234
|
@apply flex flex-row border border-gray-200 rounded-xl p-4 gap-2 w-full;
|
package/translations/de.json
CHANGED
|
@@ -65,9 +65,10 @@
|
|
|
65
65
|
"dataset.error.restrictedAccess": "",
|
|
66
66
|
"dataset.error.unknown": "Die Daten können nicht angezeigt werden: \"{ info }\"",
|
|
67
67
|
"dataset.error.unsupportedType": "Der folgende Inhaltstyp wird nicht unterstützt: \"{ info }\"",
|
|
68
|
-
"daterange.filter.
|
|
68
|
+
"daterange.filter.datePlaceholder": "tt.mm.jjjj",
|
|
69
|
+
"daterange.filter.from": "Ab dem",
|
|
69
70
|
"daterange.filter.period": "Zeitraum auswählen",
|
|
70
|
-
"daterange.filter.to": "Bis",
|
|
71
|
+
"daterange.filter.to": "Bis zum",
|
|
71
72
|
"domain.contact.role.author": "Autor",
|
|
72
73
|
"domain.contact.role.collaborator": "Mitarbeiter",
|
|
73
74
|
"domain.contact.role.contributor": "Beitragender",
|
|
@@ -424,7 +425,6 @@
|
|
|
424
425
|
"map.add.layer.wms": "Aus WMS",
|
|
425
426
|
"map.addFromFile.placeholder": "Klicke hier oder ziehe eine Datei herein",
|
|
426
427
|
"map.dropdown.placeholder": "Kein Vorschaulayer verfügbar",
|
|
427
|
-
"map.geocoding.placeholder": "Nach einem Ort suchen",
|
|
428
428
|
"map.help.addFromFile": "Klicke oder ziehe eine Datei herein um eine Karte hinzuzufügen (momentan wird nur das GeoJSON-Format unterstützt).",
|
|
429
429
|
"map.layer.add": "Hinzufügen",
|
|
430
430
|
"map.layers.available": "Verfügbare Layer",
|
|
@@ -689,9 +689,23 @@
|
|
|
689
689
|
"search.filters.recordKind.reuse": "Wiederverwendungen",
|
|
690
690
|
"search.filters.recordKind.service": "Dienste",
|
|
691
691
|
"search.filters.representationType": "Repräsentationstyp",
|
|
692
|
+
"search.filters.resourceCreationRevisionDate": "Ressourcendatum",
|
|
692
693
|
"search.filters.resourceType": "Ressourcentyp",
|
|
694
|
+
"search.filters.spatialExtent.bboxFromFile": "",
|
|
695
|
+
"search.filters.spatialExtent.bboxFromFileDelete": "",
|
|
696
|
+
"search.filters.spatialExtent.bboxInitial": "",
|
|
697
|
+
"search.filters.spatialExtent.bboxInitialDelete": "",
|
|
698
|
+
"search.filters.spatialExtent.error.fileTooLarge": "",
|
|
699
|
+
"search.filters.spatialExtent.error.invalidFormat": "",
|
|
700
|
+
"search.filters.spatialExtent.error.noGeometry": "",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "",
|
|
702
|
+
"search.filters.spatialExtent.error.title": "",
|
|
703
|
+
"search.filters.spatialExtent": "",
|
|
704
|
+
"search.filters.spatialExtent.helpText": "",
|
|
705
|
+
"search.filters.spatialExtent.import": "",
|
|
693
706
|
"search.filters.standard": "Standard",
|
|
694
707
|
"search.filters.summaryLabel.changeDate": "Geändert am: ",
|
|
708
|
+
"search.filters.summaryLabel.resourceCreationRevisionDate": "Ressourcendatum: ",
|
|
695
709
|
"search.filters.summaryLabel.user": "Geändert von: ",
|
|
696
710
|
"search.filters.title": "Ergebnisse filtern",
|
|
697
711
|
"search.filters.topic": "Themen",
|
package/translations/en.json
CHANGED
|
@@ -65,9 +65,10 @@
|
|
|
65
65
|
"dataset.error.restrictedAccess": "Access to this resource is restricted",
|
|
66
66
|
"dataset.error.unknown": "The data cannot be displayed: \"{ info }\"",
|
|
67
67
|
"dataset.error.unsupportedType": "The following content type is unsupported: \"{ info }\"",
|
|
68
|
+
"daterange.filter.datePlaceholder": "mm/dd/yyyy",
|
|
68
69
|
"daterange.filter.from": "From",
|
|
69
70
|
"daterange.filter.period": "Period",
|
|
70
|
-
"daterange.filter.to": "
|
|
71
|
+
"daterange.filter.to": "Until",
|
|
71
72
|
"domain.contact.role.author": "Author",
|
|
72
73
|
"domain.contact.role.collaborator": "Collaborator",
|
|
73
74
|
"domain.contact.role.contributor": "Contributor",
|
|
@@ -424,7 +425,6 @@
|
|
|
424
425
|
"map.add.layer.wms": "From WMS",
|
|
425
426
|
"map.addFromFile.placeholder": "Click or drop a file here",
|
|
426
427
|
"map.dropdown.placeholder": "No preview layer available",
|
|
427
|
-
"map.geocoding.placeholder": "Search for a place",
|
|
428
428
|
"map.help.addFromFile": "Click or drag and drop a file to add to the map (currently supports GeoJSON format only).",
|
|
429
429
|
"map.layer.add": "Add",
|
|
430
430
|
"map.layers.available": "Available Layers",
|
|
@@ -689,9 +689,23 @@
|
|
|
689
689
|
"search.filters.recordKind.reuse": "Reuses",
|
|
690
690
|
"search.filters.recordKind.service": "Services",
|
|
691
691
|
"search.filters.representationType": "Representation type",
|
|
692
|
+
"search.filters.resourceCreationRevisionDate": "Resource date",
|
|
692
693
|
"search.filters.resourceType": "Resource type",
|
|
694
|
+
"search.filters.spatialExtent.bboxFromFile": "Extent from {fileName}",
|
|
695
|
+
"search.filters.spatialExtent.bboxFromFileDelete": "Delete {fileName}",
|
|
696
|
+
"search.filters.spatialExtent.bboxInitial": "Pre-selected extent",
|
|
697
|
+
"search.filters.spatialExtent.bboxInitialDelete": "Delete selection",
|
|
698
|
+
"search.filters.spatialExtent.error.fileTooLarge": "The file exceeds the maximum allowed size ({maxSize} MB)",
|
|
699
|
+
"search.filters.spatialExtent.error.invalidFormat": "The file could not be read as a valid GeoJSON",
|
|
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",
|
|
702
|
+
"search.filters.spatialExtent.error.title": "GeoJSON import error",
|
|
703
|
+
"search.filters.spatialExtent": "Spatial extent",
|
|
704
|
+
"search.filters.spatialExtent.helpText": "The extent of the selected geometry will be used",
|
|
705
|
+
"search.filters.spatialExtent.import": "Import a GeoJSON",
|
|
693
706
|
"search.filters.standard": "Standard",
|
|
694
707
|
"search.filters.summaryLabel.changeDate": "Updated: ",
|
|
708
|
+
"search.filters.summaryLabel.resourceCreationRevisionDate": "Resource date: ",
|
|
695
709
|
"search.filters.summaryLabel.user": "Editor: ",
|
|
696
710
|
"search.filters.title": "Filter your results",
|
|
697
711
|
"search.filters.topic": "Topics",
|
package/translations/es.json
CHANGED
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"dataset.error.restrictedAccess": "",
|
|
66
66
|
"dataset.error.unknown": "",
|
|
67
67
|
"dataset.error.unsupportedType": "",
|
|
68
|
+
"daterange.filter.datePlaceholder": "dd/mm/aaaa",
|
|
68
69
|
"daterange.filter.from": "",
|
|
69
70
|
"daterange.filter.period": "",
|
|
70
71
|
"daterange.filter.to": "",
|
|
@@ -424,7 +425,6 @@
|
|
|
424
425
|
"map.add.layer.wms": "",
|
|
425
426
|
"map.addFromFile.placeholder": "",
|
|
426
427
|
"map.dropdown.placeholder": "",
|
|
427
|
-
"map.geocoding.placeholder": "",
|
|
428
428
|
"map.help.addFromFile": "",
|
|
429
429
|
"map.layer.add": "",
|
|
430
430
|
"map.layers.available": "",
|
|
@@ -689,9 +689,23 @@
|
|
|
689
689
|
"search.filters.recordKind.reuse": "",
|
|
690
690
|
"search.filters.recordKind.service": "",
|
|
691
691
|
"search.filters.representationType": "",
|
|
692
|
+
"search.filters.resourceCreationRevisionDate": "",
|
|
692
693
|
"search.filters.resourceType": "",
|
|
694
|
+
"search.filters.spatialExtent.bboxFromFile": "",
|
|
695
|
+
"search.filters.spatialExtent.bboxFromFileDelete": "",
|
|
696
|
+
"search.filters.spatialExtent.bboxInitial": "",
|
|
697
|
+
"search.filters.spatialExtent.bboxInitialDelete": "",
|
|
698
|
+
"search.filters.spatialExtent.error.fileTooLarge": "",
|
|
699
|
+
"search.filters.spatialExtent.error.invalidFormat": "",
|
|
700
|
+
"search.filters.spatialExtent.error.noGeometry": "",
|
|
701
|
+
"search.filters.spatialExtent.error.outOfBounds": "",
|
|
702
|
+
"search.filters.spatialExtent.error.title": "",
|
|
703
|
+
"search.filters.spatialExtent": "",
|
|
704
|
+
"search.filters.spatialExtent.helpText": "",
|
|
705
|
+
"search.filters.spatialExtent.import": "",
|
|
693
706
|
"search.filters.standard": "",
|
|
694
707
|
"search.filters.summaryLabel.changeDate": "",
|
|
708
|
+
"search.filters.summaryLabel.resourceCreationRevisionDate": "",
|
|
695
709
|
"search.filters.summaryLabel.user": "",
|
|
696
710
|
"search.filters.title": "",
|
|
697
711
|
"search.filters.topic": "",
|