geonetwork-ui 2.7.0-dev.8ac6cd7aa → 2.7.0-dev.b8a597a99

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 (106) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/status.mapper.mjs +4 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +5 -1
  3. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/constant.mjs +4 -6
  4. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.mjs +47 -3
  5. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +18 -4
  6. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +15 -1
  7. package/esm2022/libs/feature/dataviz/src/lib/service/data.service.mjs +2 -2
  8. package/esm2022/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.mjs +2 -2
  9. package/esm2022/libs/feature/map/src/lib/utils/map-utils.service.mjs +3 -5
  10. package/esm2022/libs/feature/record/src/lib/map-view/map-view.component.mjs +4 -2
  11. package/esm2022/libs/feature/router/src/lib/default/state/router.facade.mjs +3 -2
  12. package/esm2022/libs/feature/search/src/lib/state/reducer.mjs +5 -2
  13. package/esm2022/libs/ui/elements/src/lib/api-card/api-card.component.mjs +3 -3
  14. package/esm2022/libs/ui/elements/src/lib/downloads-list/downloads-list.component.mjs +5 -4
  15. package/esm2022/libs/ui/elements/src/lib/geo-data-badge/geo-data-badge.component.mjs +5 -4
  16. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +1 -4
  17. package/esm2022/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.mjs +5 -4
  18. package/esm2022/libs/util/app-config/src/lib/app-config.mjs +3 -1
  19. package/esm2022/libs/util/app-config/src/lib/model.mjs +1 -1
  20. package/esm2022/libs/util/shared/src/lib/links/link-utils.mjs +4 -2
  21. package/esm2022/libs/util/shared/src/lib/record/quality-score.util.mjs +4 -5
  22. package/esm2022/libs/util/shared/src/lib/utils/geojson.mjs +58 -1
  23. package/esm2022/libs/util/shared/src/lib/utils/index.mjs +2 -1
  24. package/esm2022/libs/util/shared/src/lib/utils/mobile-screen.mjs +9 -0
  25. package/esm2022/translations/de.json +12 -0
  26. package/esm2022/translations/en.json +12 -0
  27. package/esm2022/translations/es.json +12 -0
  28. package/esm2022/translations/fr.json +13 -1
  29. package/esm2022/translations/it.json +12 -0
  30. package/esm2022/translations/nl.json +12 -0
  31. package/esm2022/translations/pt.json +12 -0
  32. package/esm2022/translations/sk.json +12 -0
  33. package/fesm2022/geonetwork-ui.mjs +2537 -2294
  34. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  35. package/libs/api/metadata-converter/src/lib/iso19139/utils/status.mapper.d.ts.map +1 -1
  36. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  37. package/libs/api/repository/src/lib/gn4/elasticsearch/constant.d.ts.map +1 -1
  38. package/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.d.ts +1 -1
  39. package/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.d.ts.map +1 -1
  40. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts +2 -0
  41. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  42. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  43. package/libs/feature/dataviz/src/lib/service/data.service.d.ts.map +1 -1
  44. package/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.d.ts.map +1 -1
  45. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts +2 -2
  46. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts.map +1 -1
  47. package/libs/feature/record/src/lib/map-view/map-view.component.d.ts.map +1 -1
  48. package/libs/feature/router/src/lib/default/state/router.facade.d.ts.map +1 -1
  49. package/libs/feature/search/src/lib/state/reducer.d.ts.map +1 -1
  50. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -0
  51. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts.map +1 -1
  52. package/libs/ui/elements/src/lib/geo-data-badge/geo-data-badge.component.d.ts +1 -0
  53. package/libs/ui/elements/src/lib/geo-data-badge/geo-data-badge.component.d.ts.map +1 -1
  54. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts +0 -1
  55. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  56. package/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.d.ts +1 -0
  57. package/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.d.ts.map +1 -1
  58. package/libs/util/app-config/src/lib/app-config.d.ts.map +1 -1
  59. package/libs/util/app-config/src/lib/model.d.ts +1 -0
  60. package/libs/util/app-config/src/lib/model.d.ts.map +1 -1
  61. package/libs/util/shared/src/lib/links/link-utils.d.ts +1 -1
  62. package/libs/util/shared/src/lib/links/link-utils.d.ts.map +1 -1
  63. package/libs/util/shared/src/lib/record/quality-score.util.d.ts.map +1 -1
  64. package/libs/util/shared/src/lib/utils/geojson.d.ts +7 -2
  65. package/libs/util/shared/src/lib/utils/geojson.d.ts.map +1 -1
  66. package/libs/util/shared/src/lib/utils/index.d.ts +1 -0
  67. package/libs/util/shared/src/lib/utils/index.d.ts.map +1 -1
  68. package/libs/util/shared/src/lib/utils/mobile-screen.d.ts +2 -0
  69. package/libs/util/shared/src/lib/utils/mobile-screen.d.ts.map +1 -0
  70. package/package.json +11 -10
  71. package/src/libs/api/metadata-converter/src/lib/dcat-ap/utils/status.mapper.ts +3 -0
  72. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/status.mapper.ts +3 -0
  73. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +4 -0
  74. package/src/libs/api/repository/src/lib/gn4/elasticsearch/constant.ts +3 -5
  75. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +50 -3
  76. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +19 -6
  77. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +15 -0
  78. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +3 -1
  79. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +1 -4
  80. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +8 -8
  81. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +3 -1
  82. package/src/libs/feature/router/src/lib/default/state/router.facade.ts +2 -1
  83. package/src/libs/feature/search/src/lib/state/reducer.ts +4 -1
  84. package/src/libs/ui/elements/src/lib/api-card/api-card.component.html +4 -1
  85. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.html +12 -11
  86. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts +7 -1
  87. package/src/libs/ui/elements/src/lib/geo-data-badge/geo-data-badge.component.html +4 -1
  88. package/src/libs/ui/elements/src/lib/geo-data-badge/geo-data-badge.component.ts +7 -1
  89. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +0 -4
  90. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.html +7 -1
  91. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +3 -1
  92. package/src/libs/util/app-config/src/lib/app-config.ts +2 -0
  93. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  94. package/src/libs/util/shared/src/lib/links/link-utils.ts +2 -1
  95. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +3 -7
  96. package/src/libs/util/shared/src/lib/utils/geojson.ts +72 -2
  97. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  98. package/src/libs/util/shared/src/lib/utils/mobile-screen.ts +14 -0
  99. package/translations/de.json +12 -0
  100. package/translations/en.json +12 -0
  101. package/translations/es.json +12 -0
  102. package/translations/fr.json +13 -1
  103. package/translations/it.json +12 -0
  104. package/translations/nl.json +12 -0
  105. package/translations/pt.json +12 -0
  106. package/translations/sk.json +12 -0
