geonetwork-ui 2.3.0-dev.f54cc11e → 2.3.0-dev.ff2a9db7

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 (160) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +2 -2
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +2 -2
  3. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +9 -7
  4. package/esm2022/libs/feature/dataviz/src/lib/chart-view/chart-view.component.mjs +1 -1
  5. package/esm2022/libs/feature/dataviz/src/lib/service/data.service.mjs +35 -2
  6. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-license/form-field-license.component.mjs +1 -1
  7. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.mjs +104 -0
  8. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +8 -3
  9. package/esm2022/libs/feature/editor/src/lib/components/wizard-field/wizard-field.component.mjs +1 -1
  10. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +8 -1
  11. package/esm2022/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.mjs +73 -20
  12. package/esm2022/libs/feature/map/src/lib/add-layer-from-wfs/add-layer-from-wfs.component.mjs +1 -1
  13. package/esm2022/libs/feature/map/src/lib/add-layer-from-wms/add-layer-from-wms.component.mjs +1 -1
  14. package/esm2022/libs/feature/map/src/lib/map-context/map-context.model.mjs +1 -1
  15. package/esm2022/libs/feature/map/src/lib/map-context/map-context.service.mjs +29 -8
  16. package/esm2022/libs/feature/record/src/lib/data-view/data-view.component.mjs +1 -1
  17. package/esm2022/libs/feature/record/src/lib/map-view/map-view.component.mjs +4 -3
  18. package/esm2022/libs/feature/record/src/lib/state/mdview.facade.mjs +1 -1
  19. package/esm2022/libs/feature/search/src/index.mjs +2 -1
  20. package/esm2022/libs/feature/search/src/lib/results-layout/results-layout.component.mjs +1 -1
  21. package/esm2022/libs/feature/search/src/lib/sort-by/sort-by.component.mjs +1 -1
  22. package/esm2022/libs/feature/search/src/lib/utils/service/fields.service.mjs +1 -1
  23. package/esm2022/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.mjs +1 -1
  24. package/esm2022/libs/ui/catalog/src/lib/organisations-filter/organisations-filter.component.mjs +1 -1
  25. package/esm2022/libs/ui/elements/src/lib/api-card/api-card.component.mjs +3 -2
  26. package/esm2022/libs/ui/elements/src/lib/downloads-list/downloads-list.component.mjs +2 -2
  27. package/esm2022/libs/ui/elements/src/lib/link-card/link-card.component.mjs +16 -3
  28. package/esm2022/libs/ui/elements/src/lib/record-api-form/record-api-form.component.mjs +99 -36
  29. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +1 -6
  30. package/esm2022/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.mjs +3 -5
  31. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  32. package/esm2022/libs/ui/inputs/src/lib/check-toggle/check-toggle.component.mjs +4 -3
  33. package/esm2022/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.mjs +5 -3
  34. package/esm2022/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.mjs +29 -0
  35. package/esm2022/libs/ui/inputs/src/lib/text-input/text-input.component.mjs +5 -3
  36. package/esm2022/libs/ui/inputs/src/lib/ui-inputs.module.mjs +6 -5
  37. package/esm2022/libs/ui/layout/src/index.mjs +2 -1
  38. package/esm2022/libs/ui/layout/src/lib/block-list/block-list.component.mjs +76 -0
  39. package/esm2022/libs/ui/layout/src/lib/carousel/carousel.component.mjs +42 -18
  40. package/esm2022/libs/ui/layout/src/lib/ui-layout.module.mjs +3 -8
  41. package/esm2022/libs/util/shared/src/lib/links/link-classifier.service.mjs +2 -2
  42. package/esm2022/libs/util/shared/src/lib/links/link-utils.mjs +22 -1
  43. package/esm2022/translations/de.json +11 -14
  44. package/esm2022/translations/en.json +11 -14
  45. package/esm2022/translations/es.json +11 -14
  46. package/esm2022/translations/fr.json +11 -14
  47. package/esm2022/translations/it.json +11 -14
  48. package/esm2022/translations/nl.json +11 -14
  49. package/esm2022/translations/pt.json +11 -14
  50. package/fesm2022/geonetwork-ui.mjs +642 -239
  51. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  52. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  53. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  54. package/libs/feature/dataviz/src/lib/service/data.service.d.ts +3 -1
  55. package/libs/feature/dataviz/src/lib/service/data.service.d.ts.map +1 -1
  56. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.d.ts +21 -0
  57. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.d.ts.map +1 -0
  58. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +1 -0
  59. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  60. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  61. package/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.d.ts +10 -5
  62. package/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.d.ts.map +1 -1
  63. package/libs/feature/map/src/lib/map-context/map-context.model.d.ts +1 -0
  64. package/libs/feature/map/src/lib/map-context/map-context.model.d.ts.map +1 -1
  65. package/libs/feature/map/src/lib/map-context/map-context.service.d.ts.map +1 -1
  66. package/libs/feature/record/src/lib/map-view/map-view.component.d.ts.map +1 -1
  67. package/libs/feature/record/src/lib/state/mdview.facade.d.ts.map +1 -1
  68. package/libs/feature/search/src/index.d.ts +1 -0
  69. package/libs/feature/search/src/index.d.ts.map +1 -1
  70. package/libs/feature/search/src/lib/utils/service/fields.service.d.ts +3 -3
  71. package/libs/feature/search/src/lib/utils/service/fields.service.d.ts.map +1 -1
  72. package/libs/ui/elements/src/lib/api-card/api-card.component.d.ts.map +1 -1
  73. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  74. package/libs/ui/elements/src/lib/link-card/link-card.component.d.ts +3 -1
  75. package/libs/ui/elements/src/lib/link-card/link-card.component.d.ts.map +1 -1
  76. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +24 -4
  77. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts.map +1 -1
  78. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +29 -30
  79. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  80. package/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.d.ts +1 -2
  81. package/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.d.ts.map +1 -1
  82. package/libs/ui/inputs/src/index.d.ts +1 -0
  83. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  84. package/libs/ui/inputs/src/lib/check-toggle/check-toggle.component.d.ts +1 -1
  85. package/libs/ui/inputs/src/lib/check-toggle/check-toggle.component.d.ts.map +1 -1
  86. package/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.d.ts +2 -1
  87. package/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.d.ts.map +1 -1
  88. package/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.d.ts +12 -0
  89. package/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.d.ts.map +1 -0
  90. package/libs/ui/inputs/src/lib/text-input/text-input.component.d.ts +2 -1
  91. package/libs/ui/inputs/src/lib/text-input/text-input.component.d.ts.map +1 -1
  92. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts +27 -27
  93. package/libs/ui/layout/src/index.d.ts +1 -0
  94. package/libs/ui/layout/src/index.d.ts.map +1 -1
  95. package/libs/ui/layout/src/lib/block-list/block-list.component.d.ts +25 -0
  96. package/libs/ui/layout/src/lib/block-list/block-list.component.d.ts.map +1 -0
  97. package/libs/ui/layout/src/lib/carousel/carousel.component.d.ts +13 -6
  98. package/libs/ui/layout/src/lib/carousel/carousel.component.d.ts.map +1 -1
  99. package/libs/ui/layout/src/lib/ui-layout.module.d.ts +4 -5
  100. package/libs/ui/layout/src/lib/ui-layout.module.d.ts.map +1 -1
  101. package/libs/util/shared/src/lib/links/link-utils.d.ts +18 -0
  102. package/libs/util/shared/src/lib/links/link-utils.d.ts.map +1 -1
  103. package/package.json +2 -2
  104. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +1 -1
  105. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +1 -4
  106. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +16 -10
  107. package/src/libs/common/fixtures/src/lib/link.fixtures.ts +8 -0
  108. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +51 -1
  109. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.css +0 -0
  110. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +14 -0
  111. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +143 -0
  112. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +5 -0
  113. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
  114. package/src/libs/feature/editor/src/lib/fields.config.ts +7 -0
  115. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.css +7 -0
  116. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.html +32 -18
  117. package/src/libs/feature/map/src/lib/add-layer-from-ogc-api/add-layer-from-ogc-api.component.ts +72 -17
  118. package/src/libs/feature/map/src/lib/map-context/map-context.model.ts +1 -0
  119. package/src/libs/feature/map/src/lib/map-context/map-context.service.ts +26 -8
  120. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +2 -1
  121. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +0 -1
  122. package/src/libs/feature/search/src/index.ts +1 -0
  123. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +2 -2
  124. package/src/libs/ui/elements/src/lib/api-card/api-card.component.ts +2 -1
  125. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts +1 -1
  126. package/src/libs/ui/elements/src/lib/link-card/link-card.component.html +38 -20
  127. package/src/libs/ui/elements/src/lib/link-card/link-card.component.ts +12 -0
  128. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +26 -10
  129. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +126 -30
  130. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +0 -2
  131. package/src/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.html +1 -1
  132. package/src/libs/ui/elements/src/lib/user-feedback-item/user-feedback-item.component.ts +0 -1
  133. package/src/libs/ui/inputs/src/index.ts +1 -0
  134. package/src/libs/ui/inputs/src/lib/check-toggle/check-toggle.component.ts +3 -0
  135. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.html +1 -0
  136. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.ts +1 -0
  137. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.css +6 -0
  138. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.html +26 -0
  139. package/src/libs/ui/inputs/src/lib/previous-next-buttons/previous-next-buttons.component.ts +32 -0
  140. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +1 -0
  141. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +1 -0
  142. package/src/libs/ui/inputs/src/lib/ui-inputs.module.ts +1 -1
  143. package/src/libs/ui/layout/src/index.ts +1 -0
  144. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +23 -0
  145. package/src/libs/ui/layout/src/lib/block-list/block-list.component.html +20 -0
  146. package/src/libs/ui/layout/src/lib/block-list/block-list.component.ts +84 -0
  147. package/src/libs/ui/layout/src/lib/carousel/carousel.component.css +8 -4
  148. package/src/libs/ui/layout/src/lib/carousel/carousel.component.html +4 -4
  149. package/src/libs/ui/layout/src/lib/carousel/carousel.component.ts +45 -15
  150. package/src/libs/ui/layout/src/lib/ui-layout.module.ts +0 -2
  151. package/src/libs/util/shared/src/lib/links/link-classifier.service.ts +1 -1
  152. package/src/libs/util/shared/src/lib/links/link-utils.ts +21 -0
  153. package/translations/de.json +11 -14
  154. package/translations/en.json +11 -14
  155. package/translations/es.json +11 -14
  156. package/translations/fr.json +11 -14
  157. package/translations/it.json +11 -14
  158. package/translations/nl.json +11 -14
  159. package/translations/pt.json +11 -14
  160. package/translations/sk.json +11 -14
