geonetwork-ui 2.10.0-dev.f143bfdfa → 2.10.0-dev.f1c00d5fe

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 (142) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1331 -6228
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +186 -170
  4. package/index.d.ts.map +1 -1
  5. package/package.json +23 -33
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +11 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +22 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +22 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +1 -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 +1 -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/metadata.model.ts +1 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +4 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +13 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +45 -1
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -1
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +40 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +47 -1
  31. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -4
  32. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  33. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +12 -8
  34. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  35. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  36. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -22
  37. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -5
  38. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  39. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  40. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +57 -0
  41. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  42. package/src/libs/feature/editor/src/index.ts +1 -0
  43. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  44. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  45. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  46. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  47. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +7 -6
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +10 -8
  49. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  50. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  51. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  52. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  55. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  56. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +27 -16
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  67. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  68. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  69. package/src/libs/feature/editor/src/lib/fields.config.ts +354 -337
  70. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  71. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  72. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  73. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  74. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  75. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  76. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  77. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  78. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +221 -8
  79. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  80. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  81. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  82. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  83. package/src/libs/ui/dataviz/src/index.ts +0 -1
  84. package/src/libs/ui/elements/src/index.ts +1 -0
  85. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  86. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  87. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  88. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  89. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  90. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  91. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  92. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  93. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  94. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  95. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +4 -7
  96. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  97. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  98. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  99. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  100. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  101. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  102. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  103. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  104. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  105. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  106. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  107. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  108. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  109. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  110. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  111. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  112. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  113. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  114. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  115. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  116. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  117. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  118. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  119. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  120. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  121. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  122. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  123. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  124. package/src/libs/util/i18n/src/index.ts +0 -1
  125. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  126. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  127. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  128. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  129. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  130. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  131. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  132. package/style.css +0 -1
  133. package/tailwind.base.css +9 -0
  134. package/translations/de.json +102 -49
  135. package/translations/en.json +112 -59
  136. package/translations/es.json +76 -23
  137. package/translations/fr.json +109 -56
  138. package/translations/it.json +103 -50
  139. package/translations/nl.json +75 -22
  140. package/translations/pt.json +76 -23
  141. package/translations/sk.json +76 -23
  142. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -97,6 +97,7 @@ export const GEO2FRANCE_REUSE_ROILAYE_RECORD: ReuseRecord = {
97
97
  translations: {},
98
98
  lineage: null,
99
99
  sourceRecords: [],
100
+ associatedRecords: [],
100
101
  temporalExtents: [{ start: null, end: null }],
101
102
  reuseType: 'map',
102
103
  }
@@ -123,6 +123,7 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
123
123
  lineage: `Document d’urbanisme numérisé conformément aux prescriptions nationales du CNIG par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.
124
124
  Ce lot de données produit en 2019, a été numérisé à partir du PCI Vecteur de 2019 et contrôlé par le Service d'Information Géographique de l'Agglomération de la Région de Compiègne.`,
125
125
  sourceRecords: [],
