geonetwork-ui 2.2.0-dev.6695ae4d → 2.2.0-dev.6f0f0ee9

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 (126) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/gn4/atomic-operations.mjs +27 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.mjs +8 -8
  3. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.mjs +2 -2
  4. package/esm2022/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.mjs +1 -1
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.mjs +14 -0
  6. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/converter.mjs +7 -9
  7. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +30 -8
  8. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +31 -14
  9. package/esm2022/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.mjs +10 -3
  10. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.mjs +14 -5
  11. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +33 -8
  12. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  13. package/esm2022/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.mjs +1 -1
  14. package/esm2022/libs/common/domain/src/lib/platform.service.interface.mjs +1 -1
  15. package/esm2022/libs/feature/map/src/lib/map-context/map-context.service.mjs +9 -1
  16. package/esm2022/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.mjs +3 -3
  17. package/esm2022/libs/feature/search/src/lib/utils/service/fields.mjs +41 -26
  18. package/esm2022/libs/feature/search/src/lib/utils/service/fields.service.mjs +11 -9
  19. package/esm2022/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.mjs +3 -3
  20. package/esm2022/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.mjs +3 -3
  21. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +7 -7
  22. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +3 -4
  23. package/esm2022/libs/ui/elements/src/lib/thumbnail/thumbnail.component.mjs +3 -3
  24. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +4 -4
  25. package/esm2022/libs/ui/inputs/src/lib/button/button.component.mjs +3 -3
  26. package/esm2022/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.mjs +3 -3
  27. package/esm2022/libs/ui/inputs/src/lib/text-area/text-area.component.mjs +3 -3
  28. package/esm2022/libs/ui/inputs/src/lib/ui-inputs.module.mjs +6 -6
  29. package/esm2022/translations/de.json +2 -1
  30. package/esm2022/translations/en.json +2 -1
  31. package/esm2022/translations/es.json +2 -1
  32. package/esm2022/translations/fr.json +2 -1
  33. package/esm2022/translations/it.json +2 -1
  34. package/esm2022/translations/nl.json +2 -1
  35. package/esm2022/translations/pt.json +2 -1
  36. package/fesm2022/geonetwork-ui.mjs +267 -122
  37. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  38. package/libs/api/metadata-converter/src/lib/gn4/atomic-operations.d.ts +2 -0
  39. package/libs/api/metadata-converter/src/lib/gn4/atomic-operations.d.ts.map +1 -1
  40. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  41. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts +3 -5
  42. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts.map +1 -1
  43. package/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.d.ts +3 -0
  44. package/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.d.ts.map +1 -0
  45. package/libs/api/metadata-converter/src/lib/iso19139/converter.d.ts.map +1 -1
  46. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +2 -3
  47. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  48. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +1 -1
  49. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  50. package/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.d.ts.map +1 -1
  51. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.d.ts +1 -1
  52. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.d.ts.map +1 -1
  53. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts +9 -2
  54. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  55. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +13 -2
  56. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  57. package/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.d.ts +1 -0
  58. package/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.d.ts.map +1 -1
  59. package/libs/common/domain/src/lib/platform.service.interface.d.ts +1 -1
  60. package/libs/common/domain/src/lib/platform.service.interface.d.ts.map +1 -1
  61. package/libs/feature/map/src/lib/map-context/map-context.service.d.ts +1 -0
  62. package/libs/feature/map/src/lib/map-context/map-context.service.d.ts.map +1 -1
  63. package/libs/feature/search/src/lib/utils/service/fields.d.ts +21 -8
  64. package/libs/feature/search/src/lib/utils/service/fields.d.ts.map +1 -1
  65. package/libs/feature/search/src/lib/utils/service/fields.service.d.ts.map +1 -1
  66. package/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.d.ts +1 -1
  67. package/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.d.ts.map +1 -1
  68. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts +3 -3
  69. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts.map +1 -1
  70. package/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.d.ts.map +1 -1
  71. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +13 -13
  72. package/libs/ui/inputs/src/lib/button/button.component.d.ts +1 -1
  73. package/libs/ui/inputs/src/lib/button/button.component.d.ts.map +1 -1
  74. package/libs/ui/inputs/src/lib/text-area/text-area.component.d.ts +1 -1
  75. package/libs/ui/inputs/src/lib/text-area/text-area.component.d.ts.map +1 -1
  76. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts +40 -40
  77. package/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +55 -2
  80. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +50 -6
  81. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +203 -20
  82. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +29 -0
  83. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +11 -8
  84. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts +1 -1
  85. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +3 -5
  86. package/src/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.ts +16 -0
  87. package/src/libs/api/metadata-converter/src/lib/iso19139/converter.ts +5 -9
  88. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +53 -19
  89. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +78 -36
  90. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +22 -2
  91. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts +15 -4
  92. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +47 -8
  93. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +16 -3
  94. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +1 -0
  95. package/src/libs/common/domain/src/lib/platform.service.interface.ts +1 -4
  96. package/src/libs/common/fixtures/src/lib/gn4/groups.fixtures.ts +1 -1
  97. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +50 -10
  98. package/src/libs/feature/map/src/lib/map-context/map-context.service.ts +10 -0
  99. package/src/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.html +3 -4
  100. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +21 -16
  101. package/src/libs/feature/search/src/lib/utils/service/fields.ts +43 -34
  102. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.html +1 -1
  103. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.css +5 -0
  104. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.html +39 -0
  105. package/src/libs/ui/elements/src/lib/markdown-editor/markdown-editor.component.ts +58 -0
  106. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +1 -0
  107. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +6 -6
  108. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +6 -3
  109. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +2 -3
  110. package/src/libs/ui/elements/src/lib/thumbnail/thumbnail.component.html +2 -2
  111. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +1 -1
  112. package/src/libs/ui/inputs/src/lib/button/button.component.ts +1 -0
  113. package/src/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.css +3 -0
  114. package/src/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.html +1 -1
  115. package/src/libs/ui/inputs/src/lib/text-area/text-area.component.ts +1 -0
  116. package/src/libs/ui/inputs/src/lib/ui-inputs.module.ts +2 -2
  117. package/tailwind.base.config.js +3 -0
  118. package/tailwind.base.css +2 -2
  119. package/translations/de.json +2 -1
  120. package/translations/en.json +2 -1
  121. package/translations/es.json +2 -1
  122. package/translations/fr.json +2 -1
  123. package/translations/it.json +2 -1
  124. package/translations/nl.json +2 -1
  125. package/translations/pt.json +2 -1
  126. package/translations/sk.json +2 -1
