geonetwork-ui 2.4.0-dev.71a43b38 → 2.4.0-dev.8118addf

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 (179) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +2 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/gn4/atomic-operations.mjs +2 -1
  3. package/esm2022/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.mjs +1 -1
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +5 -5
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +30 -2
  6. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.mjs +31 -0
  7. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +23 -1
  8. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +6 -1
  9. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +12 -1
  10. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  11. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  12. package/esm2022/libs/data-access/gn4/src/openapi/api/records.api.service.mjs +2 -6
  13. package/esm2022/libs/feature/editor/src/lib/+state/editor.actions.mjs +2 -1
  14. package/esm2022/libs/feature/editor/src/lib/+state/editor.effects.mjs +3 -3
  15. package/esm2022/libs/feature/editor/src/lib/+state/editor.facade.mjs +7 -2
  16. package/esm2022/libs/feature/editor/src/lib/+state/editor.models.mjs +1 -1
  17. package/esm2022/libs/feature/editor/src/lib/+state/editor.reducer.mjs +7 -3
  18. package/esm2022/libs/feature/editor/src/lib/+state/editor.selectors.mjs +16 -6
  19. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +3 -3
  20. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/index.mjs +1 -2
  21. package/esm2022/libs/feature/editor/src/lib/components/record-form/record-form.component.mjs +11 -7
  22. package/esm2022/libs/feature/editor/src/lib/expressions.mjs +1 -1
  23. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +143 -60
  24. package/esm2022/libs/feature/editor/src/lib/models/editor-config.model.mjs +2 -0
  25. package/esm2022/libs/feature/editor/src/lib/models/index.mjs +2 -1
  26. package/esm2022/libs/feature/editor/src/lib/services/editor.service.mjs +4 -3
  27. package/esm2022/libs/feature/map/src/lib/utils/map-utils.service.mjs +3 -4
  28. package/esm2022/libs/feature/search/src/lib/results-table/results-table-container.component.mjs +9 -3
  29. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +5 -11
  30. package/esm2022/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.mjs +3 -3
  31. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +5 -2
  32. package/esm2022/libs/ui/elements/src/lib/user-preview/user-preview.component.mjs +3 -3
  33. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +29 -0
  34. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +15 -7
  35. package/esm2022/libs/ui/widgets/src/index.mjs +2 -1
  36. package/esm2022/libs/ui/widgets/src/lib/popover/popover.component.mjs +68 -0
  37. package/esm2022/translations/de.json +31 -1
  38. package/esm2022/translations/en.json +31 -1
  39. package/esm2022/translations/es.json +30 -0
  40. package/esm2022/translations/fr.json +31 -1
  41. package/esm2022/translations/it.json +31 -1
  42. package/esm2022/translations/nl.json +30 -0
  43. package/esm2022/translations/pt.json +30 -0
  44. package/fesm2022/geonetwork-ui.mjs +617 -117
  45. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  46. package/libs/api/metadata-converter/src/index.d.ts +1 -0
  47. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  48. package/libs/api/metadata-converter/src/lib/gn4/atomic-operations.d.ts.map +1 -1
  49. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts +1 -0
  50. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts.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 +1 -0
  61. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  62. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +2 -1
  63. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  64. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +9 -0
  65. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  66. package/libs/data-access/gn4/src/openapi/api/records.api.service.d.ts.map +1 -1
  67. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts +5 -0
  68. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts.map +1 -1
  69. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts +4 -4
  70. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts.map +1 -1
  71. package/libs/feature/editor/src/lib/+state/editor.models.d.ts +8 -0
  72. package/libs/feature/editor/src/lib/+state/editor.models.d.ts.map +1 -1
  73. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts +4 -3
  74. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts.map +1 -1
  75. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts +4 -8
  76. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts.map +1 -1
  77. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +1 -1
  78. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  79. package/libs/feature/editor/src/lib/components/record-form/form-field/index.d.ts +0 -1
  80. package/libs/feature/editor/src/lib/components/record-form/form-field/index.d.ts.map +1 -1
  81. package/libs/feature/editor/src/lib/components/record-form/record-form.component.d.ts +5 -7
  82. package/libs/feature/editor/src/lib/components/record-form/record-form.component.d.ts.map +1 -1
  83. package/libs/feature/editor/src/lib/expressions.d.ts +1 -1
  84. package/libs/feature/editor/src/lib/expressions.d.ts.map +1 -1
  85. package/libs/feature/editor/src/lib/fields.config.d.ts +38 -2
  86. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  87. package/libs/feature/editor/src/lib/models/editor-config.model.d.ts +36 -0
  88. package/libs/feature/editor/src/lib/models/editor-config.model.d.ts.map +1 -0
  89. package/libs/feature/editor/src/lib/models/index.d.ts +1 -0
  90. package/libs/feature/editor/src/lib/models/index.d.ts.map +1 -1
  91. package/libs/feature/editor/src/lib/services/editor.service.d.ts +2 -2
  92. package/libs/feature/editor/src/lib/services/editor.service.d.ts.map +1 -1
  93. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts.map +1 -1
  94. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +3 -1
  95. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  96. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  97. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts +0 -3
  98. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  99. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +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/search/src/lib/results-table/action-menu/action-menu.component.d.ts +11 -0
  103. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -0
  104. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +4 -2
  105. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  106. package/libs/ui/widgets/src/index.d.ts +1 -0
  107. package/libs/ui/widgets/src/index.d.ts.map +1 -1
  108. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts +19 -0
  109. package/libs/ui/widgets/src/lib/popover/popover.component.d.ts.map +1 -0
  110. package/package.json +1 -1
  111. package/src/libs/api/metadata-converter/src/index.ts +1 -0
  112. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +5 -1
  113. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +37 -12
  114. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -1
  115. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +1 -0
  116. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  117. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +4 -2
  118. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +72 -2
  119. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.ts +39 -0
  120. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +70 -1
  121. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +8 -0
  122. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +20 -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 +12 -0
  125. package/src/libs/common/fixtures/src/index.ts +2 -0
  126. package/src/libs/common/fixtures/src/lib/editor/editor.fixtures.ts +156 -0
  127. package/src/libs/common/fixtures/src/lib/editor/index.ts +1 -0
  128. package/src/libs/data-access/gn4/src/openapi/api/records.api.service.ts +1 -5
  129. package/src/libs/data-access/gn4/src/spec.yaml +0 -8
  130. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +5 -0
  131. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +2 -2
  132. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +9 -1
  133. package/src/libs/feature/editor/src/lib/+state/editor.models.ts +11 -0
  134. package/src/libs/feature/editor/src/lib/+state/editor.reducer.ts +11 -5
  135. package/src/libs/feature/editor/src/lib/+state/editor.selectors.ts +22 -8
  136. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +9 -9
  137. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +2 -1
  138. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +0 -1
  139. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +51 -11
  140. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +15 -8
  141. package/src/libs/feature/editor/src/lib/expressions.ts +1 -1
  142. package/src/libs/feature/editor/src/lib/fields.config.ts +175 -61
  143. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +53 -0
  144. package/src/libs/feature/editor/src/lib/models/index.ts +1 -0
  145. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -4
  146. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +2 -3
  147. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +1 -0
  148. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +5 -0
  149. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.html +14 -20
  150. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +0 -10
  151. package/src/libs/ui/elements/src/lib/metadata-quality-item/metadata-quality-item.component.html +1 -1
  152. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -1
  153. package/src/libs/ui/elements/src/lib/user-preview/user-preview.component.html +1 -1
  154. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.css +0 -0
  155. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +17 -0
  156. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +22 -0
  157. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +9 -0
  158. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +26 -12
  159. package/src/libs/ui/widgets/src/index.ts +1 -0
  160. package/src/libs/ui/widgets/src/lib/popover/popover.component.css +0 -0
  161. package/src/libs/ui/widgets/src/lib/popover/popover.component.html +3 -0
  162. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +85 -0
  163. package/tailwind.base.css +2 -1
  164. package/translations/de.json +31 -1
  165. package/translations/en.json +31 -1
  166. package/translations/es.json +30 -0
  167. package/translations/fr.json +31 -1
  168. package/translations/it.json +31 -1
  169. package/translations/nl.json +30 -0
  170. package/translations/pt.json +30 -0
  171. package/translations/sk.json +31 -1
  172. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.mjs +0 -2
  173. package/esm2022/libs/feature/editor/src/lib/models/fields.model.mjs +0 -2
  174. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.d.ts +0 -26
  175. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.d.ts.map +0 -1
  176. package/libs/feature/editor/src/lib/models/fields.model.d.ts +0 -15
  177. package/libs/feature/editor/src/lib/models/fields.model.d.ts.map +0 -1
  178. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.ts +0 -43
  179. package/src/libs/feature/editor/src/lib/models/fields.model.ts +0 -29
