geonetwork-ui 2.2.0-dev.8cce3b0e → 2.2.0-dev.91cd6a97

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 (92) 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 +23 -11
  3. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.mjs +4 -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 +10 -9
  7. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +33 -8
  8. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +31 -14
  9. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  10. package/esm2022/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.mjs +3 -3
  11. package/esm2022/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.mjs +3 -3
  12. package/esm2022/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.mjs +3 -3
  13. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +17 -5
  14. package/esm2022/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.mjs +2 -2
  15. package/esm2022/libs/ui/elements/src/lib/thumbnail/thumbnail.component.mjs +3 -3
  16. package/esm2022/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.mjs +3 -3
  17. package/esm2022/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.mjs +3 -3
  18. package/esm2022/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.mjs +3 -3
  19. package/esm2022/libs/util/i18n/src/lib/i18n.constants.mjs +18 -1
  20. package/esm2022/libs/util/shared/src/lib/utils/index.mjs +2 -1
  21. package/esm2022/libs/util/shared/src/lib/utils/temporal-extent-union.mjs +32 -0
  22. package/esm2022/translations/de.json +26 -19
  23. package/esm2022/translations/en.json +26 -19
  24. package/esm2022/translations/es.json +23 -16
  25. package/esm2022/translations/fr.json +27 -20
  26. package/esm2022/translations/it.json +26 -19
  27. package/esm2022/translations/nl.json +24 -17
  28. package/esm2022/translations/pt.json +23 -16
  29. package/fesm2022/geonetwork-ui.mjs +381 -178
  30. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  31. package/libs/api/metadata-converter/src/lib/gn4/atomic-operations.d.ts +2 -0
  32. package/libs/api/metadata-converter/src/lib/gn4/atomic-operations.d.ts.map +1 -1
  33. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  34. package/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.d.ts.map +1 -1
  35. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts +4 -5
  36. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts.map +1 -1
  37. package/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.d.ts +3 -0
  38. package/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.d.ts.map +1 -0
  39. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +3 -3
  40. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  41. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +1 -1
  42. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  43. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +16 -2
  44. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  45. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts +9 -3
  46. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts.map +1 -1
  47. package/libs/ui/layout/src/lib/interactive-table/interactive-table-column/interactive-table-column.component.d.ts +1 -1
  48. package/libs/util/i18n/src/lib/i18n.constants.d.ts +17 -0
  49. package/libs/util/i18n/src/lib/i18n.constants.d.ts.map +1 -1
  50. package/libs/util/shared/src/lib/utils/index.d.ts +1 -0
  51. package/libs/util/shared/src/lib/utils/index.d.ts.map +1 -1
  52. package/libs/util/shared/src/lib/utils/temporal-extent-union.d.ts +5 -0
  53. package/libs/util/shared/src/lib/utils/temporal-extent-union.d.ts.map +1 -0
  54. package/package.json +1 -1
  55. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +56 -2
  56. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +51 -6
  57. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +205 -20
  58. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +29 -0
  59. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +30 -11
  60. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts +3 -1
  61. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +4 -5
  62. package/src/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.ts +16 -0
  63. package/src/libs/api/metadata-converter/src/lib/iso19139/converter.ts +9 -9
  64. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +58 -20
  65. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +86 -38
  66. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +20 -3
  67. package/src/libs/common/fixtures/src/lib/elasticsearch/full-response.ts +1 -1
  68. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +44 -8
  69. package/src/libs/feature/search/src/lib/favorites/favorite-star/favorite-star.component.html +3 -4
  70. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.html +1 -1
  71. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html +1 -1
  72. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +114 -23
  73. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +20 -3
  74. package/src/libs/ui/elements/src/lib/metadata-quality/metadata-quality.component.ts +1 -1
  75. package/src/libs/ui/elements/src/lib/thumbnail/thumbnail.component.html +2 -2
  76. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  77. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.html +3 -1
  78. package/src/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.css +3 -0
  79. package/src/libs/ui/inputs/src/lib/navigation-button/navigation-button.component.html +1 -1
  80. package/src/libs/util/i18n/src/lib/i18n.constants.ts +18 -0
  81. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  82. package/src/libs/util/shared/src/lib/utils/temporal-extent-union.ts +32 -0
  83. package/tailwind.base.config.js +3 -0
  84. package/tailwind.base.css +2 -2
  85. package/translations/de.json +26 -19
  86. package/translations/en.json +26 -19
  87. package/translations/es.json +23 -16
  88. package/translations/fr.json +27 -20
  89. package/translations/it.json +26 -19
  90. package/translations/nl.json +24 -17
  91. package/translations/pt.json +23 -16
  92. package/translations/sk.json +24 -17
