geonetwork-ui 2.4.0-dev.aa5f997e → 2.4.0-dev.b0bcda82

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 (161) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +1 -2
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +5 -5
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +30 -2
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.mjs +31 -0
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +23 -1
  6. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +10 -3
  7. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +21 -4
  8. package/esm2022/libs/common/domain/src/lib/model/record/contact.model.mjs +28 -1
  9. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  10. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  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 +40 -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/sortable-list/sortable-list.component.mjs +7 -3
  31. package/esm2022/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.mjs +19 -5
  32. package/esm2022/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.mjs +3 -3
  33. package/esm2022/libs/ui/layout/src/lib/interactive-table/interactive-table.component.mjs +3 -3
  34. package/esm2022/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.mjs +49 -11
  35. package/esm2022/libs/ui/search/src/lib/results-table/results-table.component.mjs +18 -6
  36. package/esm2022/translations/de.json +41 -16
  37. package/esm2022/translations/en.json +34 -9
  38. package/esm2022/translations/es.json +34 -9
  39. package/esm2022/translations/fr.json +44 -19
  40. package/esm2022/translations/it.json +34 -9
  41. package/esm2022/translations/nl.json +34 -9
  42. package/esm2022/translations/pt.json +34 -9
  43. package/fesm2022/geonetwork-ui.mjs +1134 -263
  44. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  45. package/libs/api/metadata-converter/src/index.d.ts +0 -1
  46. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  47. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -1
  48. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +8 -1
  49. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  50. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts +5 -0
  51. package/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.d.ts.map +1 -0
  52. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +3 -1
  53. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  54. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts +1 -0
  55. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts.map +1 -1
  56. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +6 -1
  57. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  58. package/libs/common/domain/src/lib/model/record/contact.model.d.ts +1 -0
  59. package/libs/common/domain/src/lib/model/record/contact.model.d.ts.map +1 -1
  60. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +2 -1
  61. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  62. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +8 -0
  63. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  64. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts +12 -0
  65. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts.map +1 -0
  66. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts +27 -0
  67. package/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.d.ts.map +1 -0
  68. 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
  69. 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
  70. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.d.ts +17 -0
  71. 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
  72. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts +11 -0
  73. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.d.ts.map +1 -0
  74. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.d.ts +3 -1
  75. 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
  76. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +9 -1
  77. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  78. package/libs/feature/editor/src/lib/fields.config.d.ts +7 -0
  79. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  80. package/libs/feature/map/src/lib/utils/map-utils.service.d.ts.map +1 -1
  81. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts +11 -3
  82. package/libs/feature/search/src/lib/results-table/results-table-container.component.d.ts.map +1 -1
  83. package/libs/feature/search/src/lib/state/search.facade.d.ts +1 -0
  84. package/libs/feature/search/src/lib/state/search.facade.d.ts.map +1 -1
  85. package/libs/ui/elements/src/index.d.ts +1 -0
  86. package/libs/ui/elements/src/index.d.ts.map +1 -1
  87. package/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.d.ts +18 -0
  88. package/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.d.ts.map +1 -0
  89. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  90. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +1 -1
  91. package/libs/ui/elements/src/lib/sortable-list/sortable-list.component.d.ts +4 -4
  92. package/libs/ui/elements/src/lib/sortable-list/sortable-list.component.d.ts.map +1 -1
  93. package/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.d.ts +9 -1
  94. package/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.d.ts.map +1 -1
  95. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts +10 -1
  96. package/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.d.ts.map +1 -1
  97. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +6 -2
  98. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  99. package/package.json +1 -1
  100. package/src/libs/api/metadata-converter/src/index.ts +0 -1
  101. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +5 -1
  102. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +37 -12
  103. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -1
  104. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +4 -2
  105. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +72 -2
  106. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/geometry.ts +39 -0
  107. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +70 -1
  108. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +13 -5
  109. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +24 -4
  110. package/src/libs/common/domain/src/lib/model/record/contact.model.ts +28 -0
  111. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -1
  112. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +10 -0
  113. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.css +0 -0
  114. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html +25 -0
  115. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts +30 -0
  116. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html +2 -1
  117. package/src/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts +110 -19
  118. 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
  119. 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
  120. 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
  121. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +1 -1
  122. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.css +0 -0
  123. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.html +6 -0
  124. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-open-data/form-field-open-data.component.ts +59 -0
  125. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.css +0 -0
  126. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html +5 -0
  127. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +22 -0
  128. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.ts +8 -7
  129. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +18 -1
  130. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +30 -1
  131. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -1
  132. package/src/libs/feature/editor/src/lib/fields.config.ts +32 -2
  133. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +8 -4
  134. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html +1 -1
  135. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.html +2 -1
  136. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +52 -3
  137. package/src/libs/feature/search/src/lib/state/search.facade.ts +6 -0
  138. package/src/libs/ui/elements/src/index.ts +1 -0
  139. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.css +0 -0
  140. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.html +12 -0
  141. package/src/libs/ui/elements/src/lib/confirmation-dialog/confirmation-dialog.component.ts +37 -0
  142. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.html +4 -1
  143. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.css +2 -1
  144. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +12 -8
  145. package/src/libs/ui/elements/src/lib/sortable-list/sortable-list.component.html +3 -1
  146. package/src/libs/ui/elements/src/lib/sortable-list/sortable-list.component.ts +8 -4
  147. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +15 -3
  148. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +1 -1
  149. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -0
  150. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +10 -1
  151. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +55 -3
  152. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +7 -2
  153. package/src/libs/ui/search/src/lib/results-table/results-table.component.ts +9 -9
  154. package/translations/de.json +41 -16
  155. package/translations/en.json +34 -9
  156. package/translations/es.json +34 -9
  157. package/translations/fr.json +44 -19
  158. package/translations/it.json +34 -9
  159. package/translations/nl.json +34 -9
  160. package/translations/pt.json +34 -9
  161. package/translations/sk.json +34 -9