@@ -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;
@@ -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
@@ -24,6 +24,7 @@ import {
24
24
  } from '../../../../../../libs/common/domain/src/lib/model/search'
25
25
  import { catchError, map, tap } from 'rxjs/operators'
26
26
  import {
27
+ BaseConverter,
27
28
  findConverterForDocument,
28
29
  Gn4Converter,
29
30
  Gn4SearchResults,
@@ -230,6 +231,25 @@ export class Gn4Repository implements RecordsRepositoryInterface {
230
231
  )
231
232
  }
232
233
 
234
+ openRecordForDuplication(
235
+ uniqueIdentifier: string
236
+ ): Observable<[CatalogRecord, string, false] | null> {
237
+ return this.loadRecordAsXml(uniqueIdentifier).pipe(
238
+ switchMap(async (recordAsXml) => {
239
+ const converter = findConverterForDocument(recordAsXml)
240
+ const record = await converter.readRecord(recordAsXml)
241
+ record.uniqueIdentifier = `TEMP-ID-${Date.now()}`
242
+ record.title = `${record.title} (Copy)`
243
+ const xml = await converter.writeRecord(record, recordAsXml)
244
+ window.localStorage.setItem(
245
+ this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
246
+ xml
247
+ )
248
+ return [record, xml, false] as [CatalogRecord, string, false]
249
+ })
250
+ )
251
+ }
252
+
233
253
  private serializeRecordToXml(
234
254
  record: CatalogRecord,
235
255
  referenceRecordSource?: string
@@ -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
@@ -11,3 +11,5 @@ export * from './lib/records.fixtures'
11
11
  export * from './lib/repository.fixtures'
12
12
  export * from './lib/user.fixtures'
13
13
  export * from './lib/user-feedbacks.fixtures'
14
+
15
+ export * from './lib/editor'
@@ -0,0 +1,156 @@
1
+ export const EDITOR_CONFIG = () => ({
2
+ pages: [
3
+ {
4
+ labelKey: 'Resource description',
5
+ sections: [EDITOR_SECTION_ABOUT()],
6
+ },
7
+ {
8
+ labelKey: 'Resources',
9
+ sections: [EDITOR_SECTION_CLASSIFICATION()],
10
+ },
11
+ {
12
+ labelKey: 'Access and contact',
13
+ sections: [
14
+ EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS(),
15
+ EDITOR_SECTION_DATA_MANAGER(),
16
+ ],
17
+ },
18
+ ],
19
+ })
20
+
21
+ export const EDITOR_SECTION_ABOUT = () => ({
22
+ labelKey: 'About the resource',
23
+ descriptionKey: 'This section describes the resource.',
24
+ hidden: false,
25
+ fields: [
26
+ EDITOR_FIELD_TITLE(),
27
+ EDITOR_FIELD_ABSTRACT(),
28
+ EDITOR_FIELD_RESOURCE_UPDATED(),
29
+ EDITOR_FIELD_RECORD_UPDATED(),
30
+ EDITOR_FIELD_UPDATE_FREQUENCY(),
31
+ EDITOR_FIELD_TEMPORAL_EXTENTS(),
32
+ ],
33
+ })
34
+
35
+ export const EDITOR_SECTION_DATA_MANAGER = () => ({
36
+ labelKey: 'Data manager',
37
+ descriptionKey: '',
38
+ hidden: false,
39
+ fields: [],
40
+ })
41
+
42
+ export const EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS = () => ({
43
+ labelKey: 'Data manager',
44
+ descriptionKey: '',
45
+ hidden: false,
46
+ fields: [EDITOR_FIELD_LICENSE()],
47
+ })
48
+
49
+ export const EDITOR_SECTION_CLASSIFICATION = () => ({
50
+ labelKey: 'Classification',
51
+ descriptionKey: 'The classification has an impact on the access to the data.',
52
+ hidden: false,
53
+ fields: [EDITOR_FIELD_KEYWORDS(), EDITOR_FIELD_UNIQUE_IDENTIFIER()],
54
+ })
55
+
56
+ export const EDITOR_FIELD_TITLE = () => ({
57
+ model: 'title',
58
+ hidden: false,
59
+ value: 'Accroches vélos MEL',
60
+ formFieldConfig: {
61
+ labelKey: 'editor.record.form.field.title',
62
+ },
63
+ })
64
+
65
+ export const EDITOR_FIELD_ABSTRACT = () => ({
66
+ model: 'abstract',
67
+ hidden: false,
68
+ value: 'Abstract',
69
+ formFieldConfig: {
70
+ labelKey: 'editor.record.form.field.abstract',
71
+ },
72
+ })
73
+
74
+ export const EDITOR_FIELD_RESOURCE_UPDATED = () => ({
75
+ model: 'resourceUpdated',
76
+ hidden: false,
77
+ formFieldConfig: {
78
+ labelKey: 'editor.record.form.field.resourceUpdated',
79
+ },
80
+ })
81
+
82
+ export const EDITOR_FIELD_RECORD_UPDATED = () => ({
83
+ model: 'recordUpdated',
84
+ hidden: false,
85
+ formFieldConfig: {
86
+ labelKey: 'editor.record.form.field.recordUpdated',
87
+ locked: true,
88
+ },
89
+ value: '2024-07-16T05:18:53.000Z',
90
+ onSaveProcess: '${dateNow()}',
91
+ })
92
+
93
+ export const EDITOR_FIELD_UPDATE_FREQUENCY = () => ({
94
+ model: 'updateFrequency',
95
+ hidden: false,
96
+ formFieldConfig: {
97
+ labelKey: 'editor.record.form.field.updateFrequency',
98
+ },
99
+ value: 'unknown',
100
+ })
101
+
102
+ export const EDITOR_FIELD_TEMPORAL_EXTENTS = () => ({
103
+ model: 'temporalExtents',
104
+ hidden: false,
105
+ formFieldConfig: {
106
+ labelKey: 'editor.record.form.field.temporalExtents',
107
+ },
108
+ value: [],
109
+ })
110
+
111
+ export const EDITOR_FIELD_SPATIAL_EXTENTS = () => ({
112
+ model: 'spatialExtents',
113
+ hidden: false,
114
+ formFieldConfig: {
115
+ labelKey: 'editor.record.form.field.spatialExtents',
116
+ },
117
+ })
118
+
119
+ export const EDITOR_FIELD_KEYWORDS = () => ({
120
+ model: 'keywords',
121
+ hidden: false,
122
+ formFieldConfig: {
123
+ labelKey: 'editor.record.form.field.keywords',
124
+ },
125
+ })
126
+
127
+ export const EDITOR_FIELD_UNIQUE_IDENTIFIER = () => ({
128
+ model: 'uniqueIdentifier',
129
+ hidden: false,
130
+ formFieldConfig: {
131
+ labelKey: 'editor.record.form.field.uniqueIdentifier',
132
+ locked: true,
133
+ },
134
+ value: 'accroche_velos',
135
+ })
136
+
137
+ export const EDITOR_FIELD_LICENSE = () => ({
138
+ model: 'licenses',
139
+ hidden: false,
140
+ formFieldConfig: {
141
+ labelKey: 'editor.record.form.field.license',
142
+ locked: true,
143
+ },
144
+ })
145
+
146
+ export const EDITOR_FIELDS = () => [
147
+ EDITOR_FIELD_TITLE(),
148
+ EDITOR_FIELD_ABSTRACT(),
149
+ EDITOR_FIELD_RESOURCE_UPDATED(),
150
+ EDITOR_FIELD_RECORD_UPDATED(),
151
+ EDITOR_FIELD_UPDATE_FREQUENCY(),
152
+ EDITOR_FIELD_TEMPORAL_EXTENTS(),
153
+ EDITOR_FIELD_SPATIAL_EXTENTS(),
154
+ EDITOR_FIELD_KEYWORDS(),
155
+ EDITOR_FIELD_UNIQUE_IDENTIFIER(),
156
+ ]
@@ -0,0 +1 @@
1
+ export * from './editor.fixtures'
@@ -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
@@ -28,3 +28,8 @@ export const saveRecordFailure = createAction(
28
28
  )
29
29
 
30
30
  export const draftSaveSuccess = createAction('[Editor] Draft save success')
31
+
32
+ export const setCurrentPage = createAction(
33
+ '[Editor] Set current page',
34
+ props<{ page: number }>()
35
+ )
@@ -6,9 +6,9 @@ import * as EditorActions from './editor.actions'
6
6
  import { EditorService } from '../services/editor.service'
7
7
  import { Store } from '@ngrx/store'
8
8
  import {
9
+ selectEditorConfig,
9
10
  selectRecord,
10
11
  selectRecordAlreadySavedOnce,
11
- selectRecordFieldsConfig,
12
12
  } from './editor.selectors'
13
13
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
14
14
 
@@ -24,7 +24,7 @@ export class EditorEffects {
24
24
  ofType(EditorActions.saveRecord),
25
25
  withLatestFrom(
26
26
  this.store.select(selectRecord),
27
- this.store.select(selectRecordFieldsConfig),
27
+ this.store.select(selectEditorConfig),
28
28
  this.store.select(selectRecordAlreadySavedOnce)
29
29
  ),
30
30
  switchMap(([, record, fieldsConfig, alreadySavedOnce]) =>
@@ -25,8 +25,12 @@ export class EditorFacade {
25
25
  changedSinceSave$ = this.store.pipe(
26
26
  select(EditorSelectors.selectRecordChangedSinceSave)
27
27
  )
28
- recordFields$ = this.store.pipe(select(EditorSelectors.selectRecordFields))
28
+ currentSections$ = this.store.pipe(
29
+ select(EditorSelectors.selectRecordSections)
30
+ )
29
31
  draftSaveSuccess$ = this.actions$.pipe(ofType(EditorActions.draftSaveSuccess))
32
+ currentPage$ = this.store.pipe(select(EditorSelectors.selectCurrentPage))
33
+ editorConfig$ = this.store.pipe(select(EditorSelectors.selectEditorConfig))
30
34
 
31
35
  openRecord(
32
36
  record: CatalogRecord,
@@ -45,4 +49,8 @@ export class EditorFacade {
45
49
  updateRecordField(field: string, value: unknown) {
46
50
  this.store.dispatch(EditorActions.updateRecordField({ field, value }))
47
51
  }
52
+
53
+ setCurrentPage(page: number) {
54
+ this.store.dispatch(EditorActions.setCurrentPage({ page }))
55
+ }
48
56
  }
@@ -1 +1,12 @@
1
+ import { EditorField, EditorFieldValue, EditorSection } from '../models'
2
+
1
3
  export type SaveRecordError = string
4
+
5
+ export interface EditorFieldWithValue {
6
+ config: EditorField
7
+ value: EditorFieldValue
8
+ }
9
+
10
+ export type EditorSectionWithValues = EditorSection & {
11
+ fieldsWithValues: EditorFieldWithValue[]
12
+ }
@@ -2,8 +2,8 @@ import { Action, createReducer, on } from '@ngrx/store'
2
2
  import * as EditorActions from './editor.actions'
3
3
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
4
4
  import { SaveRecordError } from './editor.models'
5
- import { EditorFieldsConfig } from '../models/fields.model'
6
- import { DEFAULT_FIELDS } from '../fields.config'
5
+ import { EditorConfig } from '../models'
6
+ import { DEFAULT_CONFIGURATION } from '../fields.config'
7
7
 
8
8
  export const EDITOR_FEATURE_KEY = 'editor'
9
9
 
@@ -13,7 +13,7 @@ export const EDITOR_FEATURE_KEY = 'editor'
13
13
  * @property saving
14
14
  * @property saveError
15
15
  * @property changedSinceSave
16
- * @property fieldsConfig Configuration for the fields in the editor
16
+ * @property editorConfig Configuration for the fields in the editor
17
17
  */
18
18
  export interface EditorState {
19
19
  record: CatalogRecord | null
@@ -22,7 +22,8 @@ export interface EditorState {
22
22
  saving: boolean
23
23
  saveError: SaveRecordError | null
24
24
  changedSinceSave: boolean
25
- fieldsConfig: EditorFieldsConfig
25
+ editorConfig: EditorConfig
26
+ currentPage: number
26
27
  }
27
28
 
28
29
  export interface EditorPartialState {
@@ -36,7 +37,8 @@ export const initialEditorState: EditorState = {
36
37
  saving: false,
37
38
  saveError: null,
38
39
  changedSinceSave: false,
39
- fieldsConfig: DEFAULT_FIELDS,
40
+ editorConfig: DEFAULT_CONFIGURATION,
41
+ currentPage: 0,
40
42
  }
41
43
 
42
44
  const reducer = createReducer(
@@ -77,6 +79,10 @@ const reducer = createReducer(
77
79
  on(EditorActions.markRecordAsChanged, (state) => ({
78
80
  ...state,
79
81
  changedSinceSave: true,
82
+ })),
83
+ on(EditorActions.setCurrentPage, (state, { page }) => ({
84
+ ...state,
85
+ currentPage: page,
80
86
  }))
81
87
  )
82
88