@@ -4,8 +4,7 @@
4
4
  (valueChange)="urlChange.next($event)"
5
5
  [hint]="'map.ogc.urlInput.hint' | translate"
6
6
  class="w-96"
7
- >
8
- </gn-ui-text-input>
7
+ ></gn-ui-text-input>
9
8
  </div>
10
9
 
11
10
  <div *ngIf="errorMessage" class="text-red-500 mt-2">
@@ -16,21 +15,36 @@
16
15
  <p class="loading-message" translate>map.loading.service</p>
17
16
  </div>
18
17
 
19
- <div *ngIf="!loading && layers.length > 0">
20
- <h2 class="font-bold" translate>map.layers.available</h2>
21
- <ng-container *ngFor="let layer of layers">
22
- <div class="flex items-center justify-between my-2 layer-item-tree">
23
- <p class="max-w-xs overflow-hidden overflow-ellipsis whitespace-nowrap">
24
- {{ layer }}
25
- </p>
26
- <gn-ui-button
27
- class="layer-add-btn"
28
- type="primary"
29
- (buttonClick)="addLayer(layer)"
30
- extraClass="text-sm !px-2 !py-1"
31
- translate
32
- ><span translate> map.layer.add </span></gn-ui-button
18
+ <ng-container *ngFor="let layer of layers">
19
+ <div
20
+ *ngIf="shouldDisplayLayer(layer)"
21
+ class="flex items-center justify-between my-2 layer-item-tree"
22
+ >
23
+ <div class="flex flex-col items-start w-full">
24
+ <p
25
+ class="max-w-xs overflow-hidden overflow-ellipsis whitespace-nowrap"
26
+ [title]="layer.name"
33
27
  >