126
+ associatedRecords: [],
126
127
  licenses: [
127
128
  {
128
129
  text: "En dépit des efforts et diligences mis en œuvre pour en vérifier la fiabilité, le fournisseur n’est pas en mesure de garantir l’exactitude, la mise à jour, l’intégrité, l’exhaustivité des données et en particulier que les données sont exemptes d'erreurs, notamment de localisation, d’identification ou d’actualisation ou d’imprécisions. Les données ne sont pas fournies en vue d'une utilisation particulière et aucune garantie quant à leur aptitude à un usage particulier n'est apportée par le fournisseur. En conséquence, les utilisateurs utilisent les données sous leur responsabilité pleine et entière, à leurs risques et périls, sans recours possible contre le fournisseur dont la responsabilité ne saurait être engagée du fait d’un dommage résultant directement ou indirectement de l’utilisation de ces données. En particulier, il appartient aux utilisateurs d’apprécier, sous leur seule responsabilité : – l'opportunité d'utiliser les données ; – la compatibilité des fichiers avec leurs systèmes informatiques ; – l’adéquation des données à leurs besoins ; – qu’ils disposent de la compétence suffisante pour utiliser les données ; – l’opportunité d’utiliser la documentation ou les outils d’analyse fournis ou préconisés, en relation avec l’utilisation des données, le cas échéant. Le fournisseur n’est en aucune façon responsable des éléments extérieurs aux données et notamment des outils d’analyse, matériels, logiciels, réseaux..., utilisés pour consulter et/ou traiter les données, même s’il a préconisé ces éléments. L’utilisateur veille à vérifier que l’actualité des informations mises à disposition est compatible avec l’usage qu’il en fait.",
@@ -547,6 +547,7 @@ export const GEOCAT_CH_DATASET_RECORD: DatasetRecord = {
547
547
  lineage:
548
548
  'Digitalisiert nach den administrativen Einheiten der Schweiz, die im Anhang des Übereinkommens erscheinen.',
549
549
  sourceRecords: [],
550
+ associatedRecords: [],
550
551
  licenses: [
551
552
  {
552
553
  text: "Licence passée entre l'Office fédéral de l'environnement (OFEV), et le canton de Fribourg, représenté par la Coordination SIT (SITel)",
@@ -207,6 +207,7 @@ export const GEORHENA_REUSE_SOLAIRE_RECORD: ReuseRecord = {
207
207
  },
208
208
  lineage: null,
209
209
  sourceRecords: [],
210
+ associatedRecords: [],
210
211
  temporalExtents: [
211
212
  {
212
213
  start: new Date('2016-01-01T00:00:00.000Z'),
@@ -2171,6 +2171,7 @@ export const METADATA_FOR_I18N_DATASET_RECORD: DatasetRecord = {
2171
2171
  status: 'completed',
2172
2172
  lineage: 'This is a statement about the lineage.',
2173
2173
  sourceRecords: [],
2174
+ associatedRecords: [],
2174
2175
  spatialRepresentation: 'vector',
2175
2176
  temporalExtents: [],
2176
2177
  updateFrequency: 'continual',
@@ -4236,6 +4237,7 @@ export const METADATA_FOR_I18N_NO_OTHERLANGUAGE: DatasetRecord = {
4236
4237
  status: 'completed',
4237
4238
  lineage: 'This is a statement about the lineage.',
4238
4239
  sourceRecords: [],
4240
+ associatedRecords: [],
4239
4241
  spatialRepresentation: 'vector',
4240
4242
  temporalExtents: [],
4241
4243
  updateFrequency: 'continual',
@@ -420,6 +420,28 @@ Cette donnée a été intégrée dans la base de données routière (BDR). Les D
420
420
  Ces données, intégrées dans la Banque de Données routières (BDR), ont fait l’objet d’une mise à jour massive en 2014-2015.
421
421
  Depuis, ce sont les Districts routiers qui assurent la tenue à jour de ces informations directement dans la base de données.`,
422
422
  sourceRecords: [],
423
+ associatedRecords: [
424
+ {
425
+ uuid: 'b1882436-3016-421e-9dfd-0326cca998f2',
426
+ associationType: 'crossReference',
427
+ },
428
+ {
429
+ uuid: 'b1741571-bbda-4732-a807-cbc36b64d54f',
430
+ associationType: 'crossReference',
431
+ },
432
+ {
433
+ uuid: 'bdcb789c-4b02-4c0c-863a-98dac4ed0240',
434
+ associationType: 'crossReference',
435
+ },
436
+ {
437
+ uuid: 'c0112b86-a6e3-4df3-9da1-2927376076f4',
438
+ associationType: 'partOfSeamlessDatabase',
439
+ },
440
+ {
441
+ uuid: '7451e2bd-22e8-4a74-a999-01c58b630369',
442
+ associationType: 'largerWorkCitation',
443
+ },
444
+ ],
423
445
  otherConstraints: [],
424
446
  overviews: [
425
447
  {
@@ -371,6 +371,7 @@ export const OPENDATASWISS_DATASET_RECORD: DatasetRecord = {
371
371
  ],
372
372
  lineage: '',
373
373
  sourceRecords: [],
374
+ associatedRecords: [],
374
375
  ownerOrganization: {
375
376
  name: 'Bundesamt für Energie',
376
377
  },
@@ -83,6 +83,7 @@ export const SEXTANT_BATHYMETRY_DATASET_RECORD: DatasetRecord = {
83
83
  Produit interne Ifremer.`,
84
84
  lineage: '',
85
85
  sourceRecords: [],
86
+ associatedRecords: [],
86
87
  ownerOrganization: null,
87
88
  recordUpdated: new Date('2020-06-03T22:34:05.000Z'),
88
89
  resourceUpdated: new Date('2020-06-03T22:34:05.000Z'),
@@ -324,6 +325,7 @@ export const SEXTANT_CURRENTS_DATASET_RECORD: CatalogRecord = {
324
325
  lineage:
325
326
  'Sallée, J.B.; Speer, K and Morrow, R. Southern Ocean fronts and their variability to climate modes, Journ. of Climate, 2008, Vol. 21(12), pp. 3020-3039',
326
327
  sourceRecords: [],
328
+ associatedRecords: [],
327
329
  onlineResources: [
328
330
  {
329
331
  name: 'Digital Object Identifier (DOI)',
@@ -69,6 +69,7 @@ export const VLAANDEREN_DATASET_RECORD: DatasetRecord = {
69
69
  topics: ['biodiversity'],
70
70
  lineage: '',
71
71
  sourceRecords: [],
72
+ associatedRecords: [],
72
73
  recordUpdated: new Date('2024-09-19T01:15:09.732Z'),
73
74
  resourceUpdated: new Date('2021-04-14T11:15+02:00'),
74
75
  status: 'completed',
@@ -455,5 +455,6 @@ export const WALLONIE_REUSE_SPW_RECORD: ReuseRecord = {
455
455
  href: 'https://metawal.wallonie.be/geonetwork/srv/api/records/ee965118-2416-4d48-b07e-bbc696f002c2',
456
456
  },
457
457
  ],
458
+ associatedRecords: [],
458
459
  temporalExtents: [],
459
460
  }
@@ -101,27 +101,68 @@ export const mapContact = (
101
101
  }
102
102
  }
103
103
 
104
- export const mapKeywords = (thesauri: Thesaurus[], language: string) => {
104
+ export interface KeywordTree {
105
+ default?: string[]
106
+ }
107
+
108
+ /**
109
+ * Resolves a keyword's ancestor path within its thesaurus tree, or null for a
110
+ * root-level keyword (no ancestors to show). Tree entries are `^`-joined label
111
+ * paths (default language); they aren't index-aligned with the keywords, so we
112
+ * match on the last segment (not by position/URI), shortest path on poly-hierarchy.
113
+ */
114
+ export const getKeywordHierarchyPath = (
115
+ keywordDefaultLabel: string,
116
+ tree?: KeywordTree | null
117
+ ): string[] | null => {
118
+ if (!keywordDefaultLabel || !tree?.default) return null
119
+ const shortest = tree.default
120
+ .map((entry) => entry.split('^'))
121
+ .filter((path) => path[path.length - 1] === keywordDefaultLabel)
122
+ .reduce(
123
+ (shortest, path) =>
124
+ !shortest || path.length < shortest.length ? path : shortest,
125
+ null
126
+ )
127
+ return shortest?.length > 1 ? shortest : null
128
+ }
129
+
130
+ export const mapKeywords = (
131
+ thesauri: Record<string, Thesaurus>,
132
+ language: string,
133
+ source?: SourceWithUnknownProps
134
+ ) => {
105
135
  const keywords = []
106
136
  for (const thesaurusId in thesauri) {
107
137
  const rawThesaurus = thesauri[thesaurusId]
138
+ // thesauri have an `id` (registered) or a `multilingualTitle` (cited);
139
+ // free-keyword groups only have a machine-key `title` like "otherKeywords-theme"
140
+ const { id, multilingualTitle, link = null } = rawThesaurus
108
141
  let thesaurus = null
109
- if (rawThesaurus.id) {
110
- const thesaurusSource: SourceWithUnknownProps = { ...rawThesaurus }
111
- const url = getAsUrl(selectField(thesaurusSource, 'link'))
112
- const name = selectField(thesaurusSource, 'title')
142
+ if (id || multilingualTitle) {
143
+ const url = getAsUrl(link)
144
+ const name = multilingualTitle
145
+ ? selectTranslatedValue<string>(multilingualTitle, language)
146
+ : rawThesaurus.title
113
147
  thesaurus = {
114
- id: rawThesaurus.id,
148
+ ...(id && { id }),
115
149
  ...(name && { name }),
116
150
  ...(url && { url }),
117
151
  }
118
152
  }
153
+ const tree =
154
+ source && selectField<KeywordTree>(source, `${thesaurusId}_tree`)
155
+ // free-keyword groups have no `theme` field; their type is in the group key
156
+ const typeCode =
157
+ rawThesaurus.theme || thesaurusId.match(/otherKeywords-(\w+)$/)?.[1]
119
158
  for (const keyword of rawThesaurus.keywords) {
159
+ const hierarchyPath = getKeywordHierarchyPath(keyword.default, tree)
120
160
  keywords.push({
121
161
  label: selectTranslatedValue<string>(keyword, language),
122
- type: getKeywordTypeFromKeywordTypeCode(rawThesaurus.theme),
162
+ type: getKeywordTypeFromKeywordTypeCode(typeCode),
123
163
  ...(keyword.link && { key: keyword.link }),
124
164
  ...(thesaurus && { thesaurus }),
165
+ ...(hierarchyPath && { hierarchyPath }),
125
166
  })
126
167
  }
127
168
  }
@@ -25,6 +25,7 @@ export class Gn4Converter extends BaseConverter<Gn4Record> {
25
25
  status: null,
26
26
  lineage: null,
27
27
  sourceRecords: [],
28
+ associatedRecords: [],
28
29
  recordUpdated: null,
29
30
  recordPublished: null,
30
31
  ownerOrganization: null,
@@ -14,7 +14,7 @@ import {
14
14
  toDate,
15
15
  } from './atomic-operations'
16
16
  import { MetadataUrlService } from './metadata-url.service'
17
- import { Injectable, inject } from '@angular/core'
17
+ import { inject, Injectable } from '@angular/core'
18
18
  import { getStatusFromStatusCode } from '../iso19139/utils/status.mapper'
19
19
  import { getUpdateFrequencyFromFrequencyCode } from '../iso19139/utils/update-frequency.mapper'
20
20
  import {
@@ -49,7 +49,7 @@ export class Gn4FieldMapper {
49
49
  private translateService = inject(TranslateService)
50
50
 
51
51
  private get getLocalizedIndexKey() {
52
- return `lang${toLang3(this.translateService.currentLang)}`
52
+ return `lang${toLang3(this.translateService.getCurrentLang())}`
53
53
  }
54
54
 
55
55
  protected fields: Record<string, EsFieldMapperFn> = {
@@ -84,7 +84,7 @@ export class Gn4FieldMapper {
84
84
  'resourceAbstractObject',
85
85
  this.getLocalizedIndexKey
86
86
  ),
87
- 'no title'
87
+ this.translateService.instant('record.metadata.abstract.empty')
88
88
  ),
89
89
  }),
90
90
  overview: (output, source) => {
@@ -240,8 +240,9 @@ export class Gn4FieldMapper {
240
240
  allKeywords: (output, source) => ({
241
241
  ...output,
242
242
  keywords: mapKeywords(
243
- selectField<Thesaurus[]>(source, 'allKeywords'),
244
- this.getLocalizedIndexKey
243
+ selectField<Record<string, Thesaurus>>(source, 'allKeywords'),
244
+ this.getLocalizedIndexKey,
245
+ source
245
246
  ),
246
247
  }),
247
248
  inspireTheme: (output, source) => ({
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import { Configuration } from '../../../../../../libs/data-access/gn4/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { toLang3 } from '../../../../../../libs/util/i18n/src'
@@ -13,7 +13,7 @@ export class MetadataUrlService {
13
13
  getUrl(uuid: string, apiPath: string = this.apiConfiguration.basePath) {
14
14
  const prefix = `${apiPath}/../`
15
15
  return `${prefix}${toLang3(
16
- this.translate.currentLang
16
+ this.translate.getCurrentLang()
17
17
  )}/catalog.search#/metadata/${uuid}`
18
18
  }
19
19
  }
@@ -62,6 +62,7 @@ type ThesaurusName = string
62
62
  export interface Thesaurus {
63
63
  id?: string
64
64
  title?: string
65
+ multilingualTitle?: MultilingualField
65
66
  theme?: string
66
67
  link?: string
67
68
  keywords: MultilingualField[]
@@ -3,6 +3,7 @@ import { XmlElement } from '@rgrove/parse-xml'
3
3
  import { Iso19139Converter } from '../iso19139'
4
4
  import { renameElements } from '../xml-utils'
5
5
  import {
6
+ readAssociatedRecords,
6
7
  readContacts,
7
8
  readContactsForResource,
8
9
  readDefaultLanguage,
@@ -20,6 +21,7 @@ import {
20
21
  readUniqueIdentifier,
21
22
  } from './read-parts'
22
23
  import {
24
+ writeAssociatedRecords,
23
25
  writeContacts,
24
26
  writeContactsForResource,
25
27
  writeDefaultLanguage,
@@ -56,6 +58,7 @@ export class Iso191153Converter extends Iso19139Converter {
56
58
  this.readers['landingPage'] = readLandingPage
57
59
  this.readers['lineage'] = readLineage
58
60
  this.readers['sourceRecords'] = readSourceRecords
61
+ this.readers['associatedRecords'] = readAssociatedRecords
59
62
  this.readers['onlineResources'] = readOnlineResources
60
63
  this.readers['defaultLanguage'] = readDefaultLanguage
61
64
  this.readers['otherLanguages'] = readOtherLanguages
@@ -76,6 +79,7 @@ export class Iso191153Converter extends Iso19139Converter {
76
79
  this.writers['landingPage'] = writeLandingPage
77
80
  this.writers['lineage'] = writeLineage
78
81
  this.writers['sourceRecords'] = writeSourceRecords
82
+ this.writers['associatedRecords'] = writeAssociatedRecords
79
83
  this.writers['onlineResources'] = writeOnlineResources
80
84
  this.writers['status'] = writeStatus
81
85
  this.writers['spatialRepresentation'] = writeSpatialRepresentation
@@ -18,6 +18,7 @@ import {
18
18
  pipe,
19
19
  } from '../function-utils'
20
20
  import {
21
+ extractAssociatedRecords,
21
22
  extractCharacterString,
22
23
  extractDatasetOnlineResources,
23
24
  extractDateTime,
@@ -30,6 +31,7 @@ import {
30
31
  findIdentification,
31
32
  } from '../iso19139/read-parts'
32
33
  import {
34
+ AssociatedRecord,
33
35
  Individual,
34
36
  LanguageCode,
35
37
  SourceRecord,
@@ -299,6 +301,17 @@ export function readSourceRecords(rootEl: XmlElement): SourceRecord[] {
299
301
  )
300
302
  }
301
303
 
304
+ export function readAssociatedRecords(rootEl: XmlElement): AssociatedRecord[] {
305
+ return pipe(
306
+ findIdentification(),
307
+ extractAssociatedRecords(
308
+ 'mri:associatedResource',
309
+ 'mri:MD_AssociatedResource',
310
+ pipe(findNestedElement('mri:metadataReference'), readAttribute('uuidref'))
311
+ )
312
+ )(rootEl)
313
+ }
314
+
302
315
  function extractDateInfo(
303
316
  type: 'creation' | 'revision' | 'publication'
304
317
  ): ChainableFunction<XmlElement, Date> {
@@ -3,6 +3,7 @@ import {
3
3
  DatasetRecord,
4
4
  Individual,
5
5
  LanguageCode,
6
+ ReuseRecord,
6
7
  } from '../../../../../../libs/common/domain/src/lib/model/record'
7
8
  import {
8
9
  allChildrenElement,
@@ -10,6 +11,7 @@ import {
10
11
  appendChildTree,
11
12
  createChild,
12
13
  createElement,
14
+ createNestedElement,
13
15
  findChildElement,
14
16
  findChildOrCreate,
15
17
  findChildrenElement,
@@ -566,9 +568,51 @@ export function writeOtherLanguages(record: DatasetRecord, rootEl: XmlElement) {
566
568
  )(rootEl)
567
569
  }
568
570
 
569
- export function writeSourceRecords(record: DatasetRecord, rootEl: XmlElement) {
571
+ export function writeSourceRecords(
572
+ record: DatasetRecord | ReuseRecord,
573
+ rootEl: XmlElement
574
+ ) {
570
575
  pipe(
571
576
  findNestedChildOrCreate('mdb:resourceLineage', 'mrl:LI_Lineage'),
572
577
  appendSourceRecords(record.sourceRecords)
573
578
  )(rootEl)
574
579
  }
580
+
581
+ export function writeAssociatedRecords(
582
+ record: DatasetRecord | ReuseRecord,
583
+ rootEl: XmlElement
584
+ ) {
585
+ pipe(
586
+ findOrCreateIdentification(),
587
+ removeChildrenByName('mri:associatedResource'),
588
+ appendChildren(
589
+ ...record.associatedRecords
590
+ .filter((assoc) => assoc.uuid && assoc.associationType)
591
+ .map((assoc) =>
592
+ pipe(
593
+ createNestedElement(
594
+ 'mri:associatedResource',
595
+ 'mri:MD_AssociatedResource'
596
+ ),
597
+ appendChildren(
598
+ pipe(
599
+ createNestedElement(
600
+ 'mri:associationType',
601
+ 'mri:DS_AssociationTypeCode'
602
+ ),
603
+ writeAttribute(
604
+ 'codeList',
605
+ 'http://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#DS_AssociationTypeCode'
606
+ ),
607
+ writeAttribute('codeListValue', assoc.associationType)
608
+ ),
609
+ pipe(
610
+ createElement('mri:metadataReference'),
611
+ writeAttribute('uuidref', assoc.uuid)
612
+ )
613
+ )
614
+ )
615
+ )
616
+ )
617
+ )(rootEl)
618
+ }
@@ -21,6 +21,7 @@ import {
21
21
  } from '../xml-utils'
22
22
  import {
23
23
  readAbstract,
24
+ readAssociatedRecords,
24
25
  readContacts,
25
26
  readContactsForResource,
26
27
  readDefaultLanguage,
@@ -53,6 +54,7 @@ import {
53
54
  } from './read-parts'
54
55
  import {
55
56
  writeAbstract,
57
+ writeAssociatedRecords,
56
58
  writeContacts,
57
59
  writeContactsForResource,
58
60
  writeDefaultLanguage,
@@ -115,6 +117,7 @@ export class Iso19139Converter extends BaseConverter<string> {
115
117
  overviews: readOverviews,
116
118
  lineage: readLineage,
117
119
  sourceRecords: readSourceRecords,
120
+ associatedRecords: readAssociatedRecords,
118
121
  onlineResources: readOnlineResources,
119
122
  temporalExtents: readTemporalExtents,
120
123
  spatialExtents: readSpatialExtents,
@@ -157,6 +160,7 @@ export class Iso19139Converter extends BaseConverter<string> {
157
160
  overviews: writeGraphicOverviews,
158
161
  lineage: writeLineage,
159
162
  sourceRecords: writeSourceRecords,
163
+ associatedRecords: writeAssociatedRecords,
160
164
  onlineResources: writeOnlineResources,
161
165
  temporalExtents: writeTemporalExtents,
162
166
  spatialExtents: writeSpatialExtents,
@@ -205,7 +209,7 @@ export class Iso19139Converter extends BaseConverter<string> {
205
209
  record.contacts.map((c) => fixLanguages(c.organization))
206
210
  record.contactsForResource.map((c) => fixLanguages(c.organization))
207
211
  fixLanguages(record.ownerOrganization)
208
- if (record.kind === 'dataset') {
212
+ if (record.kind === 'dataset' || record.kind === 'reuse') {
209
213
  record.spatialExtents.map(fixLanguages)
210
214
  }
211
215
 
@@ -300,6 +304,7 @@ export class Iso19139Converter extends BaseConverter<string> {
300
304
  const temporalExtents = this.readers['temporalExtents'](rootEl, tr)
301
305
  const lineage = this.readers['lineage'](rootEl, tr)
302
306
  const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
307
+ const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
303
308
  const updateFrequency = this.readers['updateFrequency'](rootEl, tr)
304
309
 
305
310
  return this.afterRecordRead({
@@ -308,6 +313,7 @@ export class Iso19139Converter extends BaseConverter<string> {
308
313
  status,
309
314
  lineage,
310
315
  ...(sourceRecords && { sourceRecords }),
316
+ associatedRecords,
311
317
  ...(spatialRepresentation && { spatialRepresentation }),
312
318
  temporalExtents,
313
319
  updateFrequency,
@@ -316,6 +322,7 @@ export class Iso19139Converter extends BaseConverter<string> {
316
322
  } else if (kind === 'reuse') {
317
323
  const lineage = this.readers['lineage'](rootEl, tr)
318
324
  const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
325
+ const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
319
326
  const temporalExtents = this.readers['temporalExtents'](rootEl, tr)
320
327
  const reuseType = this.readers['reuseType'](rootEl, tr)
321
328
 
@@ -324,6 +331,7 @@ export class Iso19139Converter extends BaseConverter<string> {
324
331
  kind,
325
332
  lineage,
326
333
  ...(sourceRecords && { sourceRecords }),
334
+ associatedRecords,
327
335
  temporalExtents,
328
336
  reuseType,
329
337
  } as ReuseRecord)
@@ -408,6 +416,8 @@ export class Iso19139Converter extends BaseConverter<string> {
408
416
 
409
417
  if (record.kind === 'dataset') {
410
418
  fieldChanged('status') && this.writers['status'](record, rootEl)
419
+ }
420
+ if (record.kind === 'dataset' || record.kind === 'reuse') {
411
421
  fieldChanged('updateFrequency') &&
412
422
  this.writers['updateFrequency'](record, rootEl)
413
423
  fieldChanged('spatialRepresentation') &&
@@ -421,6 +431,8 @@ export class Iso19139Converter extends BaseConverter<string> {
421
431
  this.writers['lineage'](record, rootEl)
422
432
  fieldChanged('sourceRecords') &&
423
433
  this.writers['sourceRecords'](record, rootEl)
434
+ fieldChanged('associatedRecords') &&
435
+ this.writers['associatedRecords'](record, rootEl)
424
436
  }
425
437
 
426
438
  fieldChanged('otherLanguages') &&
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AssociatedRecord,
2
3
  Constraint,
3
4
  ConstraintTranslations,
4
5
  DatasetOnlineResource,
@@ -927,6 +928,45 @@ export function readSourceRecords(rootEl: XmlElement): SourceRecord[] {
927
928
  )
928
929
  }
929
930
 
931
+ export function extractAssociatedRecords(
932
+ containerName: string,
933
+ aggregateName: string,
934
+ readUuid: ChainableFunction<XmlElement, string>
935
+ ): ChainableFunction<XmlElement, AssociatedRecord[]> {
936
+ return pipe(
937
+ findChildrenElement(containerName, false),
938
+ mapArray((el) => {
939
+ const aggregateEl = findChildElement(aggregateName, false)(el)
940
+ const uuid = readUuid(aggregateEl)
941
+ const associationType = pipe(
942
+ findNestedElement('gmd:associationType', 'gmd:DS_AssociationTypeCode'),
943
+ readAttribute('codeListValue')
944
+ )(aggregateEl)
945
+ if (!uuid || !associationType) return null
946
+ return { uuid, associationType }
947
+ }),
948
+ filterArray((r): r is AssociatedRecord => r !== null)
949
+ )
950
+ }
951
+
952
+ export function readAssociatedRecords(rootEl: XmlElement): AssociatedRecord[] {
953
+ return pipe(
954
+ findIdentification(),
955
+ extractAssociatedRecords(
956
+ 'gmd:aggregationInfo',
957
+ 'gmd:MD_AggregateInformation',
958
+ pipe(
959
+ findNestedElement(
960
+ 'gmd:aggregateDataSetIdentifier',
961
+ 'gmd:MD_Identifier',
962
+ 'gmd:code'
963
+ ),
964
+ extractCharacterString()
965
+ )
966
+ )
967
+ )(rootEl)
968
+ }
969
+
930
970
  export function readUpdateFrequency(rootEl: XmlElement): UpdateFrequency {
931
971
  return pipe(
932
972
  findIdentification(),
@@ -1252,7 +1252,10 @@ export function appendSourceRecords(
1252
1252
  )
1253
1253
  }
1254
1254
 
1255
- export function writeSourceRecords(record: DatasetRecord, rootEl: XmlElement) {
1255
+ export function writeSourceRecords(
1256
+ record: DatasetRecord | ReuseRecord,
1257
+ rootEl: XmlElement
1258
+ ) {
1256
1259
  pipe(
1257
1260
  findNestedChildOrCreate(
1258
1261
  'gmd:dataQualityInfo',
@@ -1264,6 +1267,49 @@ export function writeSourceRecords(record: DatasetRecord, rootEl: XmlElement) {
1264
1267
  )(rootEl)
1265
1268
  }
1266
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.uuid && 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.uuid)
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
+
1267
1313
  export function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
1268
1314
  switch (endpoint.accessServiceProtocol.toLowerCase()) {
1269
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'
@@ -48,8 +48,8 @@ export class AuthService {
48
48
  '${current_path}',
49
49
  this.location.prepareExternalUrl(this.location.path())
50
50
  )
51
- .replace('${lang2}', toLang2(this.translateService.currentLang))
52
- .replace('${lang3}', toLang3(this.translateService.currentLang))
51
+ .replace('${lang2}', toLang2(this.translateService.getCurrentLang()))
52
+ .replace('${lang3}', toLang3(this.translateService.getCurrentLang()))
53
53
  }
54
54
 
55
55
  get logoutUrl() {
@@ -59,7 +59,7 @@ export class AuthService {
59
59
  get settingsUrl() {
60
60
  return this.baseSettingsUrl.replace(
61
61
  '${lang3}',
62
- toLang3(this.translateService.currentLang)
62
+ toLang3(this.translateService.getCurrentLang())
63
63
  )
64
64
  }
65
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,
@@ -234,7 +234,7 @@ export class ElasticsearchService {
234
234
  if (this.metadataLang) {
235
235
  const lang3 = toLang3(
236
236
  this.isCurrentSearchLang()
237
- ? this.translateService.currentLang
237
+ ? this.translateService.getCurrentLang()
238
238
  : this.metadataLang
239
239
  )
240
240
  return `lang${lang3}`