geonetwork-ui 2.4.0-dev.db93b4c4 → 2.4.0-dev.dd042cec

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 (186) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +5 -5
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +30 -2
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.mjs +31 -0
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +23 -1
  5. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +10 -3
  6. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +31 -3
  7. package/esm2022/libs/common/domain/src/lib/model/record/contact.model.mjs +28 -1
  8. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  9. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  10. package/esm2022/libs/data-access/gn4/src/openapi/api/records.api.service.mjs +2 -6
  11. package/esm2022/libs/feature/editor/src/lib/components/contact-card/contact-card.component.mjs +29 -0
  12. package/esm2022/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.mjs +131 -0
  13. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.mjs +170 -0
  14. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.mjs +3 -3
  15. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.mjs +47 -0
  16. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.mjs +21 -0
  17. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.mjs +7 -6
  18. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +33 -9
  19. package/esm2022/libs/feature/editor/src/lib/components/record-form/record-form.component.mjs +3 -3
  20. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +30 -3
  21. package/esm2022/libs/feature/map/src/lib/utils/map-utils.service.mjs +10 -5
  22. package/esm2022/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.mjs +3 -3
  23. package/esm2022/libs/feature/search/src/lib/results-table/results-table-container.component.mjs +46 -7
  24. package/esm2022/libs/feature/search/src/lib/state/search.facade.mjs +6 -2
  25. package/esm2022/libs/ui/elements/src/index.mjs +2 -1
  26. package/esm2022/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.mjs +27 -0
  27. package/esm2022/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.mjs +4 -3
  28. package/esm2022/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.mjs +2 -2
  29. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +3 -3
  30. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +5 -11
  31. package/esm2022/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.mjs +3 -3
  32. package/esm2022/libs/ui/elements/src/lib/sortable-list/sortable-list.component.mjs +7 -3
  33. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +5 -2
  34. package/esm2022/libs/ui/elements/src/lib/user-preview/user-preview.component.mjs +3 -3
  35. package/esm2022/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.mjs +19 -5
  36. package/esm2022/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.mjs +3 -3
  37. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table.component.mjs +3 -3
  38. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +67 -0
  39. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +28 -8
  40. package/esm2022/libs/ui/widgets/src/index.mjs +2 -1
  41. package/esm2022/libs/ui/widgets/src/lib/popover/popover.component.mjs +68 -0
  42. package/esm2022/translations/de.json +44 -17
  43. package/esm2022/translations/en.json +37 -10
  44. package/esm2022/translations/es.json +36 -9
  45. package/esm2022/translations/fr.json +47 -20
  46. package/esm2022/translations/it.json +37 -10
  47. package/esm2022/translations/nl.json +36 -9
  48. package/esm2022/translations/pt.json +36 -9
  49. package/fesm2022/geonetwork-ui.mjs +1266 -279
  50. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  51. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -1
  52. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +8 -1
  53. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  54. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts +5 -0
  55. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts.map +1 -0
  56. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +3 -1
  57. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  58. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts +1 -0
  59. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts.map +1 -1
  60. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +7 -1
  61. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  62. package/libs/common/domain/src/lib/model/record/contact.model.d.ts +1 -0
  63. package/libs/common/domain/src/lib/model/record/contact.model.d.ts.map +1 -1
  64. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +2 -1
  65. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  66. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +17 -0
  67. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  68. package/libs/data-access/gn4/src/openapi/api/records.api.service.d.ts.map +1 -1
  69. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts +12 -0
  70. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts.map +1 -0
  71. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts +27 -0
  72. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts.map +1 -0
  73. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.d.ts +47 -0
  74. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.d.ts.map +1 -0
  75. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.d.ts +17 -0
  76. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.d.ts.map +1 -0
  77. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts +11 -0
  78. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts.map +1 -0
  79. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.d.ts +3 -1
  80. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.d.ts.map +1 -1
  81. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +9 -1
  82. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  83. package/libs/feature/editor/src/lib/fields.config.d.ts +7 -0
  84. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  85. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts.map +1 -1
  86. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +14 -4
  87. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  88. package/libs/feature/search/src/lib/state/search.facade.d.ts +1 -0
  89. package/libs/feature/search/src/lib/state/search.facade.d.ts.map +1 -1
  90. package/libs/ui/elements/src/index.d.ts +1 -0
  91. package/libs/ui/elements/src/index.d.ts.map +1 -1
  92. package/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.d.ts +18 -0
  93. package/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.d.ts.map +1 -0
  94. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  95. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts +0 -3
  96. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  97. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +1 -1
  98. package/libs/ui/elements/src/lib/sortable-list/sortable-list.component.d.ts +4 -4
  99. package/libs/ui/elements/src/lib/sortable-list/sortable-list.component.d.ts.map +1 -1
  100. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +7 -6
  101. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  102. package/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.d.ts +9 -1
  103. package/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.d.ts.map +1 -1
  104. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts +20 -0
  105. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -0
  106. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +9 -3
  107. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  108. package/libs/ui/widgets/src/index.d.ts +1 -0
  109. package/libs/ui/widgets/src/index.d.ts.map +1 -1
  110. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts +19 -0
  111. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts.map +1 -0
  112. package/package.json +1 -1
  113. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +5 -1
  114. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +37 -12
  115. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -1
  116. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +4 -2
  117. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +72 -2
  118. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.ts +39 -0
  119. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +70 -1
  120. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +13 -5
  121. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +42 -2
  122. package/src/libs/common/domain/src/lib/model/record/contact.model.ts +28 -0
  123. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -1
  124. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +22 -0
  125. package/src/libs/data-access/gn4/src/openapi/api/records.api.service.ts +1 -5
  126. package/src/libs/data-access/gn4/src/spec.yaml +0 -8
  127. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.css +0 -0
  128. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html +25 -0
  129. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts +30 -0
  130. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html +2 -1
  131. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts +110 -19
  132. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.css +0 -0
  133. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +76 -0
  134. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +271 -0
  135. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +1 -1
  136. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.css +0 -0
  137. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.html +6 -0
  138. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.ts +59 -0
  139. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.css +0 -0
  140. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html +5 -0
  141. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +22 -0
  142. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.ts +8 -7
  143. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +18 -1
  144. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +30 -1
  145. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -1
  146. package/src/libs/feature/editor/src/lib/fields.config.ts +32 -2
  147. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +8 -4
  148. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html +1 -1
  149. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +3 -1
  150. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +57 -3
  151. package/src/libs/feature/search/src/lib/state/search.facade.ts +6 -0
  152. package/src/libs/ui/elements/src/index.ts +1 -0
  153. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.css +0 -0
  154. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.html +12 -0
  155. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.ts +37 -0
  156. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.html +4 -1
  157. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.css +2 -1
  158. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +12 -8
  159. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html +14 -20
  160. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +0 -10
  161. package/src/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html +1 -1
  162. package/src/libs/ui/elements/src/lib/sortable-list/sortable-list.component.html +3 -1
  163. package/src/libs/ui/elements/src/lib/sortable-list/sortable-list.component.ts +8 -4
  164. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -1
  165. package/src/libs/ui/elements/src/lib/user-preview/user-preview.component.html +1 -1
  166. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +15 -3
  167. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +1 -1
  168. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -0
  169. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.css +0 -0
  170. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +26 -0
  171. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +74 -0
  172. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +15 -1
  173. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +26 -12
  174. package/src/libs/ui/widgets/src/index.ts +1 -0
  175. package/src/libs/ui/widgets/src/lib/popover/popover.component.css +0 -0
  176. package/src/libs/ui/widgets/src/lib/popover/popover.component.html +3 -0
  177. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +85 -0
  178. package/tailwind.base.css +2 -1
  179. package/translations/de.json +44 -17
  180. package/translations/en.json +37 -10
  181. package/translations/es.json +36 -9
  182. package/translations/fr.json +47 -20
  183. package/translations/it.json +37 -10
  184. package/translations/nl.json +36 -9
  185. package/translations/pt.json +36 -9
  186. package/translations/sk.json +37 -10