@@ -46,7 +46,6 @@ import {
46
46
  } from 'rxjs'
47
47
  import { TranslateService } from '@ngx-translate/core'
48
48
  import { getLang3FromLang2 } from '../../../../../../../libs/util/i18n/src'
49
- import { DatavizConfigModel } from '../../../../../../../libs/common/domain/src/lib/model/dataviz/dataviz-configuration.model'
50
49
 
51
50
  const minApiVersion = '4.2.2'
52
51
 
@@ -56,6 +55,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
56
55
  private readonly me$: Observable<UserModel>
57
56
  private readonly users$: Observable<UserModel[]>
58
57
  private readonly isUserAnonymous$: Observable<boolean>
58
+ private readonly gnParseVersion = '4.2.5'
59
59
 
60
60
  private keyTranslations$ = this.toolsApiService
61
61
  .getTranslationsPackage1('gnui')
@@ -390,22 +390,35 @@ export class Gn4PlatformService implements PlatformServiceInterface {
390
390
  })
391
391
  )
392
392
  }
393
-
394
393
  getFileContent(url: URL | string): Observable<any> {
395
- return this.httpClient.get(url.toString(), { responseType: 'text' }).pipe(
396
- map((text) => {
394
+ return combineLatest([
395
+ this.httpClient.get(url.toString(), { responseType: 'text' }),
396
+ this.getApiVersion(),
397
+ ]).pipe(
398
+ map(([text, version]) => {
397
399
  const parsed = JSON.parse(text)
398
400
 
399
- if (typeof parsed === 'object') {
401
+ if (version > this.gnParseVersion) {
400
402
  return parsed
401
403
  }
402
404
 
403
- const decoded = atob(parsed)
405
+ const decoded = this.decodeBase64(parsed)
404
406
  return JSON.parse(decoded)
405
407
  })
406
408
  )
407
409
  }
408
410
 
411
+ decodeBase64(base64) {
412
+ const text = atob(base64)
413
+ const length = text.length
414
+ const bytes = new Uint8Array(length)
415
+ for (let i = 0; i < length; i++) {
416
+ bytes[i] = text.charCodeAt(i)
417
+ }
418
+ const decoder = new TextDecoder()
419
+ return decoder.decode(bytes)
420
+ }
421
+
409
422
  attachFileToRecord(
410
423
  recordUuid: string,
411
424
  file: File,
@@ -69,6 +69,8 @@ marker('domain.record.status.ongoing')
69
69
  marker('domain.record.status.under_development')
70
70
  marker('domain.record.status.deprecated')
71
71
  marker('domain.record.status.removed')
72
+ marker('domain.record.status.planned')
73
+ marker('domain.record.status.required')
72
74
 
73
75
  export const RecordStatusValues = [
74
76
  'completed',
@@ -76,6 +78,8 @@ export const RecordStatusValues = [
76
78
  'under_development',
77
79
  'deprecated',
78
80
  'removed',
81
+ 'planned',
82
+ 'required',
79
83
  ]
80
84
  export type RecordStatus = (typeof RecordStatusValues)[number]
81
85
 
@@ -156,6 +160,17 @@ export type ServiceProtocol =
156
160
  | 'maplibre-style'
157
161
  | 'other'
158
162
 
163
+ marker('record.metadata.api.accessServiceProtocol.wms')
164
+ marker('record.metadata.api.accessServiceProtocol.wfs')
165
+ marker('record.metadata.api.accessServiceProtocol.wps')
166
+ marker('record.metadata.api.accessServiceProtocol.wmts')
167
+ marker('record.metadata.api.accessServiceProtocol.esriRest')
168
+ marker('record.metadata.api.accessServiceProtocol.ogcFeatures')
169
+ marker('record.metadata.api.accessServiceProtocol.GPFDL')
170
+ marker('record.metadata.api.accessServiceProtocol.tms')
171
+ marker('record.metadata.api.accessServiceProtocol.maplibre-style')
172
+ marker('record.metadata.api.accessServiceProtocol.other')
173
+
159
174
  export type OnlineResourceType = 'service' | 'download' | 'link' | 'endpoint'
160
175
 
161
176
  export interface DatasetServiceDistribution {
@@ -237,7 +237,9 @@ export class DataService {
237
237
  tmsLink: DatasetServiceDistribution,
238
238
  keepOriginalLink = false
239
239
  ): Promise<DatasetServiceDistribution[]> {
240
- const endpoint = new TmsEndpoint(tmsLink.url.toString())
240
+ const endpoint = new TmsEndpoint(
241
+ tmsLink.url.toString().replace(/\/?$/, `/${tmsLink.name}`)
242
+ )
241
243
  const tileMaps = await endpoint.allTileMaps.catch(() => {
242
244
  throw new Error(`ogc.unreachable.unknown`)
243
245
  })
@@ -55,10 +55,7 @@ export class MetadataQualityPanelComponent implements OnChanges {
55
55
  const fieldsByPage = this.editorConfig.pages.map((page) =>
56
56
  page.sections.flatMap((section) =>
57
57
  section.fields
58
- .filter(
59
- (field) =>
60
- this.propsToValidate.includes(field.model) && !field.hidden
61
- )
58
+ .filter((field) => this.propsToValidate.includes(field.model))
62
59
  .map((field) => field.model as ValidatorMapperKeys)
63
60
  )
64
61
  )
@@ -1,25 +1,25 @@
1
1
  import { Injectable } from '@angular/core'
2
- import { extend, Extent } from 'ol/extent'
3
- import GeoJSON from 'ol/format/GeoJSON'
2
+ import { extend } from 'ol/extent'
4
3
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
5
-
6
- const GEOJSON = new GeoJSON()
4
+ import { BoundingBox, getGeometryBoundingBox } from '../../../../../../libs/util/shared/src'
7
5
 
8
6
  @Injectable({
9
7
  providedIn: 'root',
10
8
  })
11
9
  export class MapUtilsService {
12
- getRecordExtent(record: Partial<CatalogRecord>): Extent {
10
+ getRecordExtent(record: Partial<CatalogRecord>): BoundingBox {
13
11
  if (!('spatialExtents' in record) || record.spatialExtents.length === 0) {
14
12
  return null
15
13
  }
16
14
  // extend all the spatial extents into an including bbox
17
15
  return record.spatialExtents.reduce(
18
16
  (prev, curr) => {
19
- if ('bbox' in curr) return extend(prev, curr.bbox)
17
+ if ('bbox' in curr) return extend(prev, curr.bbox) as BoundingBox
20
18
  else if ('geometry' in curr) {
21
- const geom = GEOJSON.readGeometry(curr.geometry)
22
- return extend(prev, geom.getExtent())
19
+ return extend(
20
+ prev,
21
+ getGeometryBoundingBox(curr.geometry)
22
+ ) as BoundingBox
23
23
  }
24
24
  return prev
25
25
  },
@@ -389,7 +389,9 @@ export class MapViewComponent implements AfterViewInit {
389
389
  ) {
390
390
  // FIXME: here we're assuming that the TMS serves vector tiles only; should be checked with ogc-client first
391
391
  return of({
392
- url: link.url.toString().replace(/\/?$/, '/{z}/{x}/{y}.pbf'),
392
+ url: link.url
393
+ .toString()
394
+ .replace(/\/?$/, `/${link.name}/{z}/{x}/{y}.pbf`),
393
395
  type: 'xyz',
394
396
  tileFormat: 'application/vnd.mapbox-vector-tile',
395
397
  name: link.name,
@@ -63,7 +63,8 @@ export class RouterFacade {
63
63
  }
64
64
 
65
65
  goToOrganization(organizationName: string) {
66
- const path = `${this.routerService.getOrganizationPageRoute()}/${organizationName}`
66
+ const safeOrgName = organizationName.replace('/', '')
67
+ const path = `${this.routerService.getOrganizationPageRoute()}/${safeOrgName}`
67
68
  this.go({
68
69
  path,
69
70
  queryParamsHandling: '',
@@ -8,6 +8,7 @@ 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'
11
12
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
12
13
 
13
14
  export const SEARCH_FEATURE_KEY = 'searchState'
@@ -55,7 +56,9 @@ export const initSearch = (): SearchStateSearch => {
55
56
  },
56
57
  params: {
57
58
  filters: {},
58
- pageSize: DEFAULT_PAGE_SIZE,
59
+ pageSize: getOptionalSearchConfig()?.LIMIT
60
+ ? getOptionalSearchConfig().LIMIT
61
+ : DEFAULT_PAGE_SIZE,
59
62
  currentPage: 0,
60
63
  favoritesOnly: false,
61
64
  useSpatialFilter: true,
@@ -67,7 +67,10 @@
67
67
  [ngClass]="{
68
68
  '!bg-primary': currentlyActive,
69
69
  }"
70
- >{{ link.accessServiceProtocol }}</span
70
+ translate
71
+ >record.metadata.api.accessServiceProtocol.{{
72
+ link.accessServiceProtocol
73
+ }}</span
71
74
  >
72
75
  <span
73
76
  *ngIf="link.accessServiceProtocol === 'GPFDL'"
@@ -13,8 +13,7 @@
13
13
  <span class="px-3">({{ linksCount }})</span>
14
14
  </div>
15
15
  <gn-ui-previous-next-buttons
16
- class="md:block hidden"
17
- *ngIf="_list?.pagesCount > 1"
16
+ *ngIf="(isMobile$ | async) === false && _list?.pagesCount > 1"
18
17
  [listComponent]="_list"
19
18
  ></gn-ui-previous-next-buttons>
20
19
  </div>
@@ -40,7 +39,7 @@
40
39
 
41
40
  <ng-container>
42
41
  <gn-ui-block-list
43
- class="md:block hidden"
42
+ *ngIf="(isMobile$ | async) === false"
44
43
  #blockList
45
44
  (listChanges)="updateList($event)"
46
45
  containerClass="gap-4 pt-5 pb-7"
@@ -57,12 +56,14 @@
57
56
  </gn-ui-block-list>
58
57
  </ng-container>
59
58
 
60
- <div class="mb-5 md:hidden block" *ngFor="let link of filteredLinks">
61
- <gn-ui-download-item
62
- size="M"
63
- [link]="link"
64
- [color]="getLinkColor(link)"
65
- [format]="getLinkFormat(link)"
66
- [isFromApi]="isFromApi(link)"
67
- ></gn-ui-download-item>
59
+ <div class="mb-5" *ngFor="let link of filteredLinks">
60
+ <ng-container *ngIf="(isMobile$ | async) === true">
61
+ <gn-ui-download-item
62
+ size="M"
63
+ [link]="link"
64
+ [color]="getLinkColor(link)"
65
+ [format]="getLinkFormat(link)"
66
+ [isFromApi]="isFromApi(link)"
67
+ ></gn-ui-download-item>
68
+ </ng-container>
68
69
  </div>
@@ -6,7 +6,11 @@ import {
6
6
  } from '@angular/core'
7
7
  import { TranslateDirective, TranslateService } from '@ngx-translate/core'
8
8
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
9
- import { getBadgeColor, getFileFormat } from '../../../../../../libs/util/shared/src'
9
+ import {
10
+ getBadgeColor,
11
+ getFileFormat,
12
+ getIsMobile,
13
+ } from '../../../../../../libs/util/shared/src'
10
14
  import { DatasetDownloadDistribution } from '../../../../../../libs/common/domain/src/lib/model/record'
11
15
  import { CommonModule } from '@angular/common'
12
16
  import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
@@ -50,6 +54,8 @@ export class DownloadsListComponent {
50
54
  return this.filteredLinks?.length || 0
51
55
  }
52
56
 
57
+ isMobile$ = getIsMobile()
58
+
53
59
  activeFilterFormats: FilterFormat[] = ['all']
54
60
 
55
61
  updateList($event: BlockListComponent) {
@@ -7,7 +7,10 @@
7
7
  class="shrink-0 text-[0.75em]"
8
8
  name="matLocationSearchingOutline"
9
9
  ></ng-icon>
10
- <span class="ml-1 hidden sm:inline-block shrink-0" *ngIf="showLabel" translate
10
+ <span
11
+ class="ml-1 inline-block shrink-0"
12
+ *ngIf="(isMobile$ | async) === false && showLabel"
13
+ translate
11
14
  >record.metadata.isGeographical</span
12
15
  >
13
16
  </div>
@@ -4,7 +4,11 @@ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/mode
4
4
  import { CommonModule } from '@angular/common'
5
5
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
6
6
  import { NgIcon, provideIcons } from '@ng-icons/core'
7
- import { LinkClassifierService, LinkUsage } from '../../../../../../libs/util/shared/src'
7
+ import {
8
+ getIsMobile,
9
+ LinkClassifierService,
10
+ LinkUsage,
11
+ } from '../../../../../../libs/util/shared/src'
8
12
 
9
13
  @Component({
10
14
  selector: 'gn-ui-geo-data-badge',
@@ -24,6 +28,8 @@ export class GeoDataBadgeComponent {
24
28
  @Input() styling = 'default'
25
29
  @Input() record: CatalogRecord
26
30
 
31
+ isMobile$ = getIsMobile()
32
+
27
33
  isGeodata() {
28
34
  const links =
29
35
  'onlineResources' in this.record ? this.record.onlineResources : []
@@ -74,10 +74,6 @@ export class MetadataQualityComponent implements OnChanges {
74
74
  )
75
75
  }
76
76
 
77
- hasGetCapabilities(url: string): boolean {
78
- return url.toLowerCase().includes('capabilities')
79
- }
80
-
81
77
  initialize() {
82
78
  if (!this.propsToValidate) {
83
79
  this.propsToValidate = getAllKeysValidator()
@@ -1,5 +1,8 @@
1
1
  <div
2
2
  class="flex items-center justify-between p-3 mt-8 bg-white rounded-lg border-b solid border-gray-300"
3
+ *ngIf="
4
+ (isMobile$ | async) === false || featureCatalog?.featureTypes?.length > 1
5
+ "
3
6
  >
4
7
  <div
5
8
  class="relative shrink-0"
@@ -17,7 +20,10 @@
17
20
  name="iconoirSearch"
18
21
  ></ng-icon>
19
22
  </div>
20
- <div class="text-sm px-1 ml-auto hidden sm:inline">
23
+ <div
24
+ *ngIf="(isMobile$ | async) === false"
25
+ class="text-sm px-1 ml-auto inline"
26
+ >
21
27
  <ng-container *ngIf="featureCatalog?.featureTypes?.length > 1">
22
28
  <span
23
29
  class="text-sm font-medium text-gray-900"
@@ -6,7 +6,7 @@ import { DatasetFeatureCatalog } from '../../../../../../libs/common/domain/src/
6
6
  import { FormsModule } from '@angular/forms'
7
7
  import { of } from 'rxjs'
8
8
  import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'
9
- import { createFuzzyFilter } from '../../../../../../libs/util/shared/src'
9
+ import { createFuzzyFilter, getIsMobile } from '../../../../../../libs/util/shared/src'
10
10
  import { iconoirSearch } from '@ng-icons/iconoir'
11
11
 
12
12
  @Component({
@@ -39,6 +39,8 @@ export class SearchFeatureCatalogComponent {
39
39
  return this._featureCatalog
40
40
  }
41
41
 
42
+ isMobile$ = getIsMobile()
43
+
42
44
  @Output() filteredFeatureCatalogChange =
43
45
  new EventEmitter<DatasetFeatureCatalog>()
44
46
  filteredFeatureCatalog: DatasetFeatureCatalog
@@ -231,6 +231,7 @@ export function loadAppConfig() {
231
231
  'filter_geometry_url',
232
232
  'search_preset',
233
233
  'advanced_filters',
234
+ 'limit',
234
235
  ],
235
236
  warnings,
236
237
  errors
@@ -257,6 +258,7 @@ export function loadAppConfig() {
257
258
  filters: param.filters,
258
259
  })),
259
260
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
261
+ LIMIT: parsedSearchSection.limit,
260
262
  } as SearchConfig)
261
263
 
262
264
  const parsedMetadataQualitySection = parseConfigSection(
@@ -60,6 +60,7 @@ export interface SearchConfig {
60
60
  FILTER_GEOMETRY_DATA?: string
61
61
  SEARCH_PRESET?: SearchPreset[]
62
62
  ADVANCED_FILTERS?: []
63
+ LIMIT?: number
63
64
  }
64
65
 
65
66
  export interface MetadataQualityConfig {
@@ -23,8 +23,8 @@ export const FORMATS = {
23
23
  excel: {
24
24
  extensions: [
25
25
  'excel',
26
- 'xls',
27
26
  'xlsx',
27
+ 'xls',
28
28
  'ms-excel',
29
29
  'openxmlformats-officedocument',
30
30
  ],
@@ -190,6 +190,7 @@ export function isFormatInQueryParam(
190
190
  alias: string
191
191
  ): boolean {
192
192
  const queryParams = link.url.searchParams
193
+ if (!queryParams) return false
193
194
  for (const [key, value] of queryParams.entries()) {
194
195
  if (key === 'format' || key === 'f') {
195
196
  return value === alias
@@ -16,10 +16,6 @@ const ValidatorMapper: TValidatorMapper = {
16
16
  updateFrequency: (record) => !!record?.updateFrequency,
17
17
  topics: (record) => (record?.topics?.length ?? 0) > 0,
18
18
  organisation: (record) => !!record?.contacts?.[0]?.organization?.name,
19
- capabilities: (record) =>
20
- record?.onlineResources?.some((resource) =>
21
- resource?.url?.href.toLowerCase().includes('capabilities')
22
- ),
23
19
  source: (record) => !!record?.extras?.sourcesIdentifiers,
24
20
  } as const
25
21
 
@@ -40,12 +36,12 @@ function getMappersFromKind(kind: RecordKind) {
40
36
  ]
41
37
 
42
38
  switch (kind) {
43
- case 'service':
44
- kindKeys = ['capabilities']
45
- break
46
39
  case 'reuse':
47
40
  kindKeys = ['topics', 'organisation', 'source']
48
41
  break
42
+ case 'service':
43
+ kindKeys = []
44
+ break
49
45
  case 'dataset':
50
46
  default:
51
47
  kindKeys = ['updateFrequency', 'topics', 'organisation']
@@ -1,8 +1,11 @@
1
- import { Feature, FeatureCollection, Geometry } from 'geojson'
1
+ import type { Feature, FeatureCollection, Geometry, Position } from 'geojson'
2
2
 
3
+ /**
4
+ * @returns The geometry if available, otherwise null.
5
+ */
3
6
  export function getGeometryFromGeoJSON(
4
7
  data: FeatureCollection | Feature | Geometry
5
- ): Geometry {
8
+ ): Geometry | null {
6
9
  if (data.type === 'FeatureCollection') {
7
10
  return data?.features?.[0]?.geometry || null
8
11
  }
@@ -24,3 +27,70 @@ export function getGeometryFromGeoJSON(
24
27
  }
25
28
  return null
26
29
  }
30
+
31
+ // FIXME: this type should be more generic across the project
32
+ export type BoundingBox = [number, number, number, number]
33
+
34
+ export function getGeometryBoundingBox(geometry: Geometry): BoundingBox {
35
+ // use the bounding box if specified in the GeoJSON object
36
+ if (geometry.bbox) {
37
+ return geometry.bbox.length > 4
38
+ ? [geometry.bbox[0], geometry.bbox[1], geometry.bbox[3], geometry.bbox[4]]
39
+ : (geometry.bbox as BoundingBox)
40
+ }
41
+
42
+ const coordinatesReducer = (prev: BoundingBox, coords: Position) =>
43
+ [
44
+ Math.min(prev[0], coords[0]),
45
+ Math.min(prev[1], coords[1]),
46
+ Math.max(prev[2], coords[0]),
47
+ Math.max(prev[3], coords[1]),
48
+ ] as BoundingBox
49
+ const coordinatesArrayReducer = (
50
+ prev: BoundingBox,
51
+ coordsArray: Position[]
52
+ ) => {
53
+ const bbox = coordsArray.reduce(coordinatesReducer, emptyExtent)
54
+ return [
55
+ Math.min(prev[0], bbox[0]),
56
+ Math.min(prev[1], bbox[1]),
57
+ Math.max(prev[2], bbox[2]),
58
+ Math.max(prev[3], bbox[3]),
59
+ ] as BoundingBox
60
+ }
61
+ const emptyExtent = [Infinity, Infinity, -Infinity, -Infinity] as BoundingBox
62
+
63
+ switch (geometry.type) {
64
+ case 'MultiPolygon':
65
+ return geometry.coordinates.reduce((prev, polygonCoords) => {
66
+ const bbox = polygonCoords.reduce(coordinatesArrayReducer, emptyExtent)
67
+ return [
68
+ Math.min(prev[0], bbox[0]),
69
+ Math.min(prev[1], bbox[1]),
70
+ Math.max(prev[2], bbox[2]),
71
+ Math.max(prev[3], bbox[3]),
72
+ ] as BoundingBox
73
+ }, emptyExtent)
74
+ case 'GeometryCollection':
75
+ return geometry.geometries.reduce((prev, geom) => {
76
+ const bbox = getGeometryBoundingBox(geom)
77
+ return [
78
+ Math.min(prev[0], bbox[0]),
79
+ Math.min(prev[1], bbox[1]),
80
+ Math.max(prev[2], bbox[2]),
81
+ Math.max(prev[3], bbox[3]),
82
+ ] as BoundingBox
83
+ }, emptyExtent)
84
+ case 'Polygon':
85
+ case 'MultiLineString':
86
+ return geometry.coordinates.reduce(coordinatesArrayReducer, emptyExtent)
87
+ case 'LineString':
88
+ case 'MultiPoint':
89
+ return geometry.coordinates.reduce<BoundingBox>(
90
+ coordinatesReducer,
91
+ emptyExtent
92
+ )
93
+ case 'Point':
94
+ return coordinatesReducer(emptyExtent, geometry.coordinates)
95
+ }
96
+ }
@@ -4,6 +4,7 @@ export * from './format-fields'
4
4
  export * from './fuzzy-filter'
5
5
  export * from './geojson'
6
6
  export * from './image-resize'
7
+ export * from './mobile-screen'
7
8
  export * from './no-duplicate-file-name'
8
9
  export * from './parse'
9
10
  export * from './remove-whitespace'
@@ -0,0 +1,14 @@
1
+ import { fromEvent, startWith, map, shareReplay } from 'rxjs'
2
+
3
+ /**
4
+ * This returns true when the screen size is under 768px, which is the mobile threshold.
5
+ */
6
+ const MOBILE_MAX_WIDTH = 768
7
+
8
+ export function getIsMobile() {
9
+ return fromEvent(window, 'resize').pipe(
10
+ startWith(window.innerWidth),
11
+ map(() => window.innerWidth < MOBILE_MAX_WIDTH),
12
+ shareReplay({ bufferSize: 1, refCount: true })
13
+ )
14
+ }
@@ -86,7 +86,9 @@
86
86
  "domain.record.status.completed": "Abgeschlossen",
87
87
  "domain.record.status.deprecated": "Veraltet",
88
88
  "domain.record.status.ongoing": "Kontinuierliche Aktualisierung",
89
+ "domain.record.status.planned": "",
89
90
  "domain.record.status.removed": "Entfernt",
91
+ "domain.record.status.required": "",
90
92
  "domain.record.status.under_development": "In Erstellung",
91
93
  "domain.record.updateFrequency.annually": "Daten werden jedes Jahr aktualisiert",
92
94
  "domain.record.updateFrequency.asNeeded": "Daten werden nach Bedarf aktualisiert",
@@ -388,6 +390,16 @@
388
390
  "record.kind.service": "",
389
391
  "record.metadata.about": "Beschreibung",
390
392
  "record.metadata.api": "API",
393
+ "record.metadata.api.accessServiceProtocol.GPFDL": "",
394
+ "record.metadata.api.accessServiceProtocol.esriRest": "",
395
+ "record.metadata.api.accessServiceProtocol.maplibre-style": "",
396
+ "record.metadata.api.accessServiceProtocol.ogcFeatures": "",
397
+ "record.metadata.api.accessServiceProtocol.other": "",
398
+ "record.metadata.api.accessServiceProtocol.tms": "",
399
+ "record.metadata.api.accessServiceProtocol.wfs": "",
400
+ "record.metadata.api.accessServiceProtocol.wms": "",
401
+ "record.metadata.api.accessServiceProtocol.wmts": "",
402
+ "record.metadata.api.accessServiceProtocol.wps": "",
391
403
  "record.metadata.api.form.closeButton": "Schließen",
392
404
  "record.metadata.api.form.closeForm": "Formular schließen",
393
405
  "record.metadata.api.form.create": "Ihre Anfrage erstellen",
@@ -86,7 +86,9 @@
86
86
  "domain.record.status.completed": "Completed",
87
87
  "domain.record.status.deprecated": "Deprecated",
88
88
  "domain.record.status.ongoing": "Ongoing",
89
+ "domain.record.status.planned": "Planned",
89
90
  "domain.record.status.removed": "Removed",
91
+ "domain.record.status.required": "Required",
90
92
  "domain.record.status.under_development": "Under development",
91
93
  "domain.record.updateFrequency.annually": "Data is updated every year",
92
94
  "domain.record.updateFrequency.asNeeded": "Data is updated as deemed necessary",
@@ -388,6 +390,16 @@
388
390
  "record.kind.service": "Service",
389
391
  "record.metadata.about": "Description",
390
392
  "record.metadata.api": "API",
393
+ "record.metadata.api.accessServiceProtocol.GPFDL": "GPFDL",
394
+ "record.metadata.api.accessServiceProtocol.esriRest": "esriRest",
395
+ "record.metadata.api.accessServiceProtocol.maplibre-style": "maplibre-style",
396
+ "record.metadata.api.accessServiceProtocol.ogcFeatures": "ogcFeatures",
397
+ "record.metadata.api.accessServiceProtocol.other": "other",
398
+ "record.metadata.api.accessServiceProtocol.tms": "tms",
399
+ "record.metadata.api.accessServiceProtocol.wfs": "wfs",
400
+ "record.metadata.api.accessServiceProtocol.wms": "wms",
401
+ "record.metadata.api.accessServiceProtocol.wmts": "wmts",
402
+ "record.metadata.api.accessServiceProtocol.wps": "wps",
391
403
  "record.metadata.api.form.closeButton": "Close",
392
404
  "record.metadata.api.form.closeForm": "Close the form",
393
405
  "record.metadata.api.form.create": "Create your request",
@@ -86,7 +86,9 @@
86
86
  "domain.record.status.completed": "",
87
87
  "domain.record.status.deprecated": "",
88
88
  "domain.record.status.ongoing": "",
89
+ "domain.record.status.planned": "",
89
90
  "domain.record.status.removed": "",
91
+ "domain.record.status.required": "",
90
92
  "domain.record.status.under_development": "",
91
93
  "domain.record.updateFrequency.annually": "Los datos se actualizan cada año",
92
94
  "domain.record.updateFrequency.asNeeded": "Los datos se actualizan según sea necesario",
@@ -388,6 +390,16 @@
388
390
  "record.kind.service": "",
389
391
  "record.metadata.about": "",
390
392
  "record.metadata.api": "",
393
+ "record.metadata.api.accessServiceProtocol.GPFDL": "",
394
+ "record.metadata.api.accessServiceProtocol.esriRest": "",
395
+ "record.metadata.api.accessServiceProtocol.maplibre-style": "",
396
+ "record.metadata.api.accessServiceProtocol.ogcFeatures": "",
397
+ "record.metadata.api.accessServiceProtocol.other": "",
398
+ "record.metadata.api.accessServiceProtocol.tms": "",
399
+ "record.metadata.api.accessServiceProtocol.wfs": "",
400
+ "record.metadata.api.accessServiceProtocol.wms": "",
401
+ "record.metadata.api.accessServiceProtocol.wmts": "",
402
+ "record.metadata.api.accessServiceProtocol.wps": "",
391
403
  "record.metadata.api.form.closeButton": "",
392
404
  "record.metadata.api.form.closeForm": "",
393
405
  "record.metadata.api.form.create": "",