@@ -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,
@@ -288,7 +291,10 @@ function appendResponsibleParty(contact: Individual) {
288
291
  )
289
292
  }
290
293
 
291
- function updateCitationDate(date: Date, type: 'revision' | 'creation') {
294
+ function updateCitationDate(
295
+ date: Date,
296
+ type: 'revision' | 'creation' | 'publication'
297
+ ) {
292
298
  return pipe(
293
299
  findNestedElements('gmd:date', 'gmd:CI_Date'),
294
300
  filterArray(
@@ -305,7 +311,10 @@ function updateCitationDate(date: Date, type: 'revision' | 'creation') {
305
311
  )
306
312
  }
307
313
 
308
- function appendCitationDate(date, type: 'revision' | 'creation') {
314
+ function appendCitationDate(
315
+ date,
316
+ type: 'revision' | 'creation' | 'publication'
317
+ ) {
309
318
  return appendChildren(
310
319
  pipe(
311
320
  createElement('gmd:date'),
@@ -326,47 +335,78 @@ function appendCitationDate(date, type: 'revision' | 'creation') {
326
335
  )
327
336
  }
328
337
 
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))
338
+ function removeKeywords() {
339
+ return removeChildren(pipe(findNestedElements('gmd:descriptiveKeywords')))
340
+ }
341
+
342
+ // returns a <gmd:thesaurusName> element
343
+ function createThesaurus(thesaurus: KeywordThesaurus) {
344
+ return pipe(
345
+ createElement('gmd:thesaurusName'),
346
+ createChild('gmd:CI_Citation'),
347
+ appendChildren(
348
+ pipe(
349
+ createElement('gmd:title'),
350
+ writeCharacterString(thesaurus.name || thesaurus.id)
351
+ ),
352
+ pipe(
353
+ createElement('gmd:identifier'),
354
+ createChild('gmd:MD_Identifier'),
355
+ appendChildren(
356
+ pipe(
357
+ createElement('gmd:code'),
358
+ thesaurus.url
359
+ ? writeAnchor(thesaurus.url, thesaurus.id)
360
+ : writeCharacterString(thesaurus.id)
361
+ )
343
362
  )
344
363
  )
345
364
  )
346
365
  )
347
366
  }
348
367
 
349
- function appendKeywords(keywords: string[], type: string | null) {
368
+ function appendKeywords(keywords: Keyword[]) {
369
+ const keywordsByThesaurus: Keyword[][] = keywords.reduce((acc, keyword) => {
370
+ const thesaurusId = keyword.thesaurus?.id
371
+ const type = keyword.type
372
+ let existingGroup = acc.find((group) =>
373
+ group[0].thesaurus
374
+ ? group[0].thesaurus.id === thesaurusId
375
+ : group[0].type === type
376
+ )
377
+ if (!existingGroup) {
378
+ existingGroup = []
379
+ acc.push(existingGroup)
380
+ }
381
+ existingGroup.push(keyword)
382
+ return acc
383
+ }, [])
350
384
  return appendChildren(
351
- pipe(
352
- createElement('gmd:descriptiveKeywords'),
353
- createChild('gmd:MD_Keywords'),
354
- type !== null
355
- ? appendChildren(
385
+ ...keywordsByThesaurus.map((keywords) =>
386
+ pipe(
387
+ createElement('gmd:descriptiveKeywords'),
388
+ createChild('gmd:MD_Keywords'),
389
+ appendChildren(
390
+ pipe(
391
+ createElement('gmd:type'),
392
+ createChild('gmd:MD_KeywordTypeCode'),
393
+ addAttribute(
394
+ 'codeList',
395
+ 'http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_KeywordTypeCode'
396
+ ),
397
+ addAttribute('codeListValue', keywords[0].type)
398
+ )
399
+ ),
400
+ keywords[0].thesaurus
401
+ ? appendChildren(createThesaurus(keywords[0].thesaurus))
402
+ : noop,
403
+ appendChildren(
404
+ ...keywords.map((keyword) =>
356
405
  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)
406
+ createElement('gmd:keyword'),
407
+ writeCharacterString(keyword.label)
364
408
  )
365
409
  )
366
- : noop,
367
- appendChildren(
368
- ...keywords.map((keyword) =>
369
- pipe(createElement('gmd:keyword'), writeCharacterString(keyword))
370
410
  )
371
411
  )
372
412
  )
@@ -740,16 +780,24 @@ export function writeContacts(record: CatalogRecord, rootEl: XmlElement) {
740
780
  export function writeKeywords(record: CatalogRecord, rootEl: XmlElement) {
741
781
  pipe(
742
782
  findOrCreateIdentification(),
743
- removeKeywords(null),
744
- appendKeywords(record.keywords, null)
783
+ removeKeywords(),
784
+ appendKeywords(record.keywords)
745
785
  )(rootEl)
746
786
  }
747
787
 
748
- export function writeThemes(record: CatalogRecord, rootEl: XmlElement) {
788
+ export function writeTopics(record: CatalogRecord, rootEl: XmlElement) {
749
789
  pipe(
750
790
  findOrCreateIdentification(),
751
- removeKeywords('theme'),
752
- appendKeywords(record.themes, 'theme')
791
+ removeChildrenByName('gmd:topicCategory'),
792
+ appendChildren(
793
+ ...record.topics.map((topic) =>
794
+ pipe(
795
+ createElement('gmd:topicCategory'),
796
+ createChild('gmd:MD_TopicCategoryCode'),
797
+ setTextContent(topic)
798
+ )
799
+ )
800
+ )
753
801
  )(rootEl)
754
802
  }
755
803
 
@@ -60,6 +60,22 @@ 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
+ // languages should be expressed using two-letters ISO 639-1 codes
77
+ export type LanguageCode = string
78
+
63
79
  export interface BaseRecord {
64
80
  uniqueIdentifier: Uuid
65
81
  ownerOrganization: Organization
@@ -68,9 +84,11 @@ export interface BaseRecord {
68
84
  abstract: string
69
85
  recordCreated?: Date
70
86
  recordUpdated: Date
87
+ recordPublished?: Date
88
+ languages: Array<LanguageCode>
71
89
  kind: RecordKind
72
- themes: Array<string> // TODO: handle codelists
73
- keywords: Array<string> // TODO: handle thesaurus and id
90
+ topics: Array<string> // TODO: handle codelists
91
+ keywords: Array<Keyword>
74
92
  licenses: Array<Constraint>
75
93
  legalConstraints: Array<Constraint>
76
94
  securityConstraints: Array<Constraint>
@@ -80,7 +98,6 @@ export interface BaseRecord {
80
98
  landingPage?: URL
81
99
  updateFrequency?: UpdateFrequency
82
100
 
83
- // to add: iso19139.topicCategory
84
101
  // to add: canonical url
85
102
  // to add: source catalog (??)
86
103
  // to add: is open data ?
@@ -131,7 +131,7 @@ export const ES_FIXTURE_FULL_RESPONSE = deepFreeze({
131
131
  creationDateForResource: ['2012-01-01T00:00:00.000Z'],
132
132
  creationYearForResource: '2012',
133
133
  creationMonthForResource: '2012-01',
134
- publicationDateForResource: ['2021-04-01T00:00:00.000Z'],
134
+ publicationDateForResource: '2021-04-01T00:00:00.000Z',
135
135
  publicationYearForResource: '2021',
136
136
  publicationMonthForResource: '2021-04',
137
137
  resourceDate: [
@@ -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
  {
@@ -170,13 +186,33 @@ Malgré l'attention portée à la création de ces données, il est rappelé que
170
186
  },
171
187
  ],
172
188
  keywords: [
173
- 'planification',
174
- 'PLU',
175
- "Plan local d'urbanisme",
176
- 'données ouvertes',
177
- '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
+ },
178
214
  ],
179
- themes: ['Usage des sols', "document d'urbanisme"],
215
+ topics: ['Usage des sols', "document d'urbanisme"],
180
216
  spatialRepresentation: 'vector',
181
217
  distributions: [
182
218
  {
@@ -1,6 +1,6 @@
1
- <div class="flex flex-row content-center">
1
+ <div class="flex flex-row items-center">
2
2
  <span
3
- class="align-text-top mr-1.5 mt-1.5 favorite-count"
3
+ class="align-text-top mr-1.5 favorite-count"
4
4
  *ngIf="hasFavoriteCount && displayCount"
5
5
  >{{ favoriteCount }}</span
6
6
  >
@@ -8,10 +8,9 @@
8
8
  [toggled]="isFavorite$ | async"
9
9
  (newValue)="toggleFavorite($event)"
10
10
  [disabled]="loading || (isAnonymous$ | async)"
11
- class="mt-1"
12
11
  ></gn-ui-star-toggle>
13
12
  <span
14
- class="align-text-top ml-1.5 favorite-count mt-1.5"
13
+ class="align-text-top ml-1.5 favorite-count"
15
14
  *ngIf="!displayCount"
16
15
  translate="datahub.record.addToFavorites"
17
16
  ></span>
@@ -5,6 +5,6 @@
5
5
  [selected]="currentLang"
6
6
  ariaName="languages"
7
7
  [showTitle]="false"
8
- [extraBtnClass]="'flex justify-items-center text-white !pl-2 !py-1'"
8
+ extraBtnClass="bg-transparent flex justify-items-center !pl-2 !py-1"
9
9
  >
10
10
  </gn-ui-dropdown-selector>
@@ -1,5 +1,5 @@
1
1
  <div class="py-5 px-5 rounded bg-gray-100 text-black mb-6">
2
- <div class="grid gap-3">
2
+ <div class="grid gap-3 overflow-hidden">
3
3
  <div>
4
4
  <p class="text-sm font-medium" translate>record.metadata.contact</p>
5
5
  </div>
@@ -55,29 +55,85 @@
55
55
  </div>
56
56
  </gn-ui-expandable-panel>
57
57
  <gn-ui-expandable-panel
58
- class="metadata-origin"
59
58
  *ngIf="
60
59
  metadata.lineage ||
61
60
  metadata.recordUpdated ||
62
61
  metadata.updateFrequency ||
63
62
  metadata.status
64
63
  "
65
- [title]="'record.metadata.origin' | translate"
64
+ [title]="'record.metadata.details' | translate"
66
65
  >
67
- <p
66
+ <div
68
67
  *ngIf="metadata.lineage"
69
- class="mb-5 pt-4 whitespace-pre-line break-words text-gray-900"
70
- gnUiLinkify
68
+ class="text-gray-900 flex flex-col mb-5 pt-4 gap-2"
71
69
  >
72
- {{ metadata.lineage }}
73
- </p>
70
+ <p class="whitespace-pre-line break-words text-gray-900" gnUiLinkify>
71
+ {{ metadata.lineage }}
72
+ </p>
73
+ </div>
74
+ <div class="flex flex-row gap-6 mb-8" *ngIf="resourceContact">
75
+ <div
76
+ *ngIf="resourceContact.organization?.logoUrl?.href"
77
+ class="flex items-center justify-center border-solid border border-gray-300 rounded-md bg-white h-32 overflow-hidden"
78
+ >
79
+ <gn-ui-thumbnail
80
+ class="relative h-full w-full"
81
+ [thumbnailUrl]="resourceContact.organization.logoUrl.href"
82
+ fit="contain"
83
+ ></gn-ui-thumbnail>
84
+ </div>
85
+ <div class="flex flex-col gap-1">
86
+ <p class="text-sm font-medium" translate>record.metadata.producer</p>
87
+ <div
88
+ class="text-primary font-title text-21 mr-2 cursor-pointer hover:underline"
89
+ data-cy="organization-name"
90
+ >
91
+ {{ resourceContact.organization?.name }}
92
+ </div>
93
+ <div *ngIf="resourceContact.organization?.website">
94
+ <a
95
+ [href]="resourceContact.organization.website"
96
+ target="_blank"
97
+ class="contact-website text-primary text-sm cursor-pointer hover:underline transition-all"
98
+ >{{ resourceContact.organization.website }}
99
+ <mat-icon
100
+ class="material-symbols-outlined !w-[12px] !h-[12px] !text-[12px] opacity-75 shrink-0"
101
+ >open_in_new</mat-icon
102
+ >
103
+ </a>
104
+ </div>
105
+ <div class="mt-4" *ngIf="resourceContact.email">
106
+ <div class="flex">
107
+ <mat-icon
108
+ class="material-symbols-outlined !w-5 !h-5 !text-[20px] opacity-75 shrink-0"
109
+ >
110
+ mail_outline</mat-icon
111
+ >
112
+ <a
113
+ *ngIf="resourceContact.email"
114
+ [href]="'mailto:' + resourceContact.email"
115
+ class="text-sm hover:underline ml-2"
116
+ target="_blank"
117
+ data-cy="contact-email"
118
+ >{{ resourceContact?.email }}</a
119
+ >
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
74
124
  <div
75
- class="py-4 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-[10px] gap-x-[20px] text-gray-700 info-grid"
125
+ class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
76
126
  >
77
- <div *ngIf="metadata.recordUpdated">
78
- <p class="text-sm" translate>record.metadata.updatedOn</p>
127
+ <div *ngIf="metadata.recordCreated">
128
+ <p class="text-sm" translate>record.metadata.creation</p>
79
129
  <p class="text-primary font-medium mt-1">
80
- {{ metadata.recordUpdated && metadata.recordUpdated.toLocaleString() }}
130
+ {{ metadata.recordCreated.toLocaleDateString() }}
131
+ </p>
132
+ </div>
133
+ <div *ngIf="metadata.recordPublished">
134
+ <p class="text-sm" translate>record.metadata.publication</p>
135
+ <p class="text-primary font-medium mt-1">
136
+ {{ metadata.recordPublished.toLocaleDateString() }}
81
137
  </p>
82
138
  </div>
83
139
  <div *ngIf="updateFrequency">
@@ -90,17 +146,52 @@
90
146
  {{ updateFrequency }}
91
147
  </p>
92
148
  </div>
93
- <div *ngIf="metadata.status">
94
- <p class="text-sm" translate>record.metadata.updateStatus</p>
95
- <p class="text-primary font-medium mt-1" translate>
96
- domain.record.status.{{ metadata.status }}
97
- </p>
149
+ <div *ngIf="metadata.languages">
150
+ <p class="text-sm mb-1" translate>record.metadata.languages</p>
151
+ <div class="flex flex-row gap-1 flex-wrap">
152
+ <p
153
+ class="text-primary font-medium"
154
+ translate
155
+ *ngFor="let language of metadata.languages"
156
+ >
157
+ language.{{ language }}
158
+ </p>
159
+ </div>
160
+ </div>
161
+ <div *ngIf="temporalExtent">
162
+ <p class="text-sm" translate>record.metadata.temporalExtent</p>
163
+ <div class="flex flex-row gap-1 mb-1 text-primary font-medium">
164
+ <p
165
+ *ngIf="temporalExtent.start && temporalExtent.end"
166
+ translate
167
+ [translateParams]="{
168
+ start: temporalExtent.start,
169
+ end: temporalExtent.end
170
+ }"
171
+ >
172
+ record.metadata.temporalExtent.fromDateToDate
173
+ </p>
174
+ <p
175
+ *ngIf="temporalExtent.start && !temporalExtent.end"
176
+ translate
177
+ [translateParams]="{ start: temporalExtent.start }"
178
+ >
179
+ record.metadata.temporalExtent.sinceDate
180
+ </p>
181
+ <p
182
+ *ngIf="!temporalExtent.start && temporalExtent.end"
183
+ translate
184
+ [translateParams]="{ end: temporalExtent.end }"
185
+ >
186
+ record.metadata.temporalExtent.untilDate
187
+ </p>
188
+ </div>
98
189
  </div>
99
190
  </div>
100
191
  </gn-ui-expandable-panel>
101
192
  <gn-ui-expandable-panel
102
193
  *ngIf="metadata.landingPage"
103
- [title]="'record.metadata.details' | translate"
194
+ [title]="'record.metadata.technical' | translate"
104
195
  >
105
196
  <div class="flex flex-col gap-4 mr-4 py-5 rounded text-gray-700">
106
197
  <div *ngIf="metadata.recordUpdated">
@@ -136,13 +227,13 @@
136
227
  </p>
137
228
  </div>
138
229
  </div>
139
- <div *ngIf="metadata.themes?.length">
140
- <p class="text-sm mb-1" translate>record.metadata.themes</p>
230
+ <div *ngIf="metadata.topics?.length">
231
+ <p class="text-sm mb-1" translate>record.metadata.topics</p>
141
232
  <div class="sm:pb-4 sm:pr-16">
142
233
  <gn-ui-badge
143
- class="inline-block mr-2 lowercase"
144
- *ngFor="let theme of metadata.themes"
145
- >{{ theme }}</gn-ui-badge
234
+ class="inline-block mr-2 mb-2 lowercase"
235
+ *ngFor="let topic of metadata.topics"
236
+ >{{ topic }}</gn-ui-badge
146
237
  >
147
238
  </div>
148
239
  </div>
@@ -154,7 +245,7 @@
154
245
  (click)="onKeywordClick(keyword)"
155
246
  [clickable]="true"
156
247
  *ngFor="let keyword of metadata.keywords"
157
- >{{ keyword }}</gn-ui-badge
248
+ >{{ keyword.label }}</gn-ui-badge
158
249
  >
159
250
  </div>
160
251
  </div>
@@ -5,7 +5,11 @@ import {
5
5
  Input,
6
6
  Output,
7
7
  } from '@angular/core'
8
- import { DatasetRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
8
+ import {
9
+ DatasetRecord,
10
+ Keyword,
11
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
12
+ import { getTemporalRangeUnion } from '../../../../../../libs/util/shared/src'
9
13
 
10
14
  @Component({
11
15
  selector: 'gn-ui-metadata-info',
@@ -16,7 +20,7 @@ import { DatasetRecord } from '../../../../../../libs/common/domain/src/lib/mode
16
20
  export class MetadataInfoComponent {
17
21
  @Input() metadata: Partial<DatasetRecord>
18
22
  @Input() incomplete: boolean
19
- @Output() keyword = new EventEmitter<string>()
23
+ @Output() keyword = new EventEmitter<Keyword>()
20
24
  updatedTimes: number
21
25
 
22
26
  get hasUsage() {
@@ -73,11 +77,24 @@ export class MetadataInfoComponent {
73
77
  }
74
78
  }
75
79
 
80
+ get temporalExtent(): { start: string; end: string } {
81
+ const temporalExtents = this.metadata.temporalExtents
82
+ return getTemporalRangeUnion(temporalExtents)
83
+ }
84
+
85
+ get shownOrganization() {
86
+ return this.metadata.ownerOrganization
87
+ }
88
+
89
+ get resourceContact() {
90
+ return this.metadata.contactsForResource?.[0]
91
+ }
92
+
76
93
  fieldReady(propName: string) {
77
94
  return !this.incomplete || propName in this.metadata
78
95
  }
79
96
 
80
- onKeywordClick(keyword: string) {
97
+ onKeywordClick(keyword: Keyword) {
81
98
  this.keyword.emit(keyword)
82
99
  }
83
100
  }
@@ -55,7 +55,7 @@ export class MetadataQualityComponent implements OnChanges {
55
55
  this.items = []
56
56
  this.add('title', !!this.metadata?.title)
57
57
  this.add('description', !!this.metadata?.abstract)
58
- this.add('topic', this.metadata?.themes?.length > 0)
58
+ this.add('topic', this.metadata?.topics?.length > 0)
59
59
  this.add('keywords', this.metadata?.keywords?.length > 0)
60
60
  this.add('legalConstraints', this.metadata?.legalConstraints?.length > 0)
61
61
  this.add('organisation', !!contact?.organization)
@@ -1,13 +1,13 @@
1
1
  <div
2
2
  #containerElement
3
- class="h-full w-full relative shrink-0 overflow-hidden flex items-center"
3
+ class="h-full w-full relative shrink-0 overflow-hidden flex items-center justify-center"
4
4
  [ngClass]="isPlaceholder ? 'bg-gray-100' : 'bg-white'"
5
5
  [attr.data-cy-is-placeholder]="isPlaceholder.toString()"
6
6
  >
7
7
  <img
8
8
  #imageElement
9
9
  class="relative w-full object-center"
10
- [ngClass]="imgFit === 'contain' ? 'h-4/5' : 'h-full'"
10
+ [ngClass]="imgFit === 'contain' ? 'h-4/5 w-4/5' : 'h-full'"
11
11
  [ngStyle]="{ objectFit: imgFit }"
12
12
  alt="thumbnail"
13
13
  loading="lazy"
@@ -1,6 +1,6 @@
1
1
  <gn-ui-button
2
2
  type="outline"
3
- extraClass="w-full !p-[8px] !pl-[16px]"
3
+ extraClass="bg-background w-full !p-[8px] !pl-[16px]"
4
4
  [title]="title"
5
5
  [attr.aria-owns]="id"
6
6
  (buttonClick)="openOverlay()"
@@ -9,7 +9,9 @@
9
9
  <gn-ui-button
10
10
  type="outline"
11
11
  class="grow min-w-0"
12
- extraClass="!p-[8px] !pl-[16px] flex flex-row w-full {{ extraBtnClass }}"
12
+ extraClass="bg-background !p-[8px] !pl-[16px] flex flex-row w-full {{
13
+ extraBtnClass
14
+ }}"
13
15
  [title]="title"
14
16
  [attr.aria-owns]="id"
15
17
  (buttonClick)="openOverlay()"
@@ -0,0 +1,3 @@
1
+ button {
2
+ color: var(--navigation-button-color, --color-background);
3
+ }
@@ -1,5 +1,5 @@
1
1
  <button
2
- class="group flex items-center justify-center gap-1 backdrop-blur p-1 bg-primary-opacity-30 text-white rounded content-center"
2
+ class="group flex items-center justify-center gap-1 backdrop-blur p-1 bg-primary-opacity-30 rounded content-center"
3
3
  >
4
4
  <mat-icon class="material-symbols-outlined align-middle w-[18px]">{{
5
5
  icon
@@ -40,6 +40,24 @@ export const LANG_3_TO_2_MAPPER = {
40
40
  slo: 'sk',
41
41
  }
42
42
 
43
+ export const LANGUAGE_NAMES = {
44
+ en: 'English',
45
+ nl: 'Nederlands',
46
+ fr: 'Français',
47
+ de: 'Deutsch',
48
+ ko: '한국어',
49
+ es: 'Español',
50
+ cs: 'Čeština',
51
+ ca: 'Català',
52
+ fi: 'Suomi',
53
+ is: 'Íslenska',
54
+ it: 'Italiano',
55
+ pt: 'Português',
56
+ ru: 'Русский',
57
+ zh: '中文',
58
+ sk: 'Slovenčina',
59
+ }
60
+
43
61
  // Caution: changing this can break language selection from third parties!
44
62
  export const LANGUAGE_STORAGE_KEY = 'geonetwork-ui-language'
45
63
 
@@ -6,3 +6,4 @@ export * from './sort-by'
6
6
  export * from './url'
7
7
  export * from './event'
8
8
  export * from './fuzzy-filter'
9
+ export * from './temporal-extent-union'