@@ -34,6 +34,7 @@ import {
34
34
  readResourcePublished,
35
35
  readResourceUpdated,
36
36
  readSecurityConstraints,
37
+ readSpatialExtents,
37
38
  readSpatialRepresentation,
38
39
  readStatus,
39
40
  readTemporalExtents,
@@ -60,6 +61,7 @@ import {
60
61
  writeResourcePublished,
61
62
  writeResourceUpdated,
62
63
  writeSecurityConstraints,
64
+ writeSpatialExtents,
63
65
  writeSpatialRepresentation,
64
66
  writeStatus,
65
67
  writeTemporalExtents,
@@ -101,8 +103,8 @@ export class Iso19139Converter extends BaseConverter<string> {
101
103
  distributions: readDistributions,
102
104
  onlineResources: readOnlineResources,
103
105
  temporalExtents: readTemporalExtents,
106
+ spatialExtents: readSpatialExtents,
104
107
  // TODO
105
- spatialExtents: () => [],
106
108
  extras: () => undefined,
107
109
  landingPage: () => undefined,
108
110
  languages: () => [],
@@ -139,8 +141,8 @@ export class Iso19139Converter extends BaseConverter<string> {
139
141
  distributions: writeDistributions,
140
142
  onlineResources: writeOnlineResources,
141
143
  temporalExtents: writeTemporalExtents,
144
+ spatialExtents: () => writeSpatialExtents,
142
145
  // TODO
143
- spatialExtents: () => undefined,
144
146
  extras: () => undefined,
145
147
  landingPage: () => undefined,
146
148
  languages: () => undefined,
@@ -13,6 +13,8 @@ import {
13
13
  UpdateFrequency,
14
14
  UpdateFrequencyCustom,
15
15
  } from '../../../../../../libs/common/domain/src/lib/model/record'
16
+ import { ThesaurusModel } from '../../../../../../libs/common/domain/src/lib/model/thesaurus'
17
+ import { Geometry } from 'geojson'
16
18
  import { matchMimeType, matchProtocol } from '../common/distribution.mapper'
17
19
  import {
18
20
  ChainableFunction,
@@ -26,21 +28,22 @@ import {
26
28
  pipe,
27
29
  } from '../function-utils'
28
30
  import {
29
- XmlElement,
30
31
  findChildElement,
31
32
  findChildrenElement,
32
33
  findNestedElement,
33
34
  findNestedElements,
34
35
  findParent,
36
+ firstChildElement,
35
37
  readAttribute,
36
38
  readText,
39
+ XmlElement,
37
40
  } from '../xml-utils'
41
+ import { readGeometry } from './utils/geometry'
38
42
  import { fullNameToParts } from './utils/individual-name'
39
43
  import { getKeywordTypeFromKeywordTypeCode } from './utils/keyword.mapper'
40
44
  import { getRoleFromRoleCode } from './utils/role.mapper'
41
45
  import { getStatusFromStatusCode } from './utils/status.mapper'
42
46
  import { getUpdateFrequencyFromFrequencyCode } from './utils/update-frequency.mapper'
43
- import { ThesaurusModel } from '../../../../../../libs/common/domain/src/lib/model/thesaurus'
44
47
 
45
48
  export function extractCharacterString(): ChainableFunction<
46
49
  XmlElement,
@@ -66,6 +69,14 @@ export function extractDateTime(): ChainableFunction<XmlElement, Date> {
66
69
  )
67
70
  }
68
71
 
72
+ export function extractDecimal(): ChainableFunction<XmlElement, number> {
73
+ return pipe(
74
+ findChildElement('gco:Decimal', false),
75
+ readText(),
76
+ map((numberStr) => (numberStr ? Number(numberStr) : null))
77
+ )
78
+ }
79
+
69
80
  export function extractUrl(): ChainableFunction<XmlElement, URL> {
70
81
  const getUrl = pipe(findChildElement('gmd:URL', false), readText())
71
82
  const getCharacterString = pipe(
@@ -895,3 +906,62 @@ export function readTemporalExtents(rootEl: XmlElement) {
895
906
  })
896
907
  )(rootEl)
897
908
  }
909
+
910
+ export function readSpatialExtents(rootEl: XmlElement) {
911
+ const extractGeometry = (rootEl: XmlElement): Geometry => {
912
+ if (!rootEl) return null
913
+ return pipe(
914
+ findChildElement('gmd:polygon', false),
915
+ firstChildElement,
916
+ map((el) => readGeometry(el))
917
+ )(rootEl)
918
+ }
919
+
920
+ const extractBBox = (
921
+ rootEl: XmlElement
922
+ ): [number, number, number, number] => {
923
+ if (!rootEl) return null
924
+ return pipe(
925
+ combine(
926
+ pipe(findChildElement('gmd:westBoundLongitude'), extractDecimal()),
927
+ pipe(findChildElement('gmd:southBoundLatitude'), extractDecimal()),
928
+ pipe(findChildElement('gmd:eastBoundLongitude'), extractDecimal()),
929
+ pipe(findChildElement('gmd:northBoundLatitude'), extractDecimal())
930
+ )
931
+ )(rootEl)
932
+ }
933
+
934
+ const extractDescription = (rootEl: XmlElement): string => {
935
+ if (!rootEl) return null
936
+ return pipe(
937
+ findNestedElement(
938
+ 'gmd:geographicIdentifier',
939
+ 'gmd:MD_Identifier',
940
+ 'gmd:code'
941
+ ),
942
+ extractCharacterString()
943
+ )(rootEl)
944
+ }
945
+
946
+ return pipe(
947
+ findIdentification(),
948
+ findNestedElements('gmd:extent', 'gmd:EX_Extent', 'gmd:geographicElement'),
949
+ mapArray(
950
+ combine(
951
+ pipe(findChildElement('gmd:EX_BoundingPolygon'), extractGeometry),
952
+ pipe(findChildElement('gmd:EX_GeographicBoundingBox'), extractBBox),
953
+ pipe(
954
+ findChildElement('gmd:EX_GeographicDescription'),
955
+ extractDescription
956
+ )
957
+ )
958
+ ),
959
+ mapArray(([geometry, bbox, description]) => {
960
+ return {
961
+ ...(geometry && { geometry }),
962
+ ...(bbox && { bbox }),
963
+ ...(description && { description }),
964
+ }
965
+ })
966
+ )(rootEl)
967
+ }
@@ -0,0 +1,39 @@
1
+ import { XmlElement } from '@rgrove/parse-xml'
2
+ import { Geometry } from 'geojson'
3
+ import GML32 from 'ol/format/GML32'
4
+ import GeoJSON from 'ol/format/GeoJSON'
5
+ import { parse } from 'ol/xml'
6
+ import {
7
+ createDocument,
8
+ getRootElement,
9
+ parseXmlString,
10
+ xmlToString,
11
+ } from '../../xml-utils'
12
+
13
+ export function readGeometry(el: XmlElement): Geometry {
14
+ const xmlDoc = createDocument(el)
15
+ xmlDoc.root.attributes['xmlns'] = 'http://www.opengis.net/gml/3.2'
16
+ const gmlString = xmlToString(xmlDoc)
17
+ const doc = parse(gmlString)
18
+ // we need an intermediate node to be able to parse the GML
19
+ const node = document.createElement('pre')
20
+ node.appendChild(doc.documentElement)
21
+ const gml32Format = new GML32()
22
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
23
+ // @ts-ignore
24
+ const geometry = gml32Format.readGeometryFromNode(node)
25
+ const geojsonFormat = new GeoJSON()
26
+ return geojsonFormat.writeGeometryObject(geometry)
27
+ }
28
+
29
+ export function writeGeometry(geometryObject: Geometry): XmlElement {
30
+ const geojsonFormat = new GeoJSON()
31
+ const geometry = geojsonFormat.readGeometry(geometryObject)
32
+ const gml32Format = new GML32()
33
+ const node = gml32Format.writeGeometryNode(geometry)
34
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
35
+ // @ts-ignore
36
+ const element = node.firstElementChild as HTMLElement
37
+ const gmlString = new XMLSerializer().serializeToString(element)
38
+ return getRootElement(parseXmlString(gmlString))
39
+ }
@@ -14,7 +14,9 @@ import {
14
14
  UpdateFrequencyCode,
15
15
  UpdateFrequencyCustom,
16
16
  } from '../../../../../../libs/common/domain/src/lib/model/record'
17
+ import { ThesaurusModel } from '../../../../../../libs/common/domain/src/lib/model/thesaurus'
17
18
  import format from 'date-fns/format'
19
+ import { Geometry } from 'geojson'
18
20
  import {
19
21
  ChainableFunction,
20
22
  fallback,
@@ -44,8 +46,8 @@ import {
44
46
  setTextContent,
45
47
  } from '../xml-utils'
46
48
  import { readKind } from './read-parts'
49
+ import { writeGeometry } from './utils/geometry'
47
50
  import { namePartsToFull } from './utils/individual-name'
48
- import { ThesaurusModel } from '../../../../../../libs/common/domain/src/lib/model/thesaurus'
49
51
 
50
52
  export function writeCharacterString(
51
53
  text: string
@@ -101,6 +103,14 @@ export function writeDate(
101
103
  )
102
104
  }
103
105
 
106
+ export function writeDecimal(
107
+ decimal: number
108
+ ): ChainableFunction<XmlElement, XmlElement> {
109
+ return tap(
110
+ pipe(findChildOrCreate('gco:Decimal'), setTextContent(decimal.toString()))
111
+ )
112
+ }
113
+
104
114
  export function getProgressCode(status: RecordStatus): string {
105
115
  switch (status) {
106
116
  case 'completed':
@@ -1184,3 +1194,62 @@ export function writeTemporalExtents(
1184
1194
  )
1185
1195
  )(rootEl)
1186
1196
  }
1197
+
1198
+ export function writeSpatialExtents(record: DatasetRecord, rootEl: XmlElement) {
1199
+ const appendBoundingPolygon = (geometry?: Geometry) => {
1200
+ if (!geometry) return null
1201
+ return pipe(
1202
+ createElement('gmd:EX_BoundingPolygon'),
1203
+ appendChildren(
1204
+ pipe(
1205
+ createElement('gmd:polygon'),
1206
+ appendChildren(() => writeGeometry(geometry))
1207
+ )
1208
+ )
1209
+ )
1210
+ }
1211
+
1212
+ const appendGeographicBoundingBox = (
1213
+ bbox?: [number, number, number, number]
1214
+ ) => {
1215
+ if (!bbox) return null
1216
+ return pipe(
1217
+ createElement('gmd:EX_GeographicBoundingBox'),
1218
+ appendChildren(
1219
+ pipe(createElement('gmd:westBoundLongitude'), writeDecimal(bbox[0])),
1220
+ pipe(createElement('gmd:eastBoundLongitude'), writeDecimal(bbox[2])),
1221
+ pipe(createElement('gmd:southBoundLatitude'), writeDecimal(bbox[1])),
1222
+ pipe(createElement('gmd:northBoundLatitude'), writeDecimal(bbox[3]))
1223
+ )
1224
+ )
1225
+ }
1226
+
1227
+ const appendGeographicDescription = (description?: string) => {
1228
+ if (!description) return null
1229
+ return pipe(
1230
+ createElement('gmd:EX_GeographicDescription'),
1231
+ createChild('gmd:geographicIdentifier'),
1232
+ createChild('gmd:MD_Identifier'),
1233
+ createChild('gmd:code'),
1234
+ writeCharacterString(description)
1235
+ )
1236
+ }
1237
+
1238
+ pipe(
1239
+ findOrCreateIdentification(),
1240
+ findNestedChildOrCreate('gmd:extent', 'gmd:EX_Extent'),
1241
+ removeChildrenByName('gmd:geographicElement'),
1242
+ appendChildren(
1243
+ ...record.spatialExtents.map((extent) =>
1244
+ pipe(
1245
+ createElement('gmd:geographicElement'),
1246
+ appendChildren(
1247
+ appendBoundingPolygon(extent.geometry),
1248
+ appendGeographicBoundingBox(extent.bbox),
1249
+ appendGeographicDescription(extent.description)
1250
+ )
1251
+ )
1252
+ )
1253
+ )
1254
+ )(rootEl)
1255
+ }
@@ -6,6 +6,7 @@ import {
6
6
  XmlText,
7
7
  } from '@rgrove/parse-xml'
8
8
  import { ChainableFunction, fallback } from './function-utils'
9
+
9
10
  export { XmlDocument, XmlElement } from '@rgrove/parse-xml'
10
11
 
11
12
  export class XmlParseError extends Error {
@@ -115,6 +116,10 @@ export function allChildrenElement(element: XmlElement): Array<XmlElement> {
115
116
  ] as Array<XmlElement>
116
117
  }
117
118
 
119
+ export function firstChildElement(element: XmlElement): XmlElement {
120
+ return allChildrenElement(element)[0] ?? null
121
+ }
122
+
118
123
  /**
119
124
  * Will return all matching elements nested according to the given
120
125
  * names (similar to a path), starting form the input element;
@@ -175,11 +180,11 @@ export function findParent(
175
180
 
176
181
  export function readText(): ChainableFunction<XmlElement, string> {
177
182
  return (el) => {
178
- const textNode =
179
- el && Array.isArray(el.children)
180
- ? (el.children.find((node) => node.type === 'text') as XmlText)
181
- : null
182
- return textNode ? textNode.text : null
183
+ if (!el) return null
184
+ const textNode = Array.isArray(el.children)
185
+ ? (el.children.find((node) => node.type === 'text') as XmlText)
186
+ : null
187
+ return textNode ? textNode.text : ''
183
188
  }
184
189
  }
185
190
 
@@ -228,6 +233,7 @@ export function xmlToString(
228
233
  ${padding}<${el.name}${attrs}/>
229
234
  ${parentPadding}`
230
235
  }
236
+
231
237
  return `
232
238
  ${padding}<${el.name}${attrs}>${children}</${el.name}>
233
239
  ${parentPadding}`
@@ -307,11 +313,13 @@ function getTreeRoot(element: XmlElement): XmlElement {
307
313
 
308
314
  // stays on the parent element
309
315
  // if the given elements are part of a subtree, will add the root of subtree
316
+ // will filter out falsy elements
310
317
  export function appendChildren(
311
318
  ...childrenFns: Array<ChainableFunction<void, XmlElement>>
312
319
  ): ChainableFunction<XmlElement, XmlElement> {
313
320
  return (element) => {
314
321
  if (!element) return null
322
+ childrenFns = childrenFns.filter((fn) => fn)
315
323
  element.children.push(...childrenFns.map((fn) => fn()).map(getTreeRoot))
316
324
  element.children.forEach((el) => (el.parent = element))
317
325
  return element
@@ -9,6 +9,7 @@ import {
9
9
  from,
10
10
  Observable,
11
11
  of,
12
+ Subject,
12
13
  switchMap,
13
14
  throwError,
14
15
  } from 'rxjs'
@@ -32,8 +33,13 @@ import {
32
33
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
33
34
  import { HttpErrorResponse } from '@angular/common/http'
34
35
 
36
+ const TEMPORARY_ID_PREFIX = 'TEMP-ID-'
37
+
35
38
  @Injectable()
36
39
  export class Gn4Repository implements RecordsRepositoryInterface {
40
+ _draftsChanged = new Subject<void>()
41
+ draftsChanged$ = this._draftsChanged.asObservable()
42
+
37
43
  constructor(
38
44
  private gn4SearchApi: SearchApiService,
39
45
  private gn4SearchHelper: ElasticsearchService,
@@ -230,6 +236,26 @@ export class Gn4Repository implements RecordsRepositoryInterface {
230
236
  )
231
237
  }
232
238
 
239
+ openRecordForDuplication(
240
+ uniqueIdentifier: string
241
+ ): Observable<[CatalogRecord, string, false] | null> {
242
+ return this.loadRecordAsXml(uniqueIdentifier).pipe(
243
+ switchMap(async (recordAsXml) => {
244
+ const converter = findConverterForDocument(recordAsXml)
245
+ const record = await converter.readRecord(recordAsXml)
246
+ record.uniqueIdentifier = `${TEMPORARY_ID_PREFIX}${Date.now()}`
247
+ record.title = `${record.title} (Copy)`
248
+ const xml = await converter.writeRecord(record, recordAsXml)
249
+ window.localStorage.setItem(
250
+ this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
251
+ xml
252
+ )
253
+ this._draftsChanged.next()
254
+ return [record, xml, false] as [CatalogRecord, string, false]
255
+ })
256
+ )
257
+ }
258
+
233
259
  private serializeRecordToXml(
234
260
  record: CatalogRecord,
235
261
  referenceRecordSource?: string
@@ -275,17 +301,26 @@ export class Gn4Repository implements RecordsRepositoryInterface {
275
301
  )
276
302
  }
277
303
 
304
+ deleteRecord(uniqueIdentifier: string): Observable<void> {
305
+ return this.gn4RecordsApi.deleteRecord(uniqueIdentifier)
306
+ }
307
+
308
+ generateTemporaryId(): string {
309
+ return `${TEMPORARY_ID_PREFIX}${Date.now()}`
310
+ }
311
+
278
312
  saveRecordAsDraft(
279
313
  record: CatalogRecord,
280
314
  referenceRecordSource?: string
281
315
  ): Observable<string> {
282
316
  return this.serializeRecordToXml(record, referenceRecordSource).pipe(
283
- tap((recordXml) =>
317
+ tap((recordXml) => {
284
318
  window.localStorage.setItem(
285
319
  this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
286
320
  recordXml
287
321
  )
288
- )
322
+ this._draftsChanged.next()
323
+ })
289
324
  )
290
325
  }
291
326
 
@@ -293,6 +328,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
293
328
  window.localStorage.removeItem(
294
329
  this.getLocalStorageKeyForRecord(uniqueIdentifier)
295
330
  )
331
+ this._draftsChanged.next()
296
332
  }
297
333
 
298
334
  recordHasDraft(uniqueIdentifier: string): boolean {
@@ -303,6 +339,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
303
339
  )
304
340
  }
305
341
 
342
+ isRecordNotYetSaved(uniqueIdentifier: string): boolean {
343
+ return uniqueIdentifier.startsWith(TEMPORARY_ID_PREFIX)
344
+ }
345
+
306
346
  // generated by copilot
307
347
  getAllDrafts(): Observable<CatalogRecord[]> {
308
348
  const items = { ...window.localStorage }
@@ -1,4 +1,5 @@
1
1
  import { Organization } from './organization.model'
2
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
2
3
 
3
4
  export const RoleValues = [
4
5
  'unspecified',
@@ -24,6 +25,33 @@ export const RoleValues = [
24
25
  'user', // Party who uses the resource
25
26
  ]
26
27
 
28
+ export const RoleLabels = new Map<Role, string>([
29
+ ['unspecified', marker('domain.contact.role.unspecified')],
30
+ ['other', marker('domain.contact.role.other')],
31
+ ['author', marker('domain.contact.role.author')],
32
+ ['collaborator', marker('domain.contact.role.collaborator')],
33
+ ['contributor', marker('domain.contact.role.contributor')],
34
+ ['custodian', marker('domain.contact.role.custodian')],
35
+ ['distributor', marker('domain.contact.role.distributor')],
36
+ ['editor', marker('domain.contact.role.editor')],
37
+ ['funder', marker('domain.contact.role.funder')],
38
+ ['mediator', marker('domain.contact.role.mediator')],
39
+ ['originator', marker('domain.contact.role.originator')],
40
+ ['owner', marker('domain.contact.role.owner')],
41
+ ['point_of_contact', marker('domain.contact.role.point_of_contact')],
42
+ [
43
+ 'principal_investigator',
44
+ marker('domain.contact.role.principal_investigator'),
45
+ ],
46
+ ['processor', marker('domain.contact.role.processor')],
47
+ ['publisher', marker('domain.contact.role.publisher')],
48
+ ['resource_provider', marker('domain.contact.role.resource_provider')],
49
+ ['rights_holder', marker('domain.contact.role.rights_holder')],
50
+ ['sponsor', marker('domain.contact.role.sponsor')],
51
+ ['stakeholder', marker('domain.contact.role.stakeholder')],
52
+ ['user', marker('domain.contact.role.user')],
53
+ ])
54
+
27
55
  export type Role = typeof RoleValues[number]
28
56
 
29
57
  export interface Individual {
@@ -159,7 +159,8 @@ export interface GraphicOverview {
159
159
  }
160
160
 
161
161
  export interface DatasetSpatialExtent {
162
- geometry: Geometry
162
+ bbox?: [number, number, number, number]
163
+ geometry?: Geometry
163
164
  description?: string
164
165
  }
165
166
 
@@ -30,6 +30,18 @@ export abstract class RecordsRepositoryInterface {
30
30
  uniqueIdentifier: string
31
31
  ): Observable<[CatalogRecord, string, boolean] | null>
32
32
 
33
+ /**
34
+ * This emits once:
35
+ * - record object with a new unique identifier and suffixed title
36
+ * - serialized representation of the record as text
37
+ * - false, as the duplicated record is always a draft
38
+ * @param uniqueIdentifier
39
+ * @returns Observable<[CatalogRecord, string, false] | null>
40
+ */
41
+ abstract openRecordForDuplication(
42
+ uniqueIdentifier: string
43
+ ): Observable<[CatalogRecord, string, false] | null>
44
+
33
45
  /**
34
46
  * @param record
35
47
  * @param referenceRecordSource
@@ -40,6 +52,14 @@ export abstract class RecordsRepositoryInterface {
40
52
  referenceRecordSource?: string
41
53
  ): Observable<string>
42
54
 
55
+ /**
56
+ * @param uniqueIdentifier
57
+ * @returns Observable<void> Returns when record is deleted
58
+ */
59
+ abstract deleteRecord(uniqueIdentifier: string): Observable<void>
60
+
61
+ abstract generateTemporaryId(): string
62
+
43
63
  /**
44
64
  * @param record
45
65
  * @param referenceRecordSource
@@ -52,7 +72,9 @@ export abstract class RecordsRepositoryInterface {
52
72
 
53
73
  abstract clearRecordDraft(uniqueIdentifier: string): void
54
74
  abstract recordHasDraft(uniqueIdentifier: string): boolean
75
+ abstract isRecordNotYetSaved(uniqueIdentifier: string): boolean
55
76
 
56
77
  /** will return all pending drafts, both published and not published */
57
78
  abstract getAllDrafts(): Observable<CatalogRecord[]>
79
+ abstract draftsChanged$: Observable<void>
58
80
  }
@@ -7201,11 +7201,7 @@ export class RecordsApiService {
7201
7201
  }
7202
7202
 
7203
7203
  // to determine the Content-Type header
7204
- const consumes: string[] = [
7205
- 'application/xml',
7206
- 'application/json',
7207
- 'application/x-www-form-urlencoded',
7208
- ]
7204
+ const consumes: string[] = ['application/xml']
7209
7205
  const httpContentTypeSelected: string | undefined =
7210
7206
  this.configuration.selectHeaderContentType(consumes)
7211
7207
  if (httpContentTypeSelected !== undefined) {
@@ -1832,14 +1832,6 @@ paths:
1832
1832
  schema:
1833
1833
  type: string
1834
1834
  description: XML fragment.
1835
- application/json:
1836
- schema:
1837
- type: string
1838
- description: XML fragment.
1839
- application/x-www-form-urlencoded:
1840
- schema:
1841
- type: string
1842
- description: XML fragment.
1843
1835
  responses:
1844
1836
  default:
1845
1837
  description: default response
@@ -0,0 +1,25 @@
1
+ <div class="flex flex-row gap-4 items-center">
2
+ <div class="flex flex-row border border-gray-200 rounded-xl p-4 gap-4 w-full">
3
+ <gn-ui-thumbnail
4
+ class="w-[56px] h-[56px] rounded-[4px]"
5
+ [thumbnailUrl]="contact.organization.logoUrl?.href"
6
+ [fit]="'contain'"
7
+ ></gn-ui-thumbnail>
8
+ <div class="flex flex-col w-full">
9
+ <div class="flex flex-row justify-between">
10
+ <span class="flex flex-wrap font-bold w-full"
11
+ >{{ contact.firstName }} {{ contact.lastName }}</span
12
+ >
13
+ </div>
14
+ <div>{{ contact.email }}</div>
15
+ </div>
16
+ </div>
17
+ <gn-ui-button
18
+ *ngIf="removable"
19
+ data-test="removeContactButton"
20
+ type="light"
21
+ extraClass="w-[20px] h-[20px] flex items-center justify-center"
22
+ (buttonClick)="removeContact(contact)"
23
+ ><span class="material-symbols-outlined"> close </span>
24
+ </gn-ui-button>
25
+ </div>
@@ -0,0 +1,30 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ } from '@angular/core'
8
+ import { Individual } from '../../../../../../../libs/common/domain/src/lib/model/record'
9
+ import { MatIconModule } from '@angular/material/icon'
10
+ import { CommonModule } from '@angular/common'
11
+ import { ButtonComponent } from '../../../../../../../libs/ui/inputs/src'
12
+ import { ThumbnailComponent } from '../../../../../../../libs/ui/elements/src'
13
+
14
+ @Component({
15
+ selector: 'gn-ui-contact-card',
16
+ templateUrl: './contact-card.component.html',
17
+ styleUrls: ['./contact-card.component.css'],
18
+ changeDetection: ChangeDetectionStrategy.OnPush,
19
+ standalone: true,
20
+ imports: [CommonModule, MatIconModule, ButtonComponent, ThumbnailComponent],
21
+ })
22
+ export class ContactCardComponent {
23
+ @Input() contact: Individual
24
+ @Input() removable = true
25
+ @Output() contactRemoved = new EventEmitter<Individual>()
26
+
27
+ removeContact(contact: Individual) {
28
+ this.contactRemoved.emit(contact)
29
+ }
30
+ }
@@ -1,8 +1,9 @@
1
1
  <gn-ui-image-input
2
2
  [maxSizeMB]="5"
3
3
  [previewUrl]="resourceUrl"
4
- [altText]="resourceFileName"
4
+ [altText]="resourceAltText"
5
5
  (fileChange)="handleFileChange($event)"
6
6
  (urlChange)="handleUrlChange($event)"
7
+ (altTextChange)="handleAltTextChange($event)"
7
8
  (delete)="handleDelete()"
8
9
  ></gn-ui-image-input>