@@ -29,7 +29,7 @@ import {
29
29
  writeSecurityConstraints,
30
30
  writeSpatialRepresentation,
31
31
  writeStatus,
32
- writeThemes,
32
+ writeTopics,
33
33
  writeTitle,
34
34
  writeUniqueIdentifier,
35
35
  writeUpdateFrequency,
@@ -56,7 +56,6 @@ import {
56
56
  readSpatialRepresentation,
57
57
  readStatus,
58
58
  readTemporalExtents,
59
- readThemes,
60
59
  readTitle,
61
60
  readUniqueIdentifier,
62
61
  readUpdateFrequency,
@@ -76,16 +75,13 @@ export function toModel(xml: string): CatalogRecord {
76
75
  const recordUpdated = readRecordUpdated(rootEl)
77
76
  const recordCreated = recordUpdated
78
77
  const keywords = readKeywords(rootEl)
79
- const themes = readThemes(rootEl)
78
+ const topics = readIsoTopics(rootEl)
80
79
  const legalConstraints = readLegalConstraints(rootEl)
81
80
  const otherConstraints = readOtherConstraints(rootEl)
82
81
  const securityConstraints = readSecurityConstraints(rootEl)
83
82
  const licenses = readLicenses(rootEl)
84
83
  const overviews = readOverviews(rootEl)
85
84
 
86
- // not used yet
87
- const isoTopics = readIsoTopics(rootEl)
88
-
89
85
  if (kind === 'dataset') {
90
86
  const status = readStatus(rootEl)
91
87
  const datasetCreated = readDatasetCreated(rootEl)
@@ -109,7 +105,7 @@ export function toModel(xml: string): CatalogRecord {
109
105
  contacts,
110
106
  contactsForResource: [], // FIXME: is that really useful??
111
107
  keywords,
112
- themes,
108
+ topics,
113
109
  licenses,
114
110
  legalConstraints,
115
111
  securityConstraints,
@@ -136,7 +132,7 @@ export function toModel(xml: string): CatalogRecord {
136
132
  ownerOrganization,
137
133
  contacts,
138
134
  keywords,
139
- themes,
135
+ topics,
140
136
  licenses,
141
137
  legalConstraints,
142
138
  securityConstraints,
@@ -168,7 +164,7 @@ export function toXml(record: CatalogRecord, originalXml?: string): string {
168
164
  writeAbstract(record, rootEl)
169
165
  fieldChanged('contacts') && writeContacts(record, rootEl)
170
166
  fieldChanged('keywords') && writeKeywords(record, rootEl)
171
- fieldChanged('themes') && writeThemes(record, rootEl)
167
+ fieldChanged('topics') && writeTopics(record, rootEl)
172
168
  fieldChanged('legalConstraints') && writeLegalConstraints(record, rootEl)
173
169
  fieldChanged('securityConstraints') &&
174
170
  writeSecurityConstraints(record, rootEl)
@@ -5,6 +5,8 @@ import {
5
5
  DatasetTemporalExtent,
6
6
  GraphicOverview,
7
7
  Individual,
8
+ Keyword,
9
+ KeywordThesaurus,
8
10
  Organization,
9
11
  RecordKind,
10
12
  RecordStatus,
@@ -39,6 +41,7 @@ import {
39
41
  } from '../function-utils'
40
42
  import { getRoleFromRoleCode } from './codelists/role.mapper'
41
43
  import { matchMimeType, matchProtocol } from '../common/distribution.mapper'
44
+ import { getKeywordTypeFromKeywordTypeCode } from './codelists/keyword.mapper'
42
45
 
43
46
  function extractCharacterString(): ChainableFunction<XmlElement, string> {
44
47
  return pipe(
@@ -552,29 +555,60 @@ export function readContacts(rootEl: XmlElement): Individual[] {
552
555
  )(rootEl)
553
556
  }
554
557
 
555
- function readKeywordsOfType(isTheme: boolean) {
556
- return pipe(
557
- findIdentification(),
558
- findNestedElements('gmd:descriptiveKeywords', 'gmd:MD_Keywords'),
559
- filterArray(
560
- pipe(
561
- findChildrenElement('gmd:MD_KeywordTypeCode'),
562
- mapArray(readAttribute('codeListValue')),
563
- map((values) => isTheme === values.indexOf('theme') > -1)
564
- )
565
- ),
566
- mapArray(findChildrenElement('gmd:keyword')),
567
- flattenArray(),
568
- mapArray(extractCharacterString())
558
+ // from gmd:thesaurusName
559
+ function readThesaurus(rootEl: XmlElement): KeywordThesaurus {
560
+ if (!rootEl) return null
561
+
562
+ const findIdentifier = findNestedElement(
563
+ 'gmd:CI_Citation',
564
+ 'gmd:identifier',
565
+ 'gmd:MD_Identifier',
566
+ 'gmd:code'
569
567
  )
568
+ const id = pipe(findIdentifier, extractCharacterString())(rootEl)
569
+ const url = pipe(findIdentifier, extractUrl())(rootEl)
570
+ const name = pipe(
571
+ findNestedElement('gmd:CI_Citation', 'gmd:title'),
572
+ extractCharacterString()
573
+ )(rootEl)
574
+ return {
575
+ id,
576
+ ...(name && { name }),
577
+ ...(url && { url }),
578
+ }
570
579
  }
571
580
 
572
- export function readKeywords(rootEl: XmlElement): string[] {
573
- return readKeywordsOfType(false)(rootEl)
581
+ // from gmd:MD_Keywords
582
+ function readKeywordGroup(rootEl: XmlElement): Keyword[] {
583
+ const type = pipe(
584
+ findChildrenElement('gmd:MD_KeywordTypeCode'),
585
+ mapArray(readAttribute('codeListValue')),
586
+ map((values) => getKeywordTypeFromKeywordTypeCode(values[0]))
587
+ )(rootEl)
588
+ const thesaurus = pipe(
589
+ findNestedElement('gmd:thesaurusName'),
590
+ readThesaurus
591
+ )(rootEl)
592
+ return pipe(
593
+ findChildrenElement('gmd:keyword'),
594
+ mapArray((el) => {
595
+ const label = extractCharacterString()(el)
596
+ return {
597
+ ...(thesaurus ? { thesaurus } : {}),
598
+ label,
599
+ type,
600
+ } as Keyword
601
+ })
602
+ )(rootEl)
574
603
  }
575
604
 
576
- export function readThemes(rootEl: XmlElement): string[] {
577
- return readKeywordsOfType(true)(rootEl)
605
+ export function readKeywords(rootEl: XmlElement): Keyword[] {
606
+ return pipe(
607
+ findIdentification(),
608
+ findNestedElements('gmd:descriptiveKeywords', 'gmd:MD_Keywords'),
609
+ mapArray(readKeywordGroup),
610
+ flattenArray()
611
+ )(rootEl)
578
612
  }
579
613
 
580
614
  export function readStatus(rootEl: XmlElement): RecordStatus {
@@ -627,7 +661,7 @@ export function readLicenses(rootEl: XmlElement): Constraint[] {
627
661
  export function readIsoTopics(rootEl: XmlElement): string[] {
628
662
  return pipe(
629
663
  findIdentification(),
630
- findChildrenElement('gmd:MD_TopicCategoryCode', false),
664
+ findChildrenElement('gmd:MD_TopicCategoryCode', true),
631
665
  mapArray(readText())
632
666
  )(rootEl)
633
667
  }
@@ -5,6 +5,8 @@ import {
5
5
  DatasetRecord,
6
6
  DatasetServiceDistribution,
7
7
  Individual,
8
+ Keyword,
9
+ KeywordThesaurus,
8
10
  RecordStatus,
9
11
  Role,
10
12
  ServiceEndpoint,
@@ -25,6 +27,7 @@ import {
25
27
  findNestedElement,
26
28
  findNestedElements,
27
29
  readAttribute,
30
+ readText,
28
31
  removeAllChildren,
29
32
  removeChildren,
30
33
  removeChildrenByName,
@@ -326,47 +329,78 @@ function appendCitationDate(date, type: 'revision' | 'creation') {
326
329
  )
327
330
  }
328
331
 
329
- function removeKeywords(type: string | null) {
330
- return removeChildren(
331
- pipe(
332
- findNestedElements('gmd:descriptiveKeywords'),
333
- filterArray(
334
- pipe(
335
- findNestedElement(
336
- 'gmd:MD_Keywords',
337
- 'gmd:type',
338
- 'gmd:MD_KeywordTypeCode'
339
- ),
340
- readAttribute('codeListValue'),
341
- // if a specific type is targeted, compare with it; otherwise remove keywords if they have no type defined
342
- map((typeValue) => (type !== null ? type === typeValue : true))
332
+ function removeKeywords() {
333
+ return removeChildren(pipe(findNestedElements('gmd:descriptiveKeywords')))
334
+ }
335
+
336
+ // returns a <gmd:thesaurusName> element
337
+ function createThesaurus(thesaurus: KeywordThesaurus) {
338
+ return pipe(
339
+ createElement('gmd:thesaurusName'),
340
+ createChild('gmd:CI_Citation'),
341
+ appendChildren(
342
+ pipe(
343
+ createElement('gmd:title'),
344
+ writeCharacterString(thesaurus.name || thesaurus.id)
345
+ ),
346
+ pipe(
347
+ createElement('gmd:identifier'),
348
+ createChild('gmd:MD_Identifier'),
349
+ appendChildren(
350
+ pipe(
351
+ createElement('gmd:code'),
352
+ thesaurus.url
353
+ ? writeAnchor(thesaurus.url, thesaurus.id)
354
+ : writeCharacterString(thesaurus.id)
355
+ )
343
356
  )
344
357
  )
345
358
  )
346
359
  )
347
360
  }
348
361
 
349
- function appendKeywords(keywords: string[], type: string | null) {
362
+ function appendKeywords(keywords: Keyword[]) {
363
+ const keywordsByThesaurus: Keyword[][] = keywords.reduce((acc, keyword) => {
364
+ const thesaurusId = keyword.thesaurus?.id
365
+ const type = keyword.type
366
+ let existingGroup = acc.find((group) =>
367
+ group[0].thesaurus
368
+ ? group[0].thesaurus.id === thesaurusId
369
+ : group[0].type === type
370
+ )
371
+ if (!existingGroup) {
372
+ existingGroup = []
373
+ acc.push(existingGroup)
374
+ }
375
+ existingGroup.push(keyword)
376
+ return acc
377
+ }, [])
350
378
  return appendChildren(
351
- pipe(
352
- createElement('gmd:descriptiveKeywords'),
353
- createChild('gmd:MD_Keywords'),
354
- type !== null
355
- ? appendChildren(
379
+ ...keywordsByThesaurus.map((keywords) =>
380
+ pipe(
381
+ createElement('gmd:descriptiveKeywords'),
382
+ createChild('gmd:MD_Keywords'),
383
+ appendChildren(
384
+ pipe(
385
+ createElement('gmd:type'),
386
+ createChild('gmd:MD_KeywordTypeCode'),
387
+ addAttribute(
388
+ 'codeList',
389
+ 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_KeywordTypeCode'
390
+ ),
391
+ addAttribute('codeListValue', keywords[0].type)
392
+ )
393
+ ),
394
+ keywords[0].thesaurus
395
+ ? appendChildren(createThesaurus(keywords[0].thesaurus))
396
+ : noop,
397
+ appendChildren(
398
+ ...keywords.map((keyword) =>
356
399
  pipe(
357
- createElement('gmd:type'),
358
- createChild('gmd:MD_KeywordTypeCode'),
359
- addAttribute(
360
- 'codeList',
361
- 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_KeywordTypeCode'
362
- ),
363
- addAttribute('codeListValue', type)
400
+ createElement('gmd:keyword'),
401
+ writeCharacterString(keyword.label)
364
402
  )
365
403
  )
366
- : noop,
367
- appendChildren(
368
- ...keywords.map((keyword) =>
369
- pipe(createElement('gmd:keyword'), writeCharacterString(keyword))
370
404
  )
371
405
  )
372
406
  )
@@ -740,16 +774,24 @@ export function writeContacts(record: CatalogRecord, rootEl: XmlElement) {
740
774
  export function writeKeywords(record: CatalogRecord, rootEl: XmlElement) {
741
775
  pipe(
742
776
  findOrCreateIdentification(),
743
- removeKeywords(null),
744
- appendKeywords(record.keywords, null)
777
+ removeKeywords(),
778
+ appendKeywords(record.keywords)
745
779
  )(rootEl)
746
780
  }
747
781
 
748
- export function writeThemes(record: CatalogRecord, rootEl: XmlElement) {
782
+ export function writeTopics(record: CatalogRecord, rootEl: XmlElement) {
749
783
  pipe(
750
784
  findOrCreateIdentification(),
751
- removeKeywords('theme'),
752
- appendKeywords(record.themes, 'theme')
785
+ removeChildrenByName('gmd:topicCategory'),
786
+ appendChildren(
787
+ ...record.topics.map((topic) =>
788
+ pipe(
789
+ createElement('gmd:topicCategory'),
790
+ createChild('gmd:MD_TopicCategoryCode'),
791
+ setTextContent(topic)
792
+ )
793
+ )
794
+ )
753
795
  )(rootEl)
754
796
  }
755
797
 
@@ -25,7 +25,14 @@ import {
25
25
  SourceWithUnknownProps,
26
26
  } from '../../../../../../../libs/api/metadata-converter/src'
27
27
  import { combineLatest, Observable, of, switchMap, takeLast } from 'rxjs'
28
- import { filter, map, shareReplay, startWith, tap } from 'rxjs/operators'
28
+ import {
29
+ filter,
30
+ map,
31
+ shareReplay,
32
+ startWith,
33
+ tap,
34
+ withLatestFrom,
35
+ } from 'rxjs/operators'
29
36
  import { LangService } from '../../../../../../../libs/util/i18n/src'
30
37
  import { PlatformServiceInterface } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
31
38
  import { coerce, satisfies, valid } from 'semver'
@@ -296,15 +303,28 @@ export class OrganizationsFromMetadataService
296
303
 
297
304
  const ownerOrganization = allContactOrgs[0]
298
305
 
306
+ // read the owner group as well to have a fallback logo
307
+ const groupId = selectField(source, 'groupOwner')
308
+ const group$ = this.groups$.pipe(
309
+ map((groups) =>
310
+ groups.find((group) => {
311
+ return group.id === Number(groupId)
312
+ })
313
+ )
314
+ )
315
+
299
316
  return this.organisations$.pipe(
300
317
  takeLast(1),
301
- map((organisations: IncompleteOrganization[]) => {
318
+ withLatestFrom(group$),
319
+ map(([organisations, group]: [Organization[], GroupApiModel]) => {
302
320
  const recordOrganisation = organisations.filter(
303
321
  (org) => org.name === ownerOrganization.name
304
322
  )[0]
323
+ const logoUrl = group?.logo && getAsUrl(`${IMAGE_URL}${group.logo}`)
305
324
  return {
306
325
  ...record,
307
326
  ownerOrganization: {
327
+ logoUrl,
308
328
  ...ownerOrganization,
309
329
  ...recordOrganisation,
310
330
  },
@@ -45,12 +45,23 @@ export class Gn4PlatformMapper {
45
45
  return { ...apiUser, id: id.toString() } as UserModel
46
46
  }
47
47
 
48
- thesaurusFromApi(thesaurus: any[]): ThesaurusModel {
48
+ thesaurusFromApi(thesaurus: any[], lang3?: string): ThesaurusModel {
49
49
  return thesaurus.map((keyword) => {
50
- const { uri, value } = keyword
50
+ let key = keyword.uri
51
+ // sometines GN can prefix an URI with an "all thesaurus" URI; only keep the last one
52
+ if (key.indexOf('@@@') > -1) {
53
+ key = key.split('@@@')[1]
54
+ }
55
+ const label =
56
+ lang3 && lang3 in keyword.values ? keyword.values[lang3] : keyword.value
57
+ const description =
58
+ lang3 && lang3 in keyword.definitions
59
+ ? keyword.definitions[lang3]
60
+ : keyword.definition
51
61
  return {
52
- key: uri,
53
- label: value,
62
+ key,
63
+ label,
64
+ description,
54
65
  }
55
66
  })
56
67
  }
@@ -14,8 +14,10 @@ import { Organization } from '../../../../../../../libs/common/domain/src/lib/mo
14
14
  import { Gn4PlatformMapper } from './gn4-platform.mapper'
15
15
  import { ltr } from 'semver'
16
16
  import { ThesaurusModel } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus/thesaurus.model'
17
+ import { LangService } from '../../../../../../../libs/util/i18n/src'
17
18
 
18
19
  const minApiVersion = '4.2.2'
20
+
19
21
  @Injectable()
20
22
  export class Gn4PlatformService implements PlatformServiceInterface {
21
23
  private readonly type = 'GeoNetwork'
@@ -50,13 +52,20 @@ export class Gn4PlatformService implements PlatformServiceInterface {
50
52
  shareReplay(1)
51
53
  )
52
54
 
55
+ /**
56
+ * A map of already loaded thesauri (groups of keywords); the key is a URI
57
+ * @private
58
+ */
59
+ private thesauri: Record<string, Observable<ThesaurusModel>> = {}
60
+
53
61
  constructor(
54
62
  private siteApiService: SiteApiService,
55
63
  private meApi: MeApiService,
56
64
  private usersApi: UsersApiService,
57
65
  private mapper: Gn4PlatformMapper,
58
66
  private toolsApiService: ToolsApiService,
59
- private registriesApiService: RegistriesApiService
67
+ private registriesApiService: RegistriesApiService,
68
+ private langService: LangService
60
69
  ) {
61
70
  this.me$ = this.meApi.getMe().pipe(
62
71
  switchMap((apiUser) => this.mapper.userFromMeApi(apiUser)),
@@ -98,17 +107,47 @@ export class Gn4PlatformService implements PlatformServiceInterface {
98
107
  }
99
108
 
100
109
  translateKey(key: string): Observable<string> {
110
+ // if the key is a URI, use the registries API to look for the translation
111
+ if (key.match(/^https?:\/\//)) {
112
+ // the thesaurus URI is inferred by removing a part of the keyword URI
113
+ // this is not exact science but it's OK, we'll still end up loading a bunch of keywords at once anyway
114
+ const thesaurusUri = key.replace(/\/([^/]+)$/, '/')
115
+ return this.getThesaurusByUri(thesaurusUri).pipe(
116
+ map((thesaurus) => {
117
+ for (const item of thesaurus) {
118
+ if (item.key === key) return item.label
119
+ }
120
+ return key
121
+ })
122
+ )
123
+ }
101
124
  return this.keyTranslations$.pipe(map((translations) => translations[key]))
102
125
  }
103
126
 
104
- getThesaurusByLang(
105
- thesaurusName: string,
106
- lang: string
107
- ): Observable<ThesaurusModel> {
108
- return this.registriesApiService
109
- .searchKeywords(null, lang, 1000, 0, null, [thesaurusName])
127
+ getThesaurusByUri(uri: string): Observable<ThesaurusModel> {
128
+ if (this.thesauri[uri]) {
129
+ return this.thesauri[uri]
130
+ }
131
+ this.thesauri[uri] = this.registriesApiService
132
+ .searchKeywords(
133
+ null,
134
+ this.langService.iso3,
135
+ 1000,
136
+ 0,
137
+ null,
138
+ null,
139
+ null,
140
+ `${uri}*`
141
+ )
110
142
  .pipe(
111
- map((thesaurus) => this.mapper.thesaurusFromApi(thesaurus as any[]))
143
+ map((thesaurus) =>
144
+ this.mapper.thesaurusFromApi(
145
+ thesaurus as any[],
146
+ this.langService.iso3
147
+ )
148
+ ),
149
+ shareReplay(1)
112
150
  )
151
+ return this.thesauri[uri]
113
152
  }
114
153
  }
@@ -60,6 +60,20 @@ export type SpatialRepresentationType =
60
60
  | 'table'
61
61
  | 'point'
62
62
 
63
+ export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
64
+
65
+ export type KeywordThesaurus = {
66
+ id: string
67
+ name?: string
68
+ url?: URL
69
+ }
70
+
71
+ export interface Keyword {
72
+ label: string
73
+ type: KeywordType
74
+ thesaurus?: KeywordThesaurus
75
+ }
76
+
63
77
  export interface BaseRecord {
64
78
  uniqueIdentifier: Uuid
65
79
  ownerOrganization: Organization
@@ -69,8 +83,8 @@ export interface BaseRecord {
69
83
  recordCreated?: Date
70
84
  recordUpdated: Date
71
85
  kind: RecordKind
72
- themes: Array<string> // TODO: handle codelists
73
- keywords: Array<string> // TODO: handle thesaurus and id
86
+ topics: Array<string> // TODO: handle codelists
87
+ keywords: Array<Keyword>
74
88
  licenses: Array<Constraint>
75
89
  legalConstraints: Array<Constraint>
76
90
  securityConstraints: Array<Constraint>
@@ -80,7 +94,6 @@ export interface BaseRecord {
80
94
  landingPage?: URL
81
95
  updateFrequency?: UpdateFrequency
82
96
 
83
- // to add: iso19139.topicCategory
84
97
  // to add: canonical url
85
98
  // to add: source catalog (??)
86
99
  // to add: is open data ?
@@ -1,6 +1,7 @@
1
1
  export interface ThesaurusItemModel {
2
2
  key: string
3
3
  label: string
4
+ description?: string
4
5
  }
5
6
 
6
7
  export type ThesaurusModel = ThesaurusItemModel[]
@@ -15,8 +15,5 @@ export abstract class PlatformServiceInterface {
15
15
  ): Observable<UserModel[]>
16
16
  abstract getOrganizations(): Observable<Organization[]>
17
17
  abstract translateKey(key: string): Observable<string>
18
- abstract getThesaurusByLang(
19
- thesaurusName: string,
20
- lang: string
21
- ): Observable<ThesaurusModel>
18
+ abstract getThesaurusByUri(uri: string): Observable<ThesaurusModel>
22
19
  }
@@ -64,7 +64,7 @@ export const GROUPS_FIXTURE = deepFreeze([
64
64
  defaultCategory: null,
65
65
  allowedCategories: [],
66
66
  enableAllowedCategories: false,
67
- id: 348385324,
67
+ id: 2,
68
68
  email: 'ifremer.ifremer@ifremer.admin.fr',
69
69
  referrer: null,
70
70
  description: "Institut français de recherche pour l'exploitation de la mer",
@@ -69,8 +69,24 @@ Cette section contient des *caractères internationaux* (ainsi que des "caractè
69
69
  url: new URL('http://my-org.net/two.png'),
70
70
  },
71
71
  ],
72
- keywords: ['international', 'test', '_another_keyword_'],
73
- themes: ['testData', 'exampleData'],
72
+ keywords: [
73
+ {
74
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
75
+ type: 'other',
76
+ label: 'international',
77
+ },
78
+ {
79
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
80
+ type: 'other',
81
+ label: 'test',
82
+ },
83
+ {
84
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
85
+ type: 'other',
86
+ label: '_another_keyword_',
87
+ },
88
+ ],
89
+ topics: ['testData', 'exampleData'],
74
90
  spatialRepresentation: 'grid',
75
91
  distributions: [
76
92
  {
@@ -115,7 +131,7 @@ As such, **it is not very interesting at all.**`,
115
131
  url: new URL('https://data.rennesmetropole.fr/pages/licence/'),
116
132
  },
117
133
  ],
118
- accessConstraints: [
134
+ legalConstraints: [
119
135
  {
120
136
  text: "Dataset access isn't possible since it does not really exist",
121
137
  type: 'other',
@@ -125,6 +141,8 @@ As such, **it is not very interesting at all.**`,
125
141
  type: 'security',
126
142
  },
127
143
  ],
144
+ securityConstraints: [],
145
+ otherConstraints: [],
128
146
  spatialExtents: [],
129
147
  temporalExtents: [],
130
148
  updateFrequency: {
@@ -168,13 +186,33 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
168
186
  },
169
187
  ],
170
188
  keywords: [
171
- 'planification',
172
- 'PLU',
173
- "Plan local d'urbanisme",
174
- 'données ouvertes',
175
- 'Avrigny*60036',
189
+ {
190
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
191
+ type: 'other',
192
+ label: 'planification',
193
+ },
194
+ {
195
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
196
+ type: 'other',
197
+ label: 'PLU',
198
+ },
199
+ {
200
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
201
+ type: 'other',
202
+ label: "Plan local d'urbanisme",
203
+ },
204
+ {
205
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
206
+ type: 'other',
207
+ label: 'données ouvertes',
208
+ },
209
+ {
210
+ thesaurus: { id: 'geonetwork.thesaurus.local' },
211
+ type: 'other',
212
+ label: 'Avrigny*60036',
213
+ },
176
214
  ],
177
- themes: ['Usage des sols', "document d'urbanisme"],
215
+ topics: ['Usage des sols', "document d'urbanisme"],
178
216
  spatialRepresentation: 'vector',
179
217
  distributions: [
180
218
  {
@@ -189,7 +227,9 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
189
227
  ],
190
228
  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.
191
229
  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.`,
192
- accessConstraints: [],
230
+ legalConstraints: [],
231
+ securityConstraints: [],
232
+ otherConstraints: [],
193
233
  useLimitations: ["Aucune condition ne s'applique", 'Licence Ouverte 2.0'],
194
234
  licenses: [
195
235
  {
@@ -58,6 +58,8 @@ export class MapContextService {
58
58
  ): Map {
59
59
  if (mapConfig) {
60
60
  mapContext = this.mergeMapConfigWithContext(mapContext, mapConfig)
61
+ } else {
62
+ mapContext.layers = this.addDefaultBaselayerContext(mapContext.layers)
61
63
  }
62
64
  if (
63
65
  !mapContext.view?.extent &&
@@ -179,6 +181,14 @@ export class MapContextService {
179
181
  return view
180
182
  }
181
183
 
184
+ addDefaultBaselayerContext(
185
+ layers: MapContextLayerModel[]
186
+ ): MapContextLayerModel[] {
187
+ return layers.includes(DEFAULT_BASELAYER_CONTEXT)
188
+ ? layers
189
+ : [DEFAULT_BASELAYER_CONTEXT, ...layers]
190
+ }
191
+
182
192
  mergeMapConfigWithContext(
183
193
  mapContext: MapContextModel,
184
194
  mapConfig: MapConfig