@@ -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'
@@ -24,7 +25,6 @@ import {
24
25
  } from '../../../../../../libs/common/domain/src/lib/model/search'
25
26
  import { catchError, map, tap } from 'rxjs/operators'
26
27
  import {
27
- BaseConverter,
28
28
  findConverterForDocument,
29
29
  Gn4Converter,
30
30
  Gn4SearchResults,
@@ -33,8 +33,13 @@ import {
33
33
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
34
34
  import { HttpErrorResponse } from '@angular/common/http'
35
35
 
36
+ const TEMPORARY_ID_PREFIX = 'TEMP-ID-'
37
+
36
38
  @Injectable()
37
39
  export class Gn4Repository implements RecordsRepositoryInterface {
40
+ _draftsChanged = new Subject<void>()
41
+ draftsChanged$ = this._draftsChanged.asObservable()
42
+
38
43
  constructor(
39
44
  private gn4SearchApi: SearchApiService,
40
45
  private gn4SearchHelper: ElasticsearchService,
@@ -238,13 +243,14 @@ export class Gn4Repository implements RecordsRepositoryInterface {
238
243
  switchMap(async (recordAsXml) => {
239
244
  const converter = findConverterForDocument(recordAsXml)
240
245
  const record = await converter.readRecord(recordAsXml)
241
- record.uniqueIdentifier = `TEMP-ID-${Date.now()}`
246
+ record.uniqueIdentifier = `${TEMPORARY_ID_PREFIX}${Date.now()}`
242
247
  record.title = `${record.title} (Copy)`
243
248
  const xml = await converter.writeRecord(record, recordAsXml)
244
249
  window.localStorage.setItem(
245
250
  this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
246
251
  xml
247
252
  )
253
+ this._draftsChanged.next()
248
254
  return [record, xml, false] as [CatalogRecord, string, false]
249
255
  })
250
256
  )
@@ -295,17 +301,26 @@ export class Gn4Repository implements RecordsRepositoryInterface {
295
301
  )
296
302
  }
297
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
+
298
312
  saveRecordAsDraft(
299
313
  record: CatalogRecord,
300
314
  referenceRecordSource?: string
301
315
  ): Observable<string> {
302
316
  return this.serializeRecordToXml(record, referenceRecordSource).pipe(
303
- tap((recordXml) =>
317
+ tap((recordXml) => {
304
318
  window.localStorage.setItem(
305
319
  this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
306
320
  recordXml
307
321
  )
308
- )
322
+ this._draftsChanged.next()
323
+ })
309
324
  )
310
325
  }
311
326
 
@@ -313,6 +328,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
313
328
  window.localStorage.removeItem(
314
329
  this.getLocalStorageKeyForRecord(uniqueIdentifier)
315
330
  )
331
+ this._draftsChanged.next()
316
332
  }
317
333
 
318
334
  recordHasDraft(uniqueIdentifier: string): boolean {
@@ -323,6 +339,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
323
339
  )
324
340
  }
325
341
 
342
+ isRecordNotYetSaved(uniqueIdentifier: string): boolean {
343
+ return uniqueIdentifier.startsWith(TEMPORARY_ID_PREFIX)
344
+ }
345
+
326
346
  // generated by copilot
327
347
  getAllDrafts(): Observable<CatalogRecord[]> {
328
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
 
@@ -52,6 +52,14 @@ export abstract class RecordsRepositoryInterface {
52
52
  referenceRecordSource?: string
53
53
  ): Observable<string>
54
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
+
55
63
  /**
56
64
  * @param record
57
65
  * @param referenceRecordSource
@@ -64,7 +72,9 @@ export abstract class RecordsRepositoryInterface {
64
72
 
65
73
  abstract clearRecordDraft(uniqueIdentifier: string): void
66
74
  abstract recordHasDraft(uniqueIdentifier: string): boolean
75
+ abstract isRecordNotYetSaved(uniqueIdentifier: string): boolean
67
76
 
68
77
  /** will return all pending drafts, both published and not published */
69
78
  abstract getAllDrafts(): Observable<CatalogRecord[]>
79
+ abstract draftsChanged$: Observable<void>
70
80
  }
@@ -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>
@@ -2,12 +2,18 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  ChangeDetectorRef,
4
4
  Component,
5
+ EventEmitter,
5
6
  Input,
7
+ OnChanges,
6
8
  OnInit,
9
+ Output,
10
+ SimpleChanges,
7
11
  } from '@angular/core'
