geonetwork-ui 2.4.0-dev.9075aa64 → 2.4.0-dev.9b37393d

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 (174) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +2 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +3 -3
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +1 -16
  4. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +18 -2
  5. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +78 -39
  6. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  7. package/esm2022/libs/feature/editor/src/index.mjs +2 -1
  8. package/esm2022/libs/feature/editor/src/lib/components/contact-card/contact-card.component.mjs +4 -16
  9. package/esm2022/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.mjs +4 -2
  10. package/esm2022/libs/feature/editor/src/lib/components/import-record/import-record.component.mjs +93 -0
  11. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.mjs +6 -3
  12. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-date-updated/form-field-date-updated.component.mjs +19 -0
  13. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.mjs +23 -10
  14. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-license/form-field-license.component.mjs +3 -3
  15. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-link-resources/form-field-online-link-resources.component.mjs +3 -3
  16. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.mjs +4 -8
  17. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.mjs +10 -7
  18. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.mjs +6 -3
  19. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.mjs +3 -3
  20. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +5 -5
  21. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/index.mjs +2 -2
  22. package/esm2022/libs/feature/editor/src/lib/components/record-form/record-form.component.mjs +3 -3
  23. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +6 -4
  24. package/esm2022/libs/feature/editor/src/lib/models/editor-config.model.mjs +1 -1
  25. package/esm2022/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.mjs +3 -3
  26. package/esm2022/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.mjs +6 -6
  27. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  28. package/esm2022/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.mjs +7 -4
  29. package/esm2022/libs/ui/inputs/src/lib/badge/badge.component.mjs +5 -3
  30. package/esm2022/libs/ui/inputs/src/lib/button/button.component.mjs +8 -1
  31. package/esm2022/libs/ui/inputs/src/lib/date-picker/date-picker.component.mjs +11 -4
  32. package/esm2022/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.mjs +9 -3
  33. package/esm2022/libs/ui/inputs/src/lib/file-input/file-input.component.mjs +3 -3
  34. package/esm2022/libs/ui/inputs/src/lib/image-input/image-input.component.mjs +3 -3
  35. package/esm2022/libs/ui/inputs/src/lib/switch-toggle/switch-toggle.component.mjs +3 -3
  36. package/esm2022/libs/ui/inputs/src/lib/url-input/url-input.component.mjs +3 -3
  37. package/esm2022/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.mjs +5 -3
  38. package/esm2022/libs/util/shared/src/lib/services/theme.service.mjs +2 -1
  39. package/esm2022/translations/de.json +9 -0
  40. package/esm2022/translations/en.json +9 -0
  41. package/esm2022/translations/es.json +9 -0
  42. package/esm2022/translations/fr.json +9 -0
  43. package/esm2022/translations/it.json +9 -0
  44. package/esm2022/translations/nl.json +9 -0
  45. package/esm2022/translations/pt.json +9 -0
  46. package/fesm2022/geonetwork-ui.mjs +388 -186
  47. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  48. package/libs/api/metadata-converter/src/index.d.ts +1 -0
  49. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  50. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -1
  51. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +0 -1
  52. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  53. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts +6 -0
  54. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts.map +1 -1
  55. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +12 -7
  56. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  57. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +7 -0
  58. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  59. package/libs/feature/editor/src/index.d.ts +1 -0
  60. package/libs/feature/editor/src/index.d.ts.map +1 -1
  61. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts +1 -5
  62. package/libs/feature/editor/src/lib/components/contact-card/contact-card.component.d.ts.map +1 -1
  63. package/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.d.ts +2 -1
  64. package/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.d.ts.map +1 -1
  65. package/libs/feature/editor/src/lib/components/import-record/import-record.component.d.ts +33 -0
  66. package/libs/feature/editor/src/lib/components/import-record/import-record.component.d.ts.map +1 -0
  67. 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 -1
  68. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-date-updated/form-field-date-updated.component.d.ts +9 -0
  69. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-date-updated/form-field-date-updated.component.d.ts.map +1 -0
  70. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.d.ts +5 -2
  71. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.d.ts.map +1 -1
  72. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.d.ts +1 -3
  73. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.d.ts.map +1 -1
  74. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.d.ts.map +1 -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/index.d.ts +1 -1
  77. package/libs/feature/editor/src/lib/components/record-form/form-field/index.d.ts.map +1 -1
  78. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  79. package/libs/feature/editor/src/lib/models/editor-config.model.d.ts +1 -0
  80. package/libs/feature/editor/src/lib/models/editor-config.model.d.ts.map +1 -1
  81. package/libs/ui/elements/src/lib/downloads-list/downloads-list.component.d.ts +1 -1
  82. package/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.d.ts +2 -2
  83. package/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.d.ts.map +1 -1
  84. package/libs/ui/elements/src/lib/record-api-form/record-api-form.component.d.ts +1 -1
  85. package/libs/ui/inputs/src/index.d.ts +1 -0
  86. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  87. package/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.d.ts.map +1 -1
  88. package/libs/ui/inputs/src/lib/badge/badge.component.d.ts.map +1 -1
  89. package/libs/ui/inputs/src/lib/button/button.component.d.ts +1 -1
  90. package/libs/ui/inputs/src/lib/button/button.component.d.ts.map +1 -1
  91. package/libs/ui/inputs/src/lib/date-picker/date-picker.component.d.ts.map +1 -1
  92. package/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.d.ts.map +1 -1
  93. package/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.d.ts +2 -2
  94. package/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.d.ts.map +1 -1
  95. package/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.d.ts +1 -1
  96. package/libs/ui/search/src/lib/results-table/results-table.component.d.ts +1 -1
  97. package/libs/util/shared/src/lib/services/theme.service.d.ts.map +1 -1
  98. package/package.json +1 -1
  99. package/src/libs/api/metadata-converter/src/index.ts +1 -0
  100. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +1 -2
  101. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +0 -27
  102. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +20 -1
  103. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +132 -68
  104. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +10 -0
  105. package/src/libs/feature/editor/src/index.ts +1 -0
  106. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.html +12 -22
  107. package/src/libs/feature/editor/src/lib/components/contact-card/contact-card.component.ts +1 -13
  108. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +1 -0
  109. package/src/libs/feature/editor/src/lib/components/import-record/import-record.component.html +43 -0
  110. package/src/libs/feature/editor/src/lib/components/import-record/import-record.component.ts +129 -0
  111. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.css +4 -0
  112. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +13 -33
  113. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +2 -0
  114. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-date-updated/form-field-date-updated.component.css +0 -0
  115. package/src/libs/feature/editor/src/lib/components/record-form/form-field/{form-field-resource-updated/form-field-resource-updated.component.ts → form-field-date-updated/form-field-date-updated.component.ts} +4 -4
  116. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +3 -2
  117. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts +35 -3
  118. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-license/form-field-license.component.css +8 -0
  119. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-link-resources/form-field-online-link-resources.component.html +1 -0
  120. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +13 -6
  121. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +0 -9
  122. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.html +1 -1
  123. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-spatial-extent/form-field-spatial-extent.component.ts +16 -5
  124. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.css +4 -0
  125. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +2 -1
  126. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.ts +2 -0
  127. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.css +4 -0
  128. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +16 -14
  129. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +19 -20
  130. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +2 -2
  131. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -1
  132. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +30 -29
  133. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -3
  134. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +3 -0
  135. package/src/libs/feature/search/src/lib/fuzzy-search/fuzzy-search.component.html +1 -0
  136. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.html +13 -20
  137. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.ts +1 -1
  138. package/src/libs/ui/inputs/src/index.ts +1 -0
  139. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +25 -18
  140. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +38 -24
  141. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +4 -1
  142. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +12 -3
  143. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +2 -1
  144. package/src/libs/ui/inputs/src/lib/button/button.component.ts +15 -1
  145. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.css +7 -2
  146. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.html +13 -6
  147. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +7 -1
  148. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.css +7 -2
  149. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.html +27 -23
  150. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +7 -1
  151. package/src/libs/ui/inputs/src/lib/file-input/file-input.component.css +4 -0
  152. package/src/libs/ui/inputs/src/lib/file-input/file-input.component.html +6 -2
  153. package/src/libs/ui/inputs/src/lib/image-input/image-input.component.css +4 -0
  154. package/src/libs/ui/inputs/src/lib/image-input/image-input.component.html +29 -21
  155. package/src/libs/ui/inputs/src/lib/switch-toggle/switch-toggle.component.css +4 -3
  156. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +1 -1
  157. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +8 -3
  158. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +4 -3
  159. package/src/libs/util/shared/src/lib/services/theme.service.ts +1 -0
  160. package/tailwind.base.config.js +1 -0
  161. package/tailwind.base.css +30 -4
  162. package/translations/de.json +9 -0
  163. package/translations/en.json +9 -0
  164. package/translations/es.json +9 -0
  165. package/translations/fr.json +9 -0
  166. package/translations/it.json +9 -0
  167. package/translations/nl.json +9 -0
  168. package/translations/pt.json +9 -0
  169. package/translations/sk.json +9 -0
  170. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-resource-updated/form-field-resource-updated.component.mjs +0 -19
  171. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-resource-updated/form-field-resource-updated.component.d.ts +0 -9
  172. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-resource-updated/form-field-resource-updated.component.d.ts.map +0 -1
  173. /package/src/libs/feature/editor/src/lib/components/{record-form/form-field/form-field-resource-updated/form-field-resource-updated.component.css → import-record/import-record.component.css} +0 -0
  174. /package/src/libs/feature/editor/src/lib/components/record-form/form-field/{form-field-resource-updated/form-field-resource-updated.component.html → form-field-date-updated/form-field-date-updated.component.html} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geonetwork-ui",