28
+ {{ layer.name }}
29
+ </p>
30
+ <div class="flex justify-between items-center w-full">
31
+ <gn-ui-dropdown-selector
32
+ [title]="'Add Layer As' | translate"
33
+ [choices]="getLayerChoices(layer)"
34
+ (selectValue)="onLayerTypeSelect(layer.name, $event)"
35
+ [selected]="selectedLayerTypes[layer.name]"
36
+ extraBtnClass="w-6 h-5 !text-sm !px-2 !py-1"
37
+ ></gn-ui-dropdown-selector>
38
+ <gn-ui-button
39
+ class="layer-add-btn"
40
+ type="primary"
41
+ (buttonClick)="addLayer(layer.name, selectedLayerTypes[layer.name])"
42
+ extraClass="text-sm !px-2 !py-1"
43
+ translate
44
+ >
45
+ <span translate>map.layer.add</span>
46
+ </gn-ui-button>
47
+ </div>
34
48
  </div>
35
- </ng-container>
36
- </div>
49
+ </div>
50
+ </ng-container>
@@ -14,7 +14,7 @@ import {
14
14
  MapContextLayerTypeEnum,
15
15
  } from '../map-context/map-context.model'
16
16
  import { TranslateModule } from '@ngx-translate/core'
17
- import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
17
+ import { DropdownChoice, UiInputsModule } from '../../../../../../libs/ui/inputs/src'
18
18
  import { CommonModule } from '@angular/common'
19
19
  import { MapLayer } from '../+state/map.models'
20
20
 
