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.
Files changed (63) hide show
  1. package/fesm2022/geonetwork-ui.mjs +836 -362
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +219 -78
  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 +107 -34
  7. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +20 -1
  8. package/src/libs/feature/map/src/index.ts +0 -1
  9. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  10. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  11. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  12. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  13. package/src/libs/feature/search/src/index.ts +2 -0
  14. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  15. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +58 -19
  16. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  17. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  18. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  19. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +18 -8
  20. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +9 -1
  21. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  22. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  23. package/src/libs/ui/inputs/src/index.ts +2 -0
  24. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  25. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  26. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  27. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  28. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  29. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  30. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  31. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  32. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  33. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  34. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  35. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  36. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  37. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  38. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  39. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  40. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  41. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  42. package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
  43. package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
  44. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  45. package/src/libs/util/shared/src/index.ts +1 -0
  46. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  47. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  48. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  49. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  50. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  51. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  52. package/tailwind.base.css +5 -0
  53. package/translations/de.json +17 -3
  54. package/translations/en.json +16 -2
  55. package/translations/es.json +15 -1
  56. package/translations/fr.json +17 -3
  57. package/translations/it.json +15 -1
  58. package/translations/nl.json +15 -1
  59. package/translations/pt.json +15 -1
  60. package/translations/sk.json +15 -1
  61. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  62. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  63. /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
@@ -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
+ }
@@ -3,6 +3,7 @@
3
3
  }
4
4
 
5
5
  :host {
6
+ display: block;
6
7
  position: relative;
7
8
  }
8
9
 
@@ -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: MapContextLayerXyz = {
59
- type: 'xyz',
60
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
61
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
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
- advanced_filters = ['publicationYear', 'documentStandard', 'inspireKeyword', 'topic', 'license']
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 {
@@ -1,6 +1,7 @@
1
1
  export * from './lib/services'
2
2
  export * from './lib/utils'
3
3
  export * from './lib/links'
4
+ export * from './lib/autofocus.directive'
4
5
  export * from './lib/image-fallback.directive'
5
6
  export * from './lib/gn-ui-version'
6
7
  export * from './lib/record'
@@ -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(url: string, serviceProtocol: ServiceProtocol) {
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 = await endpointWfs.getFeatureTypes()
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 layers = (
370
- await endpointWms
371
- .getLayers()
372
- .flatMap(wmsLayerFlatten)
373
- .filter((l) => l.name)
374
- ).map((collection) => {
375
- return endpointWms.getLayerByName(collection.name)
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
- private async getDateLocale(): Promise<Locale> {
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.getDateLocale()
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) {
@@ -1,5 +1,6 @@
1
1
  export * from './bytes-convert'
2
2
  export * from './event'
3
+ export * from './file'
3
4
  export * from './format-fields'
4
5
  export * from './fuzzy-filter'
5
6
  export * from './geojson'
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;
@@ -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.from": "Von",
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",
@@ -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": "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",
@@ -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": "",