3
- "version": "2.4.0-dev.9075aa64",
3
+ "version": "2.4.0-dev.9b37393d",
4
4
  "engines": {
5
5
  "node": ">=14.17.0"
6
6
  },
@@ -2,3 +2,4 @@ export * from './lib/iso19139'
2
2
  export * from './lib/iso19115-3'
3
3
  export * from './lib/find-converter'
4
4
  export * from './lib/gn4'
5
+ export * from './lib/xml-utils'
@@ -53,7 +53,6 @@ import {
53
53
  writeLineage,
54
54
  writeOnlineResources,
55
55
  writeOtherConstraints,
56
- writeOwnerOrganization,
57
56
  writeRecordUpdated,
58
57
  writeResourceCreated,
59
58
  writeResourcePublished,
@@ -113,7 +112,7 @@ export class Iso19139Converter extends BaseConverter<string> {
113
112
  > = {
114
113
  uniqueIdentifier: writeUniqueIdentifier,
115
114
  kind: writeKind,
116
- ownerOrganization: writeOwnerOrganization,
115
+ ownerOrganization: () => undefined, // fixme: find a way to store this value properly
117
116
  recordUpdated: writeRecordUpdated,
118
117
  recordCreated: () => undefined, // not supported in ISO19139
119
118
  recordPublished: () => undefined, // not supported in ISO19139
@@ -745,33 +745,6 @@ export function writeKind(record: CatalogRecord, rootEl: XmlElement) {
745
745
  )(rootEl)
746
746
  }
747
747
 
748
- export function writeOwnerOrganization(
749
- record: CatalogRecord,
750
- rootEl: XmlElement
751
- ) {
752
- // if no contact matches the owner org, create an empty one
753
- const ownerContact: Individual = record.contacts.find(
754
- (contact) => contact.organization.name === record.ownerOrganization.name
755
- )
756
- pipe(
757
- findChildOrCreate('gmd:contact'),
758
- removeAllChildren(),
759
- appendResponsibleParty(
760
- ownerContact
761
- ? {
762
- ...ownerContact,
763
- // owner responsible party is always point of contact
764
- role: 'point_of_contact',
765
- }
766
- : {
767
- organization: record.ownerOrganization,
768
- email: '',
769
- role: 'point_of_contact',
770
- }
771
- )
772
- )(rootEl)
773
- }
774
-
775
748
  export function writeRecordUpdated(record: CatalogRecord, rootEl: XmlElement) {
776
749
  pipe(
777
750
  findChildOrCreate('gmd:dateStamp'),
@@ -36,7 +36,8 @@ export function createDocument(rootEl: XmlElement): XmlDocument {
36
36
  if (namespace === 'xmlns' || namespace === null) return
37
37
  if (rootEl.attributes[`xmlns:${namespace}`]) return
38
38
  if (!NAMESPACES[namespace]) {
39
- throw new Error(`No known URI for namespace ${namespace}`)
39
+ // the namespace is unknown but it might still be declared correctly: ignore it
40
+ return
40
41
  }
41
42
  rootEl.attributes[`xmlns:${namespace}`] = NAMESPACES[namespace]
42
43
  }
@@ -456,3 +457,21 @@ export function renameElements(
456
457
  doReplace(rootElement)
457
458
  return rootElement
458
459
  }
460
+
461
+ /**
462
+ * This function use the DOMParser to check if the given xmlString is a valid XML file or throw an error
463
+ * (Generated by chatGPT)
464
+ * @param xmlString
465
+ */
466
+ export function assertValidXml(xmlString: string): Document {
467
+ const parser = new DOMParser()
468
+ const xmlDoc = parser.parseFromString(xmlString, 'application/xml')
469
+ const parserError = xmlDoc.querySelector('parsererror')
470
+
471
+ if (parserError) {
472
+ console.error(parserError)
473
+ throw new Error('File is not a valid XML.')
474
+ }
475
+
476
+ return xmlDoc
477
+ }
@@ -6,6 +6,7 @@ import {
6
6
  import { ElasticsearchService } from './elasticsearch'
7
7
  import {
8
8
  combineLatest,
9
+ exhaustMap,
9
10
  from,
10
11
  Observable,
11
12
  of,
@@ -25,22 +26,30 @@ import {
25
26
  } from '../../../../../../libs/common/domain/src/lib/model/search'
26
27
  import { catchError, map, tap } from 'rxjs/operators'
27
28
  import {
29
+ assertValidXml,
28
30
  findConverterForDocument,
29
31
  Gn4Converter,
30
32
  Gn4SearchResults,
31
33
  Iso19139Converter,
32
34
  } from '../../../../../../libs/api/metadata-converter/src'
33
35
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
34
- import { HttpErrorResponse } from '@angular/common/http'
36
+ import {
37
+ HttpClient,
38
+ HttpErrorResponse,
39
+ HttpHeaders,
40
+ } from '@angular/common/http'
35
41
 
36
42
  const TEMPORARY_ID_PREFIX = 'TEMP-ID-'
37
43
 
44
+ export type RecordAsXml = string
45
+
38
46
  @Injectable()
39
47
  export class Gn4Repository implements RecordsRepositoryInterface {
40
48
  _draftsChanged = new Subject<void>()
41
49
  draftsChanged$ = this._draftsChanged.asObservable()
42
50
 
43
51
  constructor(
52
+ private httpClient: HttpClient,
44
53
  private gn4SearchApi: SearchApiService,
45
54
  private gn4SearchHelper: ElasticsearchService,
46
55
  private gn4Mapper: Gn4Converter,
@@ -140,6 +149,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
140
149
  )
141
150
  )
142
151
  }
152
+
143
153
  aggregate(params: AggregationsParams): Observable<Aggregations> {
144
154
  // if aggregations are empty, return an empty object right away
145
155
  if (Object.keys(params).length === 0) return of({})
@@ -184,44 +194,12 @@ export class Gn4Repository implements RecordsRepositoryInterface {
184
194
  )
185
195
  }
186
196
 
187
- /**
188
- * Returns null if the record is not found
189
- */
190
- private loadRecordAsXml(uniqueIdentifier: string): Observable<string | null> {
191
- return this.gn4RecordsApi
192
- .getRecordAs(
193
- uniqueIdentifier,
194
- undefined,
195
- false,
196
- undefined,
197
- undefined,
198
- undefined,
199
- 'application/xml',
200
- 'response',
201
- undefined,
202
- { httpHeaderAccept: 'text/xml,application/xml' as 'application/xml' } // this is to make sure that the response is parsed as text
203
- )
204
- .pipe(
205
- map((response) => response.body),
206
- catchError((error: HttpErrorResponse) =>
207
- error.status === 404 ? of(null) : throwError(() => error)
208
- )
209
- )
210
- }
211
-
212
- private getLocalStorageKeyForRecord(uniqueIdentifier: string) {
213
- return `geonetwork-ui-draft-${uniqueIdentifier}`
214
- }
215
-
216
197
  openRecordForEdition(
217
198
  uniqueIdentifier: string
218
199
  ): Observable<[CatalogRecord, string, boolean] | null> {
219
- const draft$ = of(
220
- window.localStorage.getItem(
221
- this.getLocalStorageKeyForRecord(uniqueIdentifier)
222
- )
223
- )
224
- const recordAsXml$ = this.loadRecordAsXml(uniqueIdentifier)
200
+ const draft$ = of(this.getRecordFromLocalStorage(uniqueIdentifier))
201
+ const recordAsXml$ = this.getRecordAsXml(uniqueIdentifier)
202
+
225
203
  return combineLatest([draft$, recordAsXml$]).pipe(
226
204
  switchMap(([draft, recordAsXml]) => {
227
205
  const xml = draft ?? recordAsXml
@@ -239,34 +217,27 @@ export class Gn4Repository implements RecordsRepositoryInterface {
239
217
  openRecordForDuplication(
240
218
  uniqueIdentifier: string
241
219
  ): 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)
220
+ return this.getRecordAsXml(uniqueIdentifier).pipe(
221
+ switchMap(async (fetchedRecordAsXml) => {
222
+ const converter = findConverterForDocument(fetchedRecordAsXml)
223
+ const record = await converter.readRecord(fetchedRecordAsXml)
224
+
246
225
  record.uniqueIdentifier = `${TEMPORARY_ID_PREFIX}${Date.now()}`
247
226
  record.title = `${record.title} (Copy)`
248
- const xml = await converter.writeRecord(record, recordAsXml)
249
- window.localStorage.setItem(
250
- this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
251
- xml
227
+
228
+ const recordAsXml = await converter.writeRecord(
229
+ record,
230
+ fetchedRecordAsXml
252
231
  )
232
+
233
+ this.saveRecordToLocalStorage(recordAsXml, record.uniqueIdentifier)
253
234
  this._draftsChanged.next()
254
- return [record, xml, false] as [CatalogRecord, string, false]
235
+
236
+ return [record, recordAsXml, false] as [CatalogRecord, string, false]
255
237
  })
256
238
  )
257
239
  }
258
240
 
259
- private serializeRecordToXml(
260
- record: CatalogRecord,
261
- referenceRecordSource?: string
262
- ): Observable<string> {
263
- // if there's a reference record, use that standard; otherwise, use iso19139
264
- const converter = referenceRecordSource
265
- ? findConverterForDocument(referenceRecordSource)
266
- : new Iso19139Converter()
267
- return from(converter.writeRecord(record, referenceRecordSource))
268
- }
269
-
270
241
  saveRecord(
271
242
  record: CatalogRecord,
272
243
  referenceRecordSource?: string
@@ -301,6 +272,32 @@ export class Gn4Repository implements RecordsRepositoryInterface {
301
272
  )
302
273
  }
303
274
 
275
+ duplicateExternalRecord(recordDownloadUrl: string): Observable<string> {
276
+ return this.getExternalRecordAsXml(recordDownloadUrl).pipe(
277
+ exhaustMap(async (fetchedRecordAsXml: string) => {
278
+ const converter = findConverterForDocument(fetchedRecordAsXml)
279
+ const record = await converter.readRecord(fetchedRecordAsXml)
280
+ const tempId = this.generateTemporaryId()
281
+
282
+ record.title = `${record.title} (Copy)`
283
+ record.uniqueIdentifier = tempId
284
+
285
+ const recordAsXml = await converter.writeRecord(
286
+ record,
287
+ fetchedRecordAsXml
288
+ )
289
+
290
+ this.saveRecordToLocalStorage(recordAsXml, record.uniqueIdentifier)
291
+ this._draftsChanged.next()
292
+
293
+ return tempId
294
+ }),
295
+ catchError((error: HttpErrorResponse) => {
296
+ return throwError(() => error)
297
+ })
298
+ )
299
+ }
300
+
304
301
  deleteRecord(uniqueIdentifier: string): Observable<void> {
305
302
  return this.gn4RecordsApi.deleteRecord(uniqueIdentifier)
306
303
  }
@@ -315,28 +312,19 @@ export class Gn4Repository implements RecordsRepositoryInterface {
315
312
  ): Observable<string> {
316
313
  return this.serializeRecordToXml(record, referenceRecordSource).pipe(
317
314
  tap((recordXml) => {
318
- window.localStorage.setItem(
319
- this.getLocalStorageKeyForRecord(record.uniqueIdentifier),
320
- recordXml
321
- )
315
+ this.saveRecordToLocalStorage(recordXml, record.uniqueIdentifier)
322
316
  this._draftsChanged.next()
323
317
  })
324
318
  )
325
319
  }
326
320
 
327
321
  clearRecordDraft(uniqueIdentifier: string): void {
328
- window.localStorage.removeItem(
329
- this.getLocalStorageKeyForRecord(uniqueIdentifier)
330
- )
322
+ this.removeRecordFromLocalStorage(uniqueIdentifier)
331
323
  this._draftsChanged.next()
332
324
  }
333
325
 
334
326
  recordHasDraft(uniqueIdentifier: string): boolean {
335
- return (
336
- window.localStorage.getItem(
337
- this.getLocalStorageKeyForRecord(uniqueIdentifier)
338
- ) !== null
339
- )
327
+ return this.getRecordFromLocalStorage(uniqueIdentifier) !== null
340
328
  }
341
329
 
342
330
  isRecordNotYetSaved(uniqueIdentifier: string): boolean {
@@ -356,4 +344,80 @@ export class Gn4Repository implements RecordsRepositoryInterface {
356
344
  )
357
345
  )
358
346
  }
347
+
348
+ private getRecordAsXml(uniqueIdentifier: string): Observable<string | null> {
349
+ return this.gn4RecordsApi
350
+ .getRecordAs(
351
+ uniqueIdentifier,
352
+ undefined,
353
+ false,
354
+ undefined,
355
+ undefined,
356
+ undefined,
357
+ 'application/xml',
358
+ 'response',
359
+ undefined,
360
+ { httpHeaderAccept: 'text/xml,application/xml' as 'application/xml' } // this is to make sure that the response is parsed as text
361
+ )
362
+ .pipe(
363
+ map((response) => response.body),
364
+ catchError((error: HttpErrorResponse) =>
365
+ error.status === 404 ? of(null) : throwError(() => error)
366
+ )
367
+ )
368
+ }
369
+
370
+ private serializeRecordToXml(
371
+ record: CatalogRecord,
372
+ referenceRecordSource?: string
373
+ ): Observable<string> {
374
+ // if there's a reference record, use that standard; otherwise, use iso19139
375
+ const converter = referenceRecordSource
376
+ ? findConverterForDocument(referenceRecordSource)
377
+ : new Iso19139Converter()
378
+ return from(converter.writeRecord(record, referenceRecordSource))
379
+ }
380
+
381
+ private getExternalRecordAsXml(
382
+ recordDownloadUrl: string
383
+ ): Observable<string> {
384
+ let headers = new HttpHeaders()
385
+ const responseType_ = 'text'
386
+ headers = headers.set('Accept', 'text/xml,application/xml')
387
+
388
+ return this.httpClient
389
+ .get<string>(recordDownloadUrl, {
390
+ responseType: <any>responseType_,
391
+ headers: headers,
392
+ observe: 'body',
393
+ })
394
+ .pipe(
395
+ map((recordAsXmlFile) => {
396
+ assertValidXml(recordAsXmlFile)
397
+
398
+ return recordAsXmlFile
399
+ })
400
+ )
401
+ }
402
+
403
+ private getLocalStorageKeyForRecord(recordId: string): string {
404
+ return `geonetwork-ui-draft-${recordId}` // Never change this prefix as it is a breaking change
405
+ }
406
+
407
+ private saveRecordToLocalStorage(recordAsXml: RecordAsXml, recordId: string) {
408
+ window.localStorage.setItem(
409
+ this.getLocalStorageKeyForRecord(recordId),
410
+ recordAsXml
411
+ )
412
+ }
413
+
414
+ private getRecordFromLocalStorage(recordId: string): RecordAsXml {
415
+ return window.localStorage.getItem(
416
+ this.getLocalStorageKeyForRecord(recordId)
417
+ )
418
+ }
419
+
420
+ private removeRecordFromLocalStorage(recordId: string): void {
421
+ window.localStorage.removeItem(this.getLocalStorageKeyForRecord(recordId))
422
+ }
359
423
  }
@@ -52,6 +52,16 @@ export abstract class RecordsRepositoryInterface {
52
52
  referenceRecordSource?: string
53
53
  ): Observable<string>
54
54
 
55
+ /**
56
+ * Try to duplicate the external record from given url. If it suceed, then it will save the record as draft and return its temporary id.
57
+ *
58
+ * @param recordDownloadUrl
59
+ * @returns Observable<string>
60
+ */
61
+ abstract duplicateExternalRecord(
62
+ recordDownloadUrl: string
63
+ ): Observable<string>
64
+
55
65
  /**
56
66
  * @param uniqueIdentifier
57
67
  * @returns Observable<void> Returns when record is deleted
@@ -5,6 +5,7 @@ export * from './lib/+state/editor.reducer'
5
5
  export * from './lib/+state/editor.actions'
6
6
  export * from './lib/feature-editor.module'
7
7
  export * from './lib/services/editor.service'
8
+ export * from './lib/components/import-record/import-record.component'
8
9
  export * from './lib/components/record-form/record-form.component'
9
10
  export * from './lib/components/wizard/wizard.component'
10
11
  export * from './lib/components/wizard-field/wizard-field.component'
@@ -1,25 +1,15 @@
1
- <div class="flex flex-row gap-2 items-center">
2
- <div class="gn-ui-card">
3
- <gn-ui-thumbnail
4
- class="w-[56px] h-[56px] rounded-[4px] overflow-hidden shrink-0"
5
- [thumbnailUrl]="contact.organization.logoUrl?.href"
6
- [fit]="'contain'"
7
- ></gn-ui-thumbnail>
8
- <div class="flex flex-col w-full overflow-hidden leading-snug">
9
- <div class="text-[16px] font-bold text-main" data-test="contactCardName">
10
- {{ contact.firstName }} {{ contact.lastName }}
11
- </div>
12
- <div class="text-[14px] text-gray-900" data-test="contactCardEmail">
13
- {{ contact.email }}
14
- </div>
1
+ <div class="gn-ui-card">
2
+ <gn-ui-thumbnail
3
+ class="w-[56px] h-[56px] rounded-[4px] overflow-hidden shrink-0"
4
+ [thumbnailUrl]="contact.organization.logoUrl?.href"
5
+ [fit]="'contain'"
6
+ ></gn-ui-thumbnail>
7
+ <div class="flex flex-col w-full overflow-hidden leading-snug">
8
+ <div class="text-[16px] font-bold text-main" data-test="contactCardName">
9
+ {{ contact.firstName }} {{ contact.lastName }}
10
+ </div>
11
+ <div class="text-[14px] text-gray-900" data-test="contactCardEmail">
12
+ {{ contact.email }}
15
13
  </div>
16
14
  </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
15
  </div>
@@ -1,10 +1,4 @@
1
- import {
2
- ChangeDetectionStrategy,
3
- Component,
4
- EventEmitter,
5
- Input,
6
- Output,
7
- } from '@angular/core'
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
8
2
  import { Individual } from '../../../../../../../libs/common/domain/src/lib/model/record'
9
3
  import { MatIconModule } from '@angular/material/icon'
10
4
  import { CommonModule } from '@angular/common'
@@ -21,10 +15,4 @@ import { ThumbnailComponent } from '../../../../../../../libs/ui/elements/src'
21
15
  })
22
16
  export class ContactCardComponent {
23
17
  @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
18
  }
@@ -41,6 +41,7 @@ export class GenericKeywordsComponent {
41
41
  @Input() keywords: Keyword[]
42
42
  @Input() keywordTypes: KeywordType[]
43
43
  @Input() placeholder: string
44
+ @Input() allowSubmit: boolean
44
45
  @Output() changedKeywords: EventEmitter<Keyword[]> = new EventEmitter()
45
46
  @Output() addedKeyword: EventEmitter<Keyword> = new EventEmitter()
46
47
  @Output() deletedKeyword: EventEmitter<Keyword> = new EventEmitter()
@@ -0,0 +1,43 @@
1
+ <ng-container [ngSwitch]="sectionDisplayed">
2
+ <ng-container *ngSwitchCase="'mainMenu'">
3
+ <div
4
+ data-test="importMenuMainSection"
5
+ class="mt-2 border border-gray-100 p-2 flex items-center bg-white shadow-2xl rounded-2xl"
6
+ >
7
+ <ul class="flex flex-col gap-2 w-full">
8
+ <li *ngFor="let menuItem of importMenuItems">
9
+ <gn-ui-button
10
+ [attr.data-test]="menuItem.dataTest"
11
+ type="light"
12
+ extraClass="flex flex-row items-center gap-2 w-full justify-start"
13
+ (buttonClick)="menuItem.action()"
14
+ ><mat-icon class="material-symbols-outlined">
15
+ {{ menuItem.icon }} </mat-icon
16
+ ><span>{{ menuItem.label }}</span></gn-ui-button
17
+ >
18
+ </li>
19
+ </ul>
20
+ </div>
21
+ </ng-container>
22
+ <ng-container *ngSwitchCase="'importExternalFile'">
23
+ <div
24
+ data-test="importMenuImportExternalFileSection"
25
+ class="p-6 flex flex-col gap-2 mt-2 border border-gray-100 bg-white shadow-2xl rounded-2xl"
26
+ >
27
+ <div class="flex flex-row items-center gap-2">
28
+ <gn-ui-button
29
+ data-test="importMenuImportExternalFileSectionBackButton"
30
+ type="light"
31
+ (buttonClick)="displayMainMenu()"
32
+ >
33
+ <mat-icon class="material-symbols-outlined">arrow_back</mat-icon>
34
+ </gn-ui-button>
35
+ <span class="font-bold"> {{ externalImportBackLabel }}</span>
36
+ </div>
37
+ <gn-ui-url-input
38
+ (valueChange)="importRecord($event)"
39
+ [disabled]="isRecordImportInProgress"
40
+ ></gn-ui-url-input>
41
+ </div>
42
+ </ng-container>
43
+ </ng-container>
@@ -0,0 +1,129 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ EventEmitter,
6
+ Output,
7
+ } from '@angular/core'
8
+ import { MatIconModule } from '@angular/material/icon'
9
+ import { CommonModule } from '@angular/common'
10
+ import { ButtonComponent, UrlInputComponent } from '../../../../../../../libs/ui/inputs/src'
11
+ import { ThumbnailComponent } from '../../../../../../../libs/ui/elements/src'
12
+ import { TranslateService } from '@ngx-translate/core'
13
+ import { NotificationsService } from '../../../../../../../libs/feature/notifications/src'
14
+ import { RecordsRepositoryInterface } from '../../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
15
+ import { Router } from '@angular/router'
16
+
17
+ interface ImportMenuItems {
18
+ label: string
19
+ icon: string
20
+ action: () => any
21
+ dataTest: string
22
+ }
23
+
24
+ type ImportMenuPage = 'mainMenu' | 'importExternalFile'
25
+
26
+ @Component({
27
+ selector: 'gn-ui-import-record',
28
+ templateUrl: './import-record.component.html',
29
+ styleUrls: ['./import-record.component.css'],
30
+ changeDetection: ChangeDetectionStrategy.OnPush,
31
+ standalone: true,
32
+ imports: [
33
+ CommonModule,
34
+ MatIconModule,
35
+ ButtonComponent,
36
+ ThumbnailComponent,
37
+ UrlInputComponent,
38
+ ],
39
+ })
40
+ export class ImportRecordComponent {
41
+ @Output() closeImportMenu = new EventEmitter<void>()
42
+
43
+ importMenuItems: ImportMenuItems[] = [
44
+ {
45
+ label: this.translateService.instant('dashboard.importRecord.useModel'),
46
+ icon: 'highlight',
47
+ action: () => null,
48
+ dataTest: 'useAModelButton',
49
+ },
50
+ {
51
+ label: this.translateService.instant(
52
+ 'dashboard.importRecord.importExternal'
53
+ ),
54
+ icon: 'cloud_download',
55
+ action: this.displayImportExternal.bind(this),
56
+ dataTest: 'importFromUrlButton',
57
+ },
58
+ ]
59
+
60
+ isRecordImportInProgress = false
61
+
62
+ sectionDisplayed: ImportMenuPage = 'mainMenu'
63
+
64
+ externalImportBackLabel = this.translateService.instant(
65
+ 'dashboard.importRecord.importExternalLabel'
66
+ )
67
+
68
+ constructor(
69
+ private router: Router,
70
+ private translateService: TranslateService,
71
+ private cdr: ChangeDetectorRef,
72
+ private notificationsService: NotificationsService,
73
+ private recordsRepository: RecordsRepositoryInterface
74
+ ) {}
75
+
76
+ displayMainMenu() {
77
+ this.sectionDisplayed = 'mainMenu'
78
+ this.cdr.markForCheck()
79
+ }
80
+
81
+ displayImportExternal() {
82
+ this.sectionDisplayed = 'importExternalFile'
83
+ this.cdr.markForCheck()
84
+ }
85
+
86
+ importRecord(url: string) {
87
+ this.isRecordImportInProgress = true
88
+
89
+ this.recordsRepository.duplicateExternalRecord(url).subscribe({
90
+ next: (recordTempId) => {
91
+ if (recordTempId) {
92
+ this.notificationsService.showNotification(
93
+ {
94
+ type: 'success',
95
+ title: this.translateService.instant(
96
+ 'editor.record.importFromExternalFile.success.title'
97
+ ),
98
+ text: `${this.translateService.instant(
99
+ 'editor.record.importFromExternalFile.success.body'
100
+ )}`,
101
+ },
102
+ 2500
103
+ )
104
+
105
+ this.router
106
+ .navigate(['/edit', recordTempId])
107
+ .catch((err) => console.error(err))
108
+ }
109
+ this.closeImportMenu.next()
110
+ },
111
+ error: (error) => {
112
+ this.notificationsService.showNotification(
113
+ {
114
+ type: 'error',
115
+ title: this.translateService.instant(
116
+ 'editor.record.importFromExternalFile.failure.title'
117
+ ),
118
+ text: `${this.translateService.instant(
119
+ 'editor.record.importFromExternalFile.failure.body'
120
+ )} ${error.message ?? ''}`,
121
+ },
122
+ 2500
123
+ )
124
+ this.isRecordImportInProgress = false
125
+ this.cdr.markForCheck()
126
+ },
127
+ })
128
+ }
129
+ }