@@ -30,18 +30,16 @@ export class AddLayerFromOgcApiComponent implements OnInit {
30
30
  @Output() layerAdded = new EventEmitter<MapLayer>()
31
31
 
32
32
  urlChange = new Subject<string>()
33
- layerUrl = ''
34
33
  loading = false
35
- layers: string[] = []
36
- ogcEndpoint: OgcApiEndpoint = null
34
+ layers: any[] = []
37
35
  errorMessage: string | null = null
36
+ selectedLayerTypes: { [key: string]: DropdownChoice['value'] } = {}
38
37
 
39
38
  constructor(private changeDetectorRef: ChangeDetectorRef) {}
40
39
 
41
40
  ngOnInit() {
42
41
  this.urlChange.pipe(debounceTime(700)).subscribe(() => {
43
42
  this.loadLayers()
44
- this.changeDetectorRef.detectChanges() // manually trigger change detection
45
43
  })
46
44
  }
47
45
 
@@ -49,14 +47,13 @@ export class AddLayerFromOgcApiComponent implements OnInit {
49
47
  this.errorMessage = null
50
48
  try {
51
49
  this.loading = true
52
- if (this.ogcUrl.trim() === '') {
50
+ if (!this.ogcUrl.trim()) {
53
51
  this.layers = []
54
52
  return
55
53
  }
56
- this.ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
57
-
58
- // Currently only supports feature collections
59
- this.layers = await this.ogcEndpoint.featureCollections
54
+ const ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
55
+ this.layers = await ogcEndpoint.allCollections
56
+ this.setDefaultLayerTypes()
60
57
  } catch (error) {
61
58
  const err = error as Error
62
59
  this.layers = []
@@ -67,14 +64,72 @@ export class AddLayerFromOgcApiComponent implements OnInit {
67
64
  }
68
65
  }
69
66
 
70
- async addLayer(layer: string) {
71
- this.layerUrl = await this.ogcEndpoint.getCollectionItemsUrl(layer)
67
+ setDefaultLayerTypes() {
68
+ this.layers.forEach((layer) => {
69
+ const choices = this.getLayerChoices(layer)
70
+ if (choices.length > 0) {
71
+ this.selectedLayerTypes[layer.name] = choices[0].value
72
+ }
73
+ })
74
+ }
72
75
 
73
- const layerToAdd: MapContextLayerModel = {
74
- name: layer,
75
- url: this.layerUrl,
76
- type: MapContextLayerTypeEnum.OGCAPI,
76
+ getLayerChoices(layer: any) {
77
+ const choices = []
78
+ if (layer.hasRecords) {
79
+ choices.push({ label: 'Records', value: 'record' })
80
+ }
81
+ if (layer.hasFeatures) {
82
+ choices.push({ label: 'Features', value: 'features' })
83
+ }
84
+ if (layer.hasVectorTiles) {
85
+ choices.push({ label: 'Vector Tiles', value: 'vectorTiles' })
86
+ }
87
+ if (layer.hasMapTiles) {
88
+ choices.push({ label: 'Map Tiles', value: 'mapTiles' })
89
+ }
90
+ return choices
91
+ }
92
+
93
+ shouldDisplayLayer(layer: any) {
94
+ return (
95
+ layer.hasRecords ||
96
+ layer.hasFeatures ||
97
+ layer.hasVectorTiles ||
98
+ layer.hasMapTiles
99
+ )
100
+ }
101
+
102
+ onLayerTypeSelect(layerName: string, selectedType: any) {
103
+ this.selectedLayerTypes[layerName] = selectedType
104
+ ? selectedType
105
+ : this.getLayerChoices(layerName)[0]?.value
106
+ }
107
+
108
+ async addLayer(layer: string, layerType: any) {
109
+ try {
110
+ const ogcEndpoint = await new OgcApiEndpoint(this.ogcUrl)
111
+ let layerUrl: string
112
+
113
+ if (layerType === 'vectorTiles') {
114
+ layerUrl = await ogcEndpoint.getVectorTilesetUrl(layer)
115
+ } else if (layerType === 'mapTiles') {
116
+ layerUrl = await ogcEndpoint.getMapTilesetUrl(layer)
117
+ } else {
118
+ layerUrl = await ogcEndpoint.getCollectionItemsUrl(layer, {
119
+ outputFormat: 'json',
120
+ })
121
+ }
122
+
123
+ const layerToAdd: MapContextLayerModel = {
124
+ name: layer,
125
+ url: layerUrl,
126
+ type: MapContextLayerTypeEnum.OGCAPI,
127
+ layerType: layerType,
128
+ }
129
+ this.layerAdded.emit({ ...layerToAdd, title: layer })
130
+ } catch (error) {
131
+ const err = error as Error
132
+ console.error('Error adding layer:', err.message)
77
133
  }
78
- this.layerAdded.emit({ ...layerToAdd, title: layer })
79
134
  }
80
135
  }
@@ -38,6 +38,7 @@ export interface MapContextLayerOgcapiModel {
38
38
  type: 'ogcapi'
39
39
  url: string
40
40
  name: string
41
+ layerType: 'feature' | 'vectorTiles' | 'mapTiles' | 'record'
41
42
  }
42
43
 
43
44
  interface LayerXyzModel {
@@ -25,6 +25,10 @@ import WMTS from 'ol/source/WMTS'
25
25
  import { Geometry } from 'ol/geom'
26
26
  import Feature from 'ol/Feature'
27
27
  import { WfsEndpoint, WmtsEndpoint } from '@camptocamp/ogc-client'
28
+ import OGCVectorTile from 'ol/source/OGCVectorTile.js'
29
+ import { MVT } from 'ol/format'
30
+ import VectorTileLayer from 'ol/layer/VectorTile'
31
+ import OGCMapTile from 'ol/source/OGCMapTile.js'
28
32
 
29
33
  export const DEFAULT_BASELAYER_CONTEXT: MapContextLayerXyzModel = {
30
34
  type: MapContextLayerTypeEnum.XYZ,
@@ -78,14 +82,28 @@ export class MapContextService {
78
82
  const style = this.styleService.styles.default
79
83
  switch (type) {
80
84
  case MapContextLayerTypeEnum.OGCAPI:
81
- return new VectorLayer({
82
- source: new VectorSource({
83
- format: new GeoJSON(),
84
- url: layerModel.url,
85
- }),
86
- style,
87
- })
88
-
85
+ if (layerModel.layerType === 'vectorTiles') {
86
+ return new VectorTileLayer({
87
+ source: new OGCVectorTile({
88
+ url: layerModel.url,
89
+ format: new MVT(),
90
+ }),
91
+ })
92
+ } else if (layerModel.layerType === 'mapTiles') {
93
+ return new TileLayer({
94
+ source: new OGCMapTile({
95
+ url: layerModel.url,
96
+ }),
97
+ })
98
+ } else {
99
+ return new VectorLayer({
100
+ source: new VectorSource({
101
+ format: new GeoJSON(),
102
+ url: layerModel.url,
103
+ }),
104
+ style,
105
+ })
106
+ }
89
107
  case MapContextLayerTypeEnum.XYZ:
90
108
  return new TileLayer({
91
109
  source: new XYZ({
@@ -196,7 +196,8 @@ export class MapViewComponent implements OnInit, OnDestroy {
196
196
  } else if (
197
197
  (link.type === 'service' &&
198
198
  (link.accessServiceProtocol === 'wfs' ||
199
- link.accessServiceProtocol === 'esriRest')) ||
199
+ link.accessServiceProtocol === 'esriRest' ||
200
+ link.accessServiceProtocol === 'ogcFeatures')) ||
200
201
  link.type === 'download'
201
202
  ) {
202
203
  return this.dataService.readAsGeoJson(link).pipe(
@@ -8,7 +8,6 @@ import { DatavizConfigurationModel } from '../../../../../../libs/common/domain/
8
8
  import {
9
9
  CatalogRecord,
10
10
  UserFeedback,
11
- UserFeedbackViewModel,
12
11
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
12
  import { AvatarServiceInterface } from '../../../../../../libs/api/repository/src'
14
13
 
@@ -8,6 +8,7 @@ export * from './lib/state/effects'
8
8
  export * from './lib/state/reducer'
9
9
  export * from './lib/utils/service/search.service'
10
10
  export * from './lib/utils/service/fields.service'
11
+ export * from './lib/utils/service/fields'
11
12
  export * from './lib/results-list/results-list.container.component'
12
13
  export * from './lib/filter-dropdown/filter-dropdown.component'
13
14
  export * from './lib/constants'
@@ -36,7 +36,7 @@ marker('search.filters.contact')
36
36
  providedIn: 'root',
37
37
  })
38
38
  export class FieldsService {
39
- private fields = {
39
+ protected fields = {
40
40
  publisher: new OrganizationSearchField(this.injector),
41
41
  format: new SimpleSearchField('format', this.injector, 'asc'),
42
42
  resourceType: new TranslatedSearchField(
@@ -76,7 +76,7 @@ export class FieldsService {
76
76
  return Object.keys(this.fields)
77
77
  }
78
78
 
79
- constructor(private injector: Injector) {}
79
+ constructor(protected injector: Injector) {}
80
80
 
81
81
  getAvailableValues(fieldName: string) {
82
82
  if (this.supportedFields.indexOf(fieldName) === -1)
@@ -26,7 +26,8 @@ export class ApiCardComponent implements OnInit, OnChanges {
26
26
 
27
27
  ngOnInit() {
28
28
  this.displayApiFormButton =
29
- this.link.accessServiceProtocol === 'ogcFeatures' ? true : false
29
+ this.link.accessServiceProtocol === 'ogcFeatures' ||
30
+ this.link.accessServiceProtocol === 'wfs'
30
31
  }
31
32
 
32
33
  ngOnChanges(changes: SimpleChanges) {
@@ -95,6 +95,6 @@ export class DownloadsListComponent {
95
95
  }
96
96
 
97
97
  isFromWfs(link: DatasetDistribution) {
98
- return link.type === 'service' && link.accessServiceProtocol === 'wfs'
98
+ return link.type === 'download' && link.accessServiceProtocol === 'wfs'
99
99
  }
100
100
  }
@@ -1,25 +1,43 @@
1
1
  <a
2
2
  [href]="link.url"
3
3
  target="_blank"
4
- class="flex flex-col justify-between group h-40 grow py-5 px-5 bg-white rounded border-gray-300 filter card-shadow overflow-hidden lg:w-80"
4
+ class="flex flex-col justify-between group grow py-5 px-5 bg-white rounded border-gray-300 filter card-shadow overflow-hidden"
5
+ [ngClass]="{ 'h-40': !compact }"
6
+ [title]="title"
5
7
  >
6
- <div class="max-h-24 overflow-hidden text-ellipsis">
7
- <p
8
- class="font-title font-medium text-21 text-black break-words mb-1 line-clamp-2"
9
- >
10
- {{ link.name }}
11
- </p>
12
- <p class="font-medium text-sm break-words">
13
- {{ link.description }}
14
- </p>
15
- <p
16
- *ngIf="!link.name && !link.description"
17
- class="font-medium text-sm break-words truncate"
18
- >
19
- {{ link.url }}
20
- </p>
21
- </div>
22
- <div>
23
- <mat-icon class="material-symbols-outlined card-icon">open_in_new</mat-icon>
24
- </div>
8
+ <ng-container *ngIf="!compact; else compactTpl">
9
+ <div class="max-h-24 overflow-hidden text-ellipsis">
10
+ <p
11
+ class="font-title font-medium text-21 text-black break-words mb-1 line-clamp-2"
12
+ >
13
+ {{ link.name }}
14
+ </p>
15
+ <p class="font-medium text-sm break-words">
16
+ {{ link.description }}
17
+ </p>
18
+ <p
19
+ *ngIf="!link.name && !link.description"
20
+ class="font-medium text-sm break-words truncate"
21
+ >
22
+ {{ link.url }}
23
+ </p>
24
+ </div>
25
+ <div>
26
+ <mat-icon class="material-symbols-outlined card-icon"
27
+ >open_in_new</mat-icon
28
+ >
29
+ </div>
30
+ </ng-container>
31
+ <ng-template #compactTpl>
32
+ <div class="flex items-center justify-between gap-4">
33
+ <p
34
+ class="overflow-hidden font-title font-medium text-21 text-black text-ellipsis whitespace-nowrap"
35
+ >
36
+ {{ link.name || link.description }}
37
+ </p>
38
+ <mat-icon class="material-symbols-outlined card-icon flex-shrink-0"
39
+ >open_in_new</mat-icon
40
+ >
41
+ </div>
42
+ </ng-template>
25
43
  </a>
@@ -1,12 +1,24 @@
1
1
  import { Component, ChangeDetectionStrategy, Input } from '@angular/core'
2
2
  import { DatasetDistribution } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+ import { MatIconModule } from '@angular/material/icon'
4
+ import { CommonModule } from '@angular/common'
3
5
 
4
6
  @Component({
5
7
  selector: 'gn-ui-link-card',
6
8
  templateUrl: './link-card.component.html',
7
9
  styleUrls: ['./link-card.component.css'],
8
10
  changeDetection: ChangeDetectionStrategy.OnPush,
11
+ standalone: true,
12
+ imports: [CommonModule, MatIconModule],
9
13
  })
10
14
  export class LinkCardComponent {
11
15
  @Input() link: DatasetDistribution
16
+ @Input() compact = false
17
+
18
+ get title() {
19
+ if (this.link.name && this.link.description) {
20
+ return `${this.link.name} | ${this.link.description}`
21
+ }
22
+ return this.link.name || this.link.description || ''
23
+ }
12
24
  }
@@ -37,15 +37,31 @@
37
37
  </div>
38
38
  </div>
39
39
  </div>
40
- <div class="flex flex-col gap-3">
41
- <p class="text-sm" translate>record.metadata.api.form.offset</p>
42
- <gn-ui-text-input
43
- class="w-20"
44
- [value]="offset$ | async"
45
- (valueChange)="setOffset($event)"
46
- hint=""
47
- >
48
- </gn-ui-text-input>
40
+ <div class="flex flex-col gap-3 relative">
41
+ <p class="text-sm" [class.text-gray-600]="!supportOffset" translate>
42
+ record.metadata.api.form.offset
43
+ </p>
44
+ <div class="flex items-center">
45
+ <gn-ui-text-input
46
+ class="w-20"
47
+ [value]="offset$ | async"
48
+ [disabled]="!supportOffset"
49
+ (valueChange)="supportOffset ? setOffset($event) : null"
50
+ hint=""
51
+ >
52
+ </gn-ui-text-input>
53
+ <div
54
+ *ngIf="!supportOffset"
55
+ class="flex items-center gap-2 text-orange-500 z-10 ml-3"
56
+ >
57
+ <span
58
+ class="material-symbols-outlined"
59
+ matTooltip="Not supported on this service"
60
+ >
61
+ warning
62
+ </span>
63
+ </div>
64
+ </div>
49
65
  </div>
50
66
  <div class="flex flex-col gap-3">
51
67
  <p class="text-sm" translate>record.metadata.api.form.type</p>
@@ -55,7 +71,7 @@
55
71
  extraBtnClass="secondary min-w-full !w-40 !text-black"
56
72
  [showTitle]="false"
57
73
  class="text-black"
58
- [choices]="formatsList"
74
+ [choices]="outputFormats"
59
75
  (selectValue)="setFormat($event)"
60
76
  [selected]="format$ | async"
61
77
  ></gn-ui-dropdown-selector>
@@ -1,12 +1,23 @@
1
1
  import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
2
- import { DatasetServiceDistribution } from '../../../../../../libs/common/domain/src/lib/model/record'
3
- import { BehaviorSubject, combineLatest, map } from 'rxjs'
2
+ import { OgcApiEndpoint, WfsEndpoint } from '@camptocamp/ogc-client'
3
+ import {
4
+ DatasetServiceDistribution,
5
+ ServiceProtocol,
6
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
7
+ import { mimeTypeToFormat } from '../../../../../../libs/util/shared/src'
8
+ import { BehaviorSubject, combineLatest, map, switchMap } from 'rxjs'
4
9
 
5
10
  const DEFAULT_PARAMS = {
6
11
  OFFSET: '',
7
12
  LIMIT: '-1',
8
13
  FORMAT: 'json',
9
14
  }
15
+
16
+ interface OutputFormats {
17
+ itemFormats?: any[]
18
+ outputFormats?: any[]
19
+ }
20
+
10
21
  @Component({
11
22
  selector: 'gn-ui-record-api-form',
12
23
  templateUrl: './record-api-form.component.html',
@@ -15,39 +26,44 @@ const DEFAULT_PARAMS = {
15
26
  })
16
27
  export class RecordApiFormComponent {
17
28
  @Input() set apiLink(value: DatasetServiceDistribution) {
18
- this.apiBaseUrl = value ? value.url.href : undefined
29
+ this.outputFormats = [{ value: 'json', label: 'JSON' }]
30
+ this.accessServiceProtocol = value ? value.accessServiceProtocol : undefined
31
+ this.apiFeatureType = value ? value.name : undefined
32
+ if (value) {
33
+ this.apiBaseUrl = value.url.href
34
+ this.createEndpoint().then(() => this.parseOutputFormats())
35
+ }
19
36
  this.resetUrl()
20
37
  }
21
- offset$ = new BehaviorSubject('')
22
- limit$ = new BehaviorSubject('')
23
- format$ = new BehaviorSubject('')
38
+
39
+ offset$ = new BehaviorSubject(DEFAULT_PARAMS.OFFSET)
40
+ limit$ = new BehaviorSubject(DEFAULT_PARAMS.LIMIT)
41
+ format$ = new BehaviorSubject(DEFAULT_PARAMS.FORMAT)
42
+ endpoint$ = new BehaviorSubject<WfsEndpoint | OgcApiEndpoint | undefined>(
43
+ undefined
44
+ )
24
45
  apiBaseUrl: string
25
- formatsList = [
26
- { label: 'JSON', value: 'json' },
27
- { label: 'CSV', value: 'csv' },
28
- ]
29
- apiQueryUrl$ = combineLatest([this.offset$, this.limit$, this.format$]).pipe(
30
- map(([offset, limit, format]) => {
31
- let outputUrl
32
- if (this.apiBaseUrl) {
33
- const url = new URL(this.apiBaseUrl)
34
- const params = { offset: offset, limit: limit, f: format }
35
- for (const [key, value] of Object.entries(params)) {
36
- if (value && value !== '0') {
37
- url.searchParams.set(key, value)
38
- } else {
39
- url.searchParams.delete(key)
40
- }
41
- }
42
- outputUrl = url.toString()
43
- }
44
- return outputUrl
45
- })
46
+ apiFeatureType: string
47
+ supportOffset = true
48
+ accessServiceProtocol: ServiceProtocol | undefined
49
+ outputFormats = [{ value: 'json', label: 'JSON' }]
50
+ endpoint: WfsEndpoint | OgcApiEndpoint | undefined
51
+ firstCollection: string | undefined
52
+
53
+ apiQueryUrl$ = combineLatest([
54
+ this.offset$,
55
+ this.limit$,
56
+ this.format$,
57
+ this.endpoint$,
58
+ ]).pipe(
59
+ switchMap(([offset, limit, format]) =>
60
+ this.generateApiQueryUrl(offset, limit, format)
61
+ )
46
62
  )
63
+
47
64
  noLimitChecked$ = this.limit$.pipe(
48
65
  map((limit) => limit === '-1' || limit === '')
49
66
  )
50
-
51
67
  displayLimit$ = this.limit$.pipe(
52
68
  map((limit) => (limit !== '-1' ? limit : ''))
53
69
  )
@@ -57,8 +73,7 @@ export class RecordApiFormComponent {
57
73
  }
58
74
 
59
75
  setLimit(value: string) {
60
- const newLimit = value === '' ? '-1' : value
61
- this.limit$.next(newLimit)
76
+ this.limit$.next(value === '' ? '-1' : value)
62
77
  }
63
78
 
64
79
  setFormat(value: string | unknown) {
@@ -70,4 +85,85 @@ export class RecordApiFormComponent {
70
85
  this.limit$.next(DEFAULT_PARAMS.LIMIT)
71
86
  this.format$.next(DEFAULT_PARAMS.FORMAT)
72
87
  }
88
+
89
+ async parseOutputFormats() {
90
+ if (!this.endpoint) return
91
+ const apiUrl = this.apiBaseUrl.endsWith('?')
92
+ ? this.apiBaseUrl.slice(0, -1)
93
+ : this.apiBaseUrl
94
+ const outputFormats = await this.getOutputFormats(apiUrl)
95
+
96
+ const formatsList = outputFormats.itemFormats
97
+ ? this.mapFormats(outputFormats.itemFormats)
98
+ : this.mapFormats(outputFormats.outputFormats || [])
99
+
100
+ this.outputFormats = this.outputFormats
101
+ .concat(formatsList.filter(Boolean))
102
+ .filter(
103
+ (format, index, self) =>
104
+ index === self.findIndex((t) => t.value === format.value)
105
+ )
106
+ .sort((a, b) => a.label.localeCompare(b.label))
107
+ }
108
+
109
+ mapFormats(formats: any[]) {
110
+ return formats.map((format) => {
111
+ const normalizedFormat = mimeTypeToFormat(format)
112
+ return normalizedFormat
113
+ ? { label: normalizedFormat.toUpperCase(), value: normalizedFormat }
114
+ : null
115
+ })
116
+ }
117
+
118
+ async getOutputFormats(url: string): Promise<OutputFormats> {
119
+ if (!this.endpoint) return {}
120
+ if (this.endpoint instanceof WfsEndpoint) {
121
+ this.supportOffset = this.endpoint.supportsStartIndex()
122
+ return this.endpoint.getServiceInfo() as OutputFormats
123
+ } else {
124
+ {
125
+ return (await this.endpoint.getCollectionInfo(
126
+ this.firstCollection
127
+ )) as OutputFormats
128
+ }
129
+ }
130
+ }
131
+
132
+ async createEndpoint() {
133
+ if (!this.apiBaseUrl || !this.accessServiceProtocol) return
134
+ if (this.accessServiceProtocol === 'wfs') {
135
+ this.endpoint = new WfsEndpoint(this.apiBaseUrl)
136
+ await (this.endpoint as WfsEndpoint).isReady()
137
+ } else {
138
+ this.endpoint = new OgcApiEndpoint(this.apiBaseUrl)
139
+ this.firstCollection = (await this.endpoint.featureCollections)[0]
140
+ }
141
+ this.endpoint$.next(this.endpoint)
142
+ }
143
+
144
+ async generateApiQueryUrl(
145
+ offset: string,
146
+ limit: string,
147
+ format: string
148
+ ): Promise<string> {
149
+ if (!this.apiBaseUrl || !this.endpoint || !this.apiFeatureType) return ''
150
+
151
+ const options = {
152
+ outputFormat: format,
153
+ startIndex: offset ? Number(offset) : undefined,
154
+ maxFeatures: limit !== '-1' ? Number(limit) : undefined,
155
+ limit: limit !== '-1' ? Number(limit) : limit === '-1' ? -1 : undefined,
156
+ offset: offset !== '' ? Number(offset) : undefined,
157
+ }
158
+
159
+ if (this.endpoint instanceof WfsEndpoint) {
160
+ options.maxFeatures = limit !== '-1' ? Number(limit) : undefined
161
+ return this.endpoint.getFeatureUrl(this.apiFeatureType, options)
162
+ } else {
163
+ return await this.endpoint.getCollectionItemsUrl(
164
+ this.firstCollection,
165
+ options
166
+ )
167
+ }
168
+ }
73
169
  }
@@ -57,7 +57,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
57
57
  DownloadItemComponent,
58
58
  DownloadsListComponent,
59
59
  ApiCardComponent,
60
- LinkCardComponent,
61
60
  RelatedRecordCardComponent,
62
61
  MetadataContactComponent,
63
62
  MetadataCatalogComponent,
@@ -80,7 +79,6 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
80
79
  DownloadItemComponent,
81
80
  DownloadsListComponent,
82
81
  ApiCardComponent,
83
- LinkCardComponent,
84
82
  RelatedRecordCardComponent,
85
83
  MetadataContactComponent,
86
84
  MetadataCatalogComponent,