8
12
  import { CommonModule } from '@angular/common'
9
13
  import { RecordsApiService } from '../../../../../../../libs/data-access/gn4/src'
10
14
  import { UiInputsModule } from '../../../../../../../libs/ui/inputs/src'
15
+ import { FormControl } from '@angular/forms'
16
+ import { GraphicOverview } from '../../../../../../../libs/common/domain/src/lib/model/record'
11
17
 
12
18
  @Component({
13
19
  selector: 'gn-ui-overview-upload',
@@ -17,9 +23,13 @@ import { UiInputsModule } from '../../../../../../../libs/ui/inputs/src'
17
23
  styleUrls: ['./overview-upload.component.css'],
18
24
  changeDetection: ChangeDetectionStrategy.OnPush,
19
25
  })
20
- export class OverviewUploadComponent implements OnInit {
26
+ export class OverviewUploadComponent implements OnInit, OnChanges {
21
27
  @Input() metadataUuid: string
28
+ @Input() formControl!: FormControl
29
+ @Output() overviewChange = new EventEmitter<GraphicOverview | null>()
30
+ @Output() altTextChange: EventEmitter<string> = new EventEmitter()
22
31
 
32
+ resourceAltText: string
23
33
  resourceFileName: string
24
34
  resourceUrl: string
25
35
 
@@ -29,42 +39,123 @@ export class OverviewUploadComponent implements OnInit {
29
39
  ) {}
30
40
 
31
41
  ngOnInit(): void {
32
- this.recordsApiService
33
- .getAllResources(this.metadataUuid)
34
- .subscribe((resources) => {
35
- this.resourceFileName = resources[0]?.filename
36
- this.resourceUrl = resources[0]?.url
42
+ this.recordsApiService.getAllResources(this.metadataUuid).subscribe({
43
+ next: (resources) => {
44
+ if (resources && resources.length > 0) {
45
+ this.resourceUrl = resources[0].url
46
+ this.resourceFileName = resources[0].filename
47
+ if (!this.resourceAltText) {
48
+ this.resourceAltText = this.resourceFileName
49
+ }
50
+ } else {
51
+ this.resourceUrl = ''
52
+ this.resourceAltText = ''
53
+ this.resourceFileName = ''
54
+ }
55
+
37
56
  this.cd.markForCheck()
38
- })
57
+ },
58
+ error: this.errorHandle,
59
+ })
39
60
  }
40
61
 
41
62
  handleFileChange(file: File) {
42
63
  this.recordsApiService
43
64
  .putResource(this.metadataUuid, file, 'public')
44
- .subscribe((resource) => {
45
- this.resourceFileName = resource.filename
46
- this.resourceUrl = resource.url
47
- this.cd.markForCheck()
65
+ .subscribe({
66
+ next: (resource) => {
67
+ this.resourceUrl = resource.url
68
+ this.resourceAltText = resource.filename
69
+
70
+ this.overviewChange.emit({
71
+ url: new URL(resource.url),
72
+ description: resource.filename,
73
+ })
74
+
75
+ this.cd.markForCheck()
76
+ },
77
+ error: this.errorHandle,
48
78
  })
49
79
  }
50
80
 
51
81
  handleUrlChange(url: string) {
52
82
  this.recordsApiService
53
83
  .putResourceFromURL(this.metadataUuid, url, 'public')
54
- .subscribe((resource) => {
55
- this.resourceFileName = resource.filename
56
- this.resourceUrl = resource.url
57
- this.cd.markForCheck()
84
+ .subscribe({
85
+ next: (resource) => {
86
+ this.resourceUrl = resource.url
87
+ this.resourceAltText = resource.filename
88
+
89
+ this.overviewChange.emit({
90
+ url: new URL(resource.url),
91
+ description: resource.filename,
92
+ })
93
+
94
+ this.cd.markForCheck()
95
+ },
96
+ error: this.errorHandle,
58
97
  })
59
98
  }
60
99
 
100
+ handleAltTextChange(newAltText: string) {
101
+ this.resourceAltText = newAltText
102
+
103
+ this.overviewChange.emit({
104
+ url: new URL(this.resourceUrl),
105
+ description: this.resourceAltText,
106
+ })
107
+
108
+ this.cd.markForCheck()
109
+ }
110
+
61
111
  handleDelete() {
112
+ //this.formControl.markAsDirty()
62
113
  this.recordsApiService
63
114
  .delResource(this.metadataUuid, this.resourceFileName)
64
- .subscribe(() => {
65
- this.resourceFileName = null
66
- this.resourceUrl = null
67
- this.cd.markForCheck()
115
+ .subscribe({
116
+ next: () => {
117
+ this.resourceAltText = ''
118
+ this.resourceUrl = ''
119
+
120
+ this.overviewChange.emit(null)
121
+
122
+ this.cd.markForCheck()
123
+ },
124
+ error: this.errorHandle,
68
125
  })
69
126
  }
127
+
128
+ private errorHandle = (error: never) => {
129
+ console.error(error)
130
+
131
+ this.resourceUrl = ''
132
+ this.resourceAltText = ''
133
+ this.resourceFileName = ''
134
+
135
+ this.overviewChange.emit(null)
136
+
137
+ this.cd.markForCheck()
138
+ }
139
+
140
+ ngOnChanges(changes: SimpleChanges): void {
141
+ const overviewChanges = changes['formControl']
142
+ if (
143
+ overviewChanges &&
144
+ overviewChanges.currentValue !== overviewChanges.previousValue
145
+ ) {
146
+ let overview: GraphicOverview
147
+ if (
148
+ overviewChanges.currentValue.value &&
149
+ overviewChanges.currentValue.value.length > 0
150
+ ) {
151
+ overview = overviewChanges.currentValue.value[0] as GraphicOverview
152
+ } else {
153
+ return
154
+ }
155
+ if (overview.description) {
156
+ this.resourceAltText = overview.description
157
+ this.cd.markForCheck()
158
+ }
159
+ }
160
+ }
70
161
  }
@@ -0,0 +1,76 @@
1
+ <div class="flex flex-col gap-3">
2
+ <div class="flex flex-row flex-wrap gap-2" data-test="rolesToPick">
3
+ <ng-container *ngFor="let role of rolesToPick">
4
+ <gn-ui-button
5
+ extraClass="px-2 py-1.5"
6
+ (buttonClick)="addRoleToDisplay(role)"
7
+ >
8
+ <div class="flex flex-row gap-1 items-center">
9
+ <span class="text-primary text-[20px] leading-[0] font-bold pb-[5px]"
10
+ >&#8330;</span
11
+ >
12
+ <span class="font-bold" translate>{{ roleToLabel(role) }}</span>
13
+ </div>
14
+ </gn-ui-button>
15
+ </ng-container>
16
+ </div>
17
+ <div
18
+ class="mt-8"
19
+ *ngIf="
20
+ roleSectionsToDisplay && roleSectionsToDisplay.length > 0;
21
+ else noContact
22
+ "
23
+ data-test="displayedRoles"
24
+ >
25
+ <div
26
+ *ngFor="
27
+ let role of roleSectionsToDisplay;
28
+ let index = index;
29
+ let isLast = last
30
+ "
31
+ class="flex flex-col gap-4"
32
+ >
33
+ <div class="flex flex-row justify-between">
34
+ <span class="font-bold text-base" translate>{{
35
+ roleToLabel(role)
36
+ }}</span>
37
+ </div>
38
+
39
+ <gn-ui-autocomplete
40
+ [placeholder]="'Choose a contact'"
41
+ [action]="autoCompleteAction"
42
+ (itemSelected)="addContact($event, role)"
43
+ [displayWithFn]="displayWithFn"
44
+ [minCharacterCount]="1"
45
+ [clearOnSelection]="true"
46
+ >
47
+ </gn-ui-autocomplete>
48
+
49
+ <ng-container *ngIf="contactsForRessourceByRole.get(role) as contacts">
50
+ <ng-container *ngIf="contacts.length > 1">
51
+ <gn-ui-sortable-list
52
+ [elements]="contactsAsDynElemByRole.get(role)"
53
+ (elementsChange)="handleContactsChanged($event)"
54
+ ></gn-ui-sortable-list>
55
+ </ng-container>
56
+ <ng-container *ngIf="contacts.length === 1">
57
+ <ng-container *ngFor="let contact of contacts">
58
+ <gn-ui-contact-card
59
+ [contact]="contact"
60
+ (contactRemoved)="removeContact(index)"
61
+ ></gn-ui-contact-card> </ng-container
62
+ ></ng-container>
63
+ </ng-container>
64
+
65
+ <hr class="border-t-[#D6D3D1] mt-4 mb-6" *ngIf="!isLast" />
66
+ </div>
67
+ </div>
68
+ <ng-template #noContact>
69
+ <div
70
+ class="p-4 border border-primary bg-primary-lightest rounded-lg"
71
+ translate
72
+ >
73
+ editor.record.form.field.contactsForResource.noContact
74
+ </div>
75
+ </ng-template>
76
+ </div>