geonetwork-ui 2.10.0-dev.fc6515c0b → 2.11.0-dev.5c2e5295e

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/fesm2022/geonetwork-ui.mjs +1709 -6267
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +230 -172
  4. package/index.d.ts.map +1 -1
  5. package/package.json +25 -35
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +20 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +4 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +23 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +8 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +2 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +2 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +2 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +2 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +4 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +23 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +2 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +4 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +9 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +2 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +6 -5
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +11 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +21 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +52 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +24 -1
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +77 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  32. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +79 -0
  33. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  34. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +24 -5
  35. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +66 -40
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +37 -2
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +49 -5
  40. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  41. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  42. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  43. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  44. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  45. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  46. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +62 -0
  47. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  48. package/src/libs/feature/editor/src/index.ts +1 -0
  49. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  50. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  51. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  52. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  53. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  54. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +68 -0
  55. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +41 -0
  56. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  57. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  58. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  59. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +34 -16
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +17 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  79. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  80. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  81. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  82. package/src/libs/feature/editor/src/lib/fields.config.ts +359 -337
  83. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -3
  84. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  85. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  86. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  87. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  88. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  89. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  90. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  91. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +219 -8
  92. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  93. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  94. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  95. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  96. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  97. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  98. package/src/libs/ui/dataviz/src/index.ts +0 -1
  99. package/src/libs/ui/elements/src/index.ts +1 -0
  100. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  101. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
  102. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  103. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  104. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  105. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  106. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  107. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  108. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  109. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  110. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  111. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +5 -8
  112. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  113. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  114. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  115. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  116. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  117. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  118. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  119. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  120. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  121. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  122. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  123. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  124. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  125. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  126. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  127. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  128. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  129. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  130. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  131. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  132. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  133. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  134. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  135. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  136. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  137. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  138. package/src/libs/util/app-config/src/lib/app-config.ts +35 -10
  139. package/src/libs/util/app-config/src/lib/model.ts +3 -0
  140. package/src/libs/util/app-config/src/lib/parse-utils.ts +27 -0
  141. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  142. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  143. package/src/libs/util/i18n/src/index.ts +0 -1
  144. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  145. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  146. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  147. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  148. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  149. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  150. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  151. package/style.css +0 -1
  152. package/tailwind.base.css +15 -0
  153. package/translations/de.json +122 -50
  154. package/translations/en.json +127 -55
  155. package/translations/es.json +96 -24
  156. package/translations/fr.json +124 -52
  157. package/translations/it.json +123 -51
  158. package/translations/nl.json +95 -23
  159. package/translations/pt.json +96 -24
  160. package/translations/sk.json +96 -24
  161. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AssociatedRecord,
2
3
  Constraint,
3
4
  ConstraintTranslations,
4
5
  DatasetOnlineResource,
@@ -8,6 +9,7 @@ import {
8
9
  Keyword,
9
10
  KeywordTranslations,
10
11
  LanguageCode,
12
+ SourceRecord,
11
13
  ModelTranslations,
12
14
  OnlineLinkResource,
13
15
  OnlineResource,
@@ -49,6 +51,7 @@ import {
49
51
  readText,
50
52
  XmlElement,
51
53
  } from '../xml-utils'
54
+ import { getAssociationTypeFromCode } from './utils/association-type.mapper'
52
55
  import { readGeometry } from './utils/geometry'
53
56
  import { fullNameToParts } from './utils/individual-name'
54
57
  import { getKeywordTypeFromKeywordTypeCode } from './utils/keyword.mapper'
@@ -894,6 +897,80 @@ export function readLineage(
894
897
  )(rootEl)
895
898
  }
896
899
 
900
+ export function extractSourceRecords(liLineageEl: XmlElement): SourceRecord[] {
901
+ if (!liLineageEl) return []
902
+ return pipe(
903
+ findChildrenElement('gmd:source', false),
904
+ mapArray((el) => {
905
+ const uuid = readAttribute('uuidref')(el)
906
+ const title = readAttribute('xlink:title')(el)
907
+ const href = readAttribute('xlink:href')(el)
908
+ if (!uuid && !title && !href) return null
909
+ return {
910
+ ...(uuid ? { uuid } : {}),
911
+ ...(title ? { title } : {}),
912
+ ...(href ? { href } : {}),
913
+ } as SourceRecord
914
+ }),
915
+ filterArray((s): s is SourceRecord => s !== null)
916
+ )(liLineageEl)
917
+ }
918
+
919
+ export function readSourceRecords(rootEl: XmlElement): SourceRecord[] {
920
+ return extractSourceRecords(
921
+ pipe(
922
+ findNestedElement(
923
+ 'gmd:dataQualityInfo',
924
+ 'gmd:DQ_DataQuality',
925
+ 'gmd:lineage',
926
+ 'gmd:LI_Lineage'
927
+ )
928
+ )(rootEl)
929
+ )
930
+ }
931
+
932
+ export function extractAssociatedRecords(
933
+ containerName: string,
934
+ aggregateName: string,
935
+ readIdentifier: ChainableFunction<XmlElement, string>
936
+ ): ChainableFunction<XmlElement, AssociatedRecord[]> {
937
+ return pipe(
938
+ findChildrenElement(containerName, false),
939
+ mapArray((el) => {
940
+ const aggregateEl = findChildElement(aggregateName, false)(el)
941
+ const uniqueIdentifier = readIdentifier(aggregateEl)
942
+ const associationTypeCode = pipe(
943
+ findNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'),
944
+ readAttribute('codeListValue')
945
+ )(aggregateEl)
946
+ if (!uniqueIdentifier || !associationTypeCode) return null
947
+ return {
948
+ uniqueIdentifier,
949
+ associationType: getAssociationTypeFromCode(associationTypeCode),
950
+ }
951
+ }),
952
+ filterArray((r): r is AssociatedRecord => r !== null)
953
+ )
954
+ }
955
+
956
+ export function readAssociatedRecords(rootEl: XmlElement): AssociatedRecord[] {
957
+ return pipe(
958
+ findIdentification(),
959
+ extractAssociatedRecords(
960
+ 'gmd:aggregationInfo',
961
+ 'gmd:MD_AggregateInformation',
962
+ pipe(
963
+ findNestedElement(
964
+ 'gmd:aggregateDataSetIdentifier',
965
+ 'gmd:MD_Identifier',
966
+ 'gmd:code'
967
+ ),
968
+ extractCharacterString()
969
+ )
970
+ )
971
+ )(rootEl)
972
+ }
973
+
897
974
  export function readUpdateFrequency(rootEl: XmlElement): UpdateFrequency {
898
975
  return pipe(
899
976
  findIdentification(),
@@ -0,0 +1,12 @@
1
+ import {
2
+ AssociationType,
3
+ associationTypeValues,
4
+ } from '../../../../../../../libs/common/domain/src/lib/model/record'
5
+
6
+ export function getAssociationTypeFromCode(
7
+ associationTypeCode: string
8
+ ): AssociationType {
9
+ return associationTypeValues.includes(associationTypeCode as AssociationType)
10
+ ? (associationTypeCode as AssociationType)
11
+ : 'crossReference'
12
+ }
@@ -8,6 +8,7 @@ import {
8
8
  Individual,
9
9
  Keyword,
10
10
  LanguageCode,
11
+ SourceRecord,
11
12
  RecordStatus,
12
13
  RecordTranslations,
13
14
  ReuseRecord,
@@ -1231,6 +1232,84 @@ export function writeLineage(record: DatasetRecord, rootEl: XmlElement) {
1231
1232
  )(rootEl)
1232
1233
  }
1233
1234
 
1235
+ export function appendSourceRecords(
1236
+ sources: SourceRecord[]
1237
+ ): ChainableFunction<XmlElement, XmlElement> {
1238
+ return pipe(
1239
+ removeChildrenByName('gmd:source'),
1240
+ appendChildren(
1241
+ ...sources
1242
+ .filter((source) => source.uuid || source.title || source.href)
1243
+ .map((source) =>
1244
+ pipe(
1245
+ createElement('gmd:source'),
1246
+ source.uuid ? writeAttribute('uuidref', source.uuid) : noop,
1247
+ source.title ? writeAttribute('xlink:title', source.title) : noop,
1248
+ source.href ? writeAttribute('xlink:href', source.href) : noop
1249
+ )
1250
+ )
1251
+ )
1252
+ )
1253
+ }
1254
+
1255
+ export function writeSourceRecords(
1256
+ record: DatasetRecord | ReuseRecord,
1257
+ rootEl: XmlElement
1258
+ ) {
1259
+ pipe(
1260
+ findNestedChildOrCreate(
1261
+ 'gmd:dataQualityInfo',
1262
+ 'gmd:DQ_DataQuality',
1263
+ 'gmd:lineage',
1264
+ 'gmd:LI_Lineage'
1265
+ ),
1266
+ appendSourceRecords(record.sourceRecords)
1267
+ )(rootEl)
1268
+ }
1269
+
1270
+ export function writeAssociatedRecords(
1271
+ record: DatasetRecord | ReuseRecord,
1272
+ rootEl: XmlElement
1273
+ ) {
1274
+ pipe(
1275
+ findOrCreateIdentification(),
1276
+ removeChildrenByName('gmd:aggregationInfo'),
1277
+ appendChildren(
1278
+ ...record.associatedRecords
1279
+ .filter((assoc) => assoc.uniqueIdentifier && assoc.associationType)
1280
+ .map((assoc) =>
1281
+ pipe(
1282
+ createNestedElement(
1283
+ 'gmd:aggregationInfo',
1284
+ 'gmd:MD_AggregateInformation'
1285
+ ),
1286
+ appendChildren(
1287
+ pipe(
1288
+ createNestedElement(
1289
+ 'gmd:aggregateDataSetIdentifier',
1290
+ 'gmd:MD_Identifier',
1291
+ 'gmd:code'
1292
+ ),
1293
+ writeCharacterString(assoc.uniqueIdentifier)
1294
+ ),
1295
+ pipe(
1296
+ createNestedElement(
1297
+ 'gmd:associationType',
1298
+ 'gmd:DS_AssociationTypeCode'
1299
+ ),
1300
+ writeAttribute(
1301
+ 'codeList',
1302
+ 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode'
1303
+ ),
1304
+ writeAttribute('codeListValue', assoc.associationType)
1305
+ )
1306
+ )
1307
+ )
1308
+ )
1309
+ )
1310
+ )(rootEl)
1311
+ }
1312
+
1234
1313
  export function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
1235
1314
  switch (endpoint.accessServiceProtocol.toLowerCase()) {
1236
1315
  case 'wfs':
@@ -1,4 +1,4 @@
1
- import { Injectable, InjectionToken, inject } from '@angular/core'
1
+ import { inject, Injectable, InjectionToken } from '@angular/core'
2
2
  import { toLang2, toLang3 } from '../../../../../../../libs/util/i18n/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { Location } from '@angular/common'
@@ -44,8 +44,12 @@ export class AuthService {
44
44
  window.location.href
45
45
  ).toString()
46
46
  )
47
- .replace('${lang2}', toLang2(this.translateService.currentLang))
48
- .replace('${lang3}', toLang3(this.translateService.currentLang))
47
+ .replace(
48
+ '${current_path}',
49
+ this.location.prepareExternalUrl(this.location.path())
50
+ )
51
+ .replace('${lang2}', toLang2(this.translateService.getCurrentLang()))
52
+ .replace('${lang3}', toLang3(this.translateService.getCurrentLang()))
49
53
  }
50
54
 
51
55
  get logoutUrl() {
@@ -55,7 +59,7 @@ export class AuthService {
55
59
  get settingsUrl() {
56
60
  return this.baseSettingsUrl.replace(
57
61
  '${lang3}',
58
- toLang3(this.translateService.currentLang)
62
+ toLang3(this.translateService.getCurrentLang())
59
63
  )
60
64
  }
61
65
  }
@@ -1,4 +1,4 @@
1
- import { Injectable, Injector, inject } from '@angular/core'
1
+ import { inject, Injectable, Injector } from '@angular/core'
2
2
  import type { Geometry } from 'geojson'
3
3
  import {
4
4
  ES_QUERY_FIELDS_PRIORITY,
@@ -11,6 +11,7 @@ import {
11
11
  AggregationsParams,
12
12
  FieldFilter,
13
13
  FieldFilters,
14
+ FieldSort,
14
15
  FilterQuery,
15
16
  FiltersAggregationParams,
16
17
  SortByField,
@@ -199,9 +200,27 @@ export class ElasticsearchService {
199
200
  }
200
201
 
201
202
  private buildPayloadSort(sortBy: SortByField): SortParams {
202
- if (sortBy === null) return undefined
203
- const fields = Array.isArray(sortBy[0]) ? sortBy : [sortBy]
204
- return fields.map((field) => ({ [field[1]]: field[0] }))
203
+ if (!sortBy || sortBy.length === 0) return undefined
204
+ const fields = Array.isArray(sortBy[0])
205
+ ? (sortBy as FieldSort[])
206
+ : [sortBy as FieldSort]
207
+ return fields.map((field) => {
208
+ // Sort by nested array of dates only works with the explicit syntax
209
+ if (field[1].endsWith('.date')) {
210
+ const nestedPath = field[1].slice(0, field[1].lastIndexOf('.date'))
211
+ return {
212
+ [field[1]]: {
213
+ order: field[0] as 'desc' | 'asc',
214
+ mode: field[0] === 'desc' ? 'max' : 'min',
215
+ missing: '_last',
216
+ nested: {
217
+ path: nestedPath,
218
+ },
219
+ },
220
+ }
221
+ }
222
+ return { [field[1]]: field[0] }
223
+ })
205
224
  }
206
225
 
207
226
  private injectLangInQueryStringFields(
@@ -234,7 +253,7 @@ export class ElasticsearchService {
234
253
  if (this.metadataLang) {
235
254
  const lang3 = toLang3(
236
255
  this.isCurrentSearchLang()
237
- ? this.translateService.currentLang
256
+ ? this.translateService.getCurrentLang()
238
257
  : this.metadataLang
239
258
  )
240
259
  return `lang${lang3}`
@@ -3,9 +3,10 @@ import {
3
3
  HttpErrorResponse,
4
4
  HttpHeaders,
5
5
  } from '@angular/common/http'
6
- import { Injectable, InjectionToken, inject } from '@angular/core'
6
+ import { inject, Injectable, InjectionToken } from '@angular/core'
7
7
  import {
8
8
  assertValidXml,
9
+ BaseConverter,
9
10
  findConverterForDocument,
10
11
  Gn4Converter,
11
12
  Gn4SearchResults,
@@ -61,6 +62,12 @@ export const DISABLE_DRAFT = new InjectionToken<boolean>('gnDisableDraft', {
61
62
  factory: () => false,
62
63
  })
63
64
 
65
+ export const DEFAULT_RECORD_CONVERTER = new InjectionToken<
66
+ BaseConverter<string>
67
+ >('defaultRecordConverter', {
68
+ factory: () => new Iso19139Converter(),
69
+ })
70
+
64
71
  @Injectable()
65
72
  export class Gn4Repository implements RecordsRepositoryInterface {
66
73
  private httpClient = inject(HttpClient)
@@ -72,6 +79,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
72
79
  private gn4LanguagesApi = inject(LanguagesApiService)
73
80
  private settingsService = inject(Gn4SettingsService)
74
81
  private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
82
+ private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
75
83
 
76
84
  _draftsChanged = new Subject<void>()
77
85
  draftsChanged$ = this._draftsChanged.asObservable()
@@ -319,8 +327,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
319
327
  return this.settingsService.allowEditHarvested$.pipe(
320
328
  map((allowEditHarvested) => {
321
329
  return (
322
- record.extras['edit'] &&
323
- (!record.extras['isHarvested'] || allowEditHarvested)
330
+ (this.platformService.supportsAuthentication() &&
331
+ record.extras?.['edit'] &&
332
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
333
+ false
324
334
  )
325
335
  })
326
336
  )
@@ -328,10 +338,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
328
338
 
329
339
  private canEdit(record: CatalogRecord, allowEditHarvested: boolean): boolean {
330
340
  return (
331
- this.platformService.supportsAuthentication() &&
332
- record.kind === 'dataset' &&
333
- record.extras['edit'] &&
334
- (!record.extras['isHarvested'] || allowEditHarvested)
341
+ (this.platformService.supportsAuthentication() &&
342
+ record.extras?.['edit'] &&
343
+ (!record.extras?.['isHarvested'] || allowEditHarvested)) ??
344
+ false
335
345
  )
336
346
  }
337
347
 
@@ -353,8 +363,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
353
363
  }
354
364
 
355
365
  openRecordForEdition(
356
- uniqueIdentifier: string
366
+ uniqueIdentifier: string,
367
+ disableDraft?: boolean
357
368
  ): Observable<[CatalogRecord, string, boolean] | null> {
369
+ this.disableDraft = disableDraft ?? this.disableDraft
358
370
  const draft$ = this.disableDraft
359
371
  ? of(null)
360
372
  : of(this.getRecordFromLocalStorage(uniqueIdentifier))
@@ -377,38 +389,52 @@ export class Gn4Repository implements RecordsRepositoryInterface {
377
389
  openRecordForDuplication(
378
390
  uniqueIdentifier: string
379
391
  ): Observable<[CatalogRecord, string, true] | null> {
380
- return this.gn4RecordsApi
381
- .create(
382
- uniqueIdentifier,
383
- '2',
384
- 'METADATA',
385
- '',
386
- false,
387
- undefined,
388
- true,
389
- false,
390
- undefined,
391
- 'body',
392
- false,
393
- {
394
- httpHeaderAccept: 'application/json',
395
- httpContentTypeSelected: 'application/json;charset=UTF-8',
396
- }
397
- )
398
- .pipe(
399
- switchMap((uniqueIdentifier) => {
400
- return this.getRecordAsXml(uniqueIdentifier)
401
- }),
402
- switchMap((xml) => {
403
- return from(
404
- findConverterForDocument(xml)
405
- .readRecord(xml)
406
- .then((record) => {
407
- return [record, xml, true] as [CatalogRecord, string, true]
408
- })
392
+ return this.platformService.getUserPermissionsByGroup().pipe(
393
+ map((permissions) => {
394
+ const groupId =
395
+ permissions.find((p) => p.canApprove)?.groupId?.toString() ??
396
+ permissions.find((p) => p.canEdit)?.groupId?.toString()
397
+ if (!groupId)
398
+ throw new Error(
399
+ 'Current user has no writable group to duplicate into'
400
+ )
401
+ return groupId
402
+ }),
403
+ switchMap((groupId) =>
404
+ this.gn4RecordsApi
405
+ .create(
406
+ uniqueIdentifier,
407
+ groupId,
408
+ 'METADATA',
409
+ '',
410
+ false,
411
+ undefined,
412
+ true,
413
+ false,
414
+ undefined,
415
+ 'body',
416
+ false,
417
+ {
418
+ httpHeaderAccept: 'application/json',
419
+ httpContentTypeSelected: 'application/json;charset=UTF-8',
420
+ }
421
+ )
422
+ .pipe(
423
+ switchMap((uniqueIdentifier) => {
424
+ return this.getRecordAsXml(uniqueIdentifier)
425
+ }),
426
+ switchMap((xml) => {
427
+ return from(
428
+ findConverterForDocument(xml)
429
+ .readRecord(xml)
430
+ .then((record) => {
431
+ return [record, xml, true] as [CatalogRecord, string, true]
432
+ })
433
+ )
434
+ })
409
435
  )
410
- })
411
436
  )
437
+ )
412
438
  }
413
439
 
414
440
  saveRecord(
@@ -605,10 +631,10 @@ export class Gn4Repository implements RecordsRepositoryInterface {
605
631
  record: CatalogRecord,
606
632
  referenceRecordSource?: string
607
633
  ): Observable<string> {
608
- // if there's a reference record, use that standard; otherwise, use iso19139
634
+ // if there's a reference record, use that standard; otherwise, use standard based on configuration or default
609
635
  const converter = referenceRecordSource
610
636
  ? findConverterForDocument(referenceRecordSource)
611
- : new Iso19139Converter()
637
+ : this.defaultConverter
612
638
  return from(converter.writeRecord(record, referenceRecordSource))
613
639
  }
614
640
 
@@ -52,7 +52,7 @@ export class OrganizationsFromGroupsService
52
52
  organisationsCount$ = this.organisations$.pipe(map((orgs) => orgs.length))
53
53
 
54
54
  private get lang3() {
55
- return toLang3(this.translateService.currentLang)
55
+ return toLang3(this.translateService.getCurrentLang())
56
56
  }
57
57
 
58
58
  constructor(
@@ -128,7 +128,7 @@ export class OrganizationsFromMetadataService
128
128
  ) {}
129
129
 
130
130
  private get langIndex() {
131
- return `lang${toLang3(this.translateService.currentLang)}`
131
+ return `lang${toLang3(this.translateService.getCurrentLang())}`
132
132
  }
133
133
 
134
134
  equalsNormalizedStrings(
@@ -1,5 +1,5 @@
1
1
  import { HttpClient, HttpEventType } from '@angular/common/http'
2
- import { Injectable, InjectionToken, inject } from '@angular/core'
2
+ import { inject, Injectable, InjectionToken } from '@angular/core'
3
3
  import {
4
4
  KeywordApiResponse,
5
5
  ThesaurusApiResponse,
@@ -12,11 +12,13 @@ import {
12
12
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
13
13
  import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
14
14
  import { UserModel } from '../../../../../../../libs/common/domain/src/lib/model/user/user.model'
15
+ import { GroupModel } from '../../../../../../../libs/common/domain/src/lib/model/user/group.model'
15
16
  import {
16
17
  PlatformServiceInterface,
17
18
  UploadEvent,
18
19
  } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
19
20
  import {
21
+ GroupsApiService,
20
22
  MeApiService,
21
23
  RecordsApiService,
22
24
  RegistriesApiService,
@@ -57,6 +59,7 @@ export const DISABLE_AUTH = new InjectionToken<boolean>('gnDisableAuth', {
57
59
  export class Gn4PlatformService implements PlatformServiceInterface {
58
60
  private meApi = inject(MeApiService)
59
61
  private usersApi = inject(UsersApiService)
62
+ private groupsApi = inject(GroupsApiService)
60
63
  private mapper = inject(Gn4PlatformMapper)
61
64
  private toolsApiService = inject(ToolsApiService)
62
65
  private registriesApiService = inject(RegistriesApiService)
@@ -97,7 +100,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
97
100
  private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
98
101
 
99
102
  private get lang3() {
100
- return toLang3(this.translateService.currentLang)
103
+ return toLang3(this.translateService.getCurrentLang())
101
104
  }
102
105
 
103
106
  constructor() {
@@ -155,6 +158,38 @@ export class Gn4PlatformService implements PlatformServiceInterface {
155
158
  return this.users$
156
159
  }
157
160
 
161
+ getUserPermissionsByGroup(): Observable<GroupModel[]> {
162
+ if (this.disableAuth) return of([])
163
+ return combineLatest([this.meApi.getMe(), this.groupsApi.getGroups()]).pipe(
164
+ map(([meResponse, groups]) => {
165
+ if (!meResponse) return []
166
+ if (meResponse.admin) {
167
+ return groups.map((group) => ({
168
+ groupId: group.id,
169
+ groupName: group.name,
170
+ isMember: true,
171
+ canEdit: true,
172
+ canApprove: true,
173
+ canAdministrate: true,
174
+ }))
175
+ }
176
+ const reviewerIds = meResponse.groupsWithReviewer ?? []
177
+ const editorIds = meResponse.groupsWithEditor ?? []
178
+ const memberIds = meResponse.groupsWithRegisteredUser ?? []
179
+ const adminIds = meResponse.groupsWithUserAdmin ?? []
180
+
181
+ return groups.map((group) => ({
182
+ groupId: group.id,
183
+ groupName: group.name,
184
+ isMember: memberIds.includes(group.id),
185
+ canEdit: editorIds.includes(group.id),
186
+ canApprove: reviewerIds.includes(group.id),
187
+ canAdministrate: adminIds.includes(group.id),
188
+ }))
189
+ })
190
+ )
191
+ }
192
+
158
193
  translateKey(key: string): Observable<string> {
159
194
  // if the key is a URI, use the registries API to look for the translation
160
195
  if (key.match(/^https?:\/\//)) {
@@ -104,15 +104,12 @@ export interface Keyword {
104
104
  type: KeywordType
105
105
  thesaurus?: ThesaurusModel
106
106
  bbox?: [number, number, number, number]
107
+ // thesaurus hierarchy path incl. the keyword as last element; unset for root-level keywords
108
+ hierarchyPath?: string[]
107
109
 
108
110
  translations?: KeywordTranslations
109
111
  }
110
112
 
111
- export interface INSPIRE_topic {
112
- value: string
113
- label: string
114
- }
115
-
116
113
  export interface ResourceIdentifier {
117
114
  code: string
118
115
  codeSpace?: string
@@ -254,10 +251,55 @@ export interface DatasetTemporalExtent {
254
251
  end?: Date
255
252
  }
256
253
 
254
+ /**
255
+ * Represents a source dataset referenced from a lineage entry.
256
+ */
257
+ export interface SourceRecord {
258
+ uuid?: string
259
+ title?: string
260
+ href?: string
261
+ }
262
+
263
+ marker('domain.record.associationType.crossReference')
264
+ marker('domain.record.associationType.largerWorkCitation')
265
+ marker('domain.record.associationType.partOfSeamlessDatabase')
266
+ marker('domain.record.associationType.stereoMate')
267
+ marker('domain.record.associationType.isComposedOf')
268
+ marker('domain.record.associationType.collectiveTitle')
269
+ marker('domain.record.associationType.series')
270
+ marker('domain.record.associationType.dependency')
271
+ marker('domain.record.associationType.revisionOf')
272
+
273
+ // DS_AssociationTypeCode values of the ISO19115-3 codelist, any unrecognized value
274
+ // being mapped to 'crossReference'
275
+ export const associationTypeValues = [
276
+ 'crossReference',
277
+ 'largerWorkCitation',
278
+ 'partOfSeamlessDatabase',
279
+ 'stereoMate',
280
+ 'isComposedOf',
281
+ 'collectiveTitle',
282
+ 'series',
283
+ 'dependency',
284
+ 'revisionOf',
285
+ ] as const
286
+
287
+ export type AssociationType = (typeof associationTypeValues)[number]
288
+
289
+ /**
290
+ * Represents another record associated with this one (e.g. a parent/sibling dataset).
291
+ */
292
+ export interface AssociatedRecord {
293
+ uniqueIdentifier: string
294
+ associationType: AssociationType
295
+ }
296
+
257
297
  export interface DatasetRecord extends BaseRecord {
258
298
  kind: 'dataset'
259
299
  status: RecordStatus
260
300
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
301
+ sourceRecords: Array<SourceRecord>
302
+ associatedRecords: Array<AssociatedRecord>
261
303
  onlineResources: Array<DatasetOnlineResource>
262
304
  spatialExtents: Array<DatasetSpatialExtent>
263
305
  temporalExtents: Array<DatasetTemporalExtent>
@@ -285,6 +327,8 @@ export interface ServiceRecord extends BaseRecord {
285
327
  export interface ReuseRecord extends BaseRecord {
286
328
  kind: 'reuse'
287
329
  lineage: string // Explanation of the origin of this record (e.g: how, why)"
330
+ sourceRecords: Array<SourceRecord>
331
+ associatedRecords: Array<AssociatedRecord>
288
332
  onlineResources: Array<DatasetOnlineResource>
289
333
  reuseType: ReuseType
290
334
  spatialExtents: Array<DatasetSpatialExtent>
@@ -6,9 +6,5 @@ export const SortByEnum: Record<string, SortByField> = {
6
6
  RELEVANCY: ['desc', '_score'],
7
7
  QUALITY_SCORE: ['desc', 'qualityScore'],
8
8
  CHANGE_DATE: ['desc', 'changeDate'],
9
- RESOURCE_DATES: [
10
- ['desc', 'revisionDateForResource'],
11
- ['desc', 'publicationDateForResource'],
12
- ['desc', 'creationDateForResource'],
13
- ],
9
+ RESOURCE_DATE: ['desc', 'resourceDate.date'],
14
10
  }
@@ -1,7 +1,8 @@
1
1
  export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
2
2
 
3
3
  export interface ThesaurusModel {
4
- id: string
4
+ // absent when the source provides no identifier for the thesaurus
5
+ id?: string
5
6
  name?: string
6
7
  url?: URL
7
8
  thesaurusKey?: string
@@ -0,0 +1,8 @@
1
+ export interface GroupModel {
2
+ groupId: number
3
+ groupName: string
4
+ isMember: boolean
5
+ canEdit: boolean
6
+ canApprove: boolean
7
+ canAdministrate: boolean
8
+ }
@@ -1 +1,2 @@
1
1
  export * from './user.model'
2
+ export * from './group.model'