geonetwork-ui 2.9.0 → 2.10.0-dev.02185426a

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 (167) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2365 -6948
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +332 -174
  4. package/index.d.ts.map +1 -1
  5. package/material-styles.css +483 -0
  6. package/package.json +25 -35
  7. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  20. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  21. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  23. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +5 -4
  24. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
  32. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  33. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  34. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +72 -38
  35. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  39. package/src/libs/common/domain/src/index.ts +1 -0
  40. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  41. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  42. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  43. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  44. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  45. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +56 -0
  46. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  47. package/src/libs/feature/editor/src/index.ts +2 -0
  48. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  49. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  50. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  51. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  52. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  53. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  54. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  55. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  56. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  57. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  58. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  59. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +11 -6
  60. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +18 -2
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +33 -34
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +8 -7
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +6 -6
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.css +0 -0
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.html +5 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.ts +89 -0
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +21 -16
  79. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +14 -0
  80. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  81. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  82. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  83. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  84. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  85. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  86. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  87. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  88. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  89. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  90. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  91. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  92. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  93. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  94. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  95. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  96. package/src/libs/ui/dataviz/src/index.ts +0 -1
  97. package/src/libs/ui/elements/src/index.ts +3 -0
  98. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  99. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  100. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  101. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  102. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  103. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  104. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  105. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  106. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  107. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  108. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  109. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  110. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  111. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  112. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  113. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +3 -2
  114. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +26 -68
  115. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  116. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  117. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  118. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  119. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  120. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  121. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  122. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  123. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  124. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  125. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +6 -2
  126. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  127. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  128. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  129. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  130. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  131. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  132. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  133. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  134. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  135. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  136. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  137. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  138. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  139. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  140. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  141. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  142. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  143. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  144. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  145. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  146. package/src/libs/util/i18n/src/index.ts +0 -1
  147. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  148. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  149. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  150. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  151. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  152. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  153. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  154. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  155. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  156. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  157. package/style.css +0 -1
  158. package/tailwind.base.css +28 -2
  159. package/translations/de.json +81 -48
  160. package/translations/en.json +86 -53
  161. package/translations/es.json +55 -22
  162. package/translations/fr.json +83 -50
  163. package/translations/it.json +81 -48
  164. package/translations/nl.json +54 -21
  165. package/translations/pt.json +55 -22
  166. package/translations/sk.json +55 -22
  167. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -58,7 +58,6 @@ export class InternalLinkCardComponent implements OnInit {
58
58
  protected elementRef = inject(ElementRef)
59
59
 
60
60
  @Input() record: CatalogRecord
61
- @Input() linkTarget = '_blank'
62
61
  @Input() linkHref: string = null
63
62
  @Input() metadataQualityDisplay: boolean
64
63
  @Input() favoriteTemplate: TemplateRef<{ $implicit: CatalogRecord }>
@@ -0,0 +1,14 @@
1
+ <gn-ui-badge
2
+ [clickable]="!editable"
3
+ [removable]="editable"
4
+ (badgeClicked)="keywordClick.emit(keyword)"
5
+ (badgeRemoveClicked)="keywordRemove.emit(keyword)"
6
+ ><ng-container>{{ keyword.label }}</ng-container>
7
+ @if (editable && isPlaceWithoutExtent) {
8
+ <ng-icon
9
+ [title]="'editor.record.form.keywords.place.withoutExtent' | translate"
10
+ class="!text-[1.4em] ml-1"
11
+ name="matWarningAmberOutline"
12
+ ></ng-icon>
13
+ }
14
+ </gn-ui-badge>
@@ -0,0 +1,85 @@
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ElementRef,
6
+ EventEmitter,
7
+ inject,
8
+ Input,
9
+ Output,
10
+ ViewChild,
11
+ } from '@angular/core'
12
+ import { Keyword } from '../../../../../../libs/common/domain/src/lib/model/record'
13
+ import { BadgeComponent } from '../../../../../../libs/ui/inputs/src'
14
+ import { TranslatePipe, TranslateService } from '@ngx-translate/core'
15
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
16
+ import {
17
+ NgIconComponent,
18
+ provideIcons,
19
+ provideNgIconsConfig,
20
+ } from '@ng-icons/core'
21
+ import { matWarningAmberOutline } from '@ng-icons/material-icons/outline'
22
+ import tippy from 'tippy.js'
23
+
24
+ marker('domain.record.keywordType.theme')
25
+ marker('domain.record.keywordType.place')
26
+ marker('domain.record.keywordType.temporal')
27
+ marker('domain.record.keywordType.other')
28
+
29
+ @Component({
30
+ selector: 'gn-ui-keyword-badge',
31
+ templateUrl: './keyword-badge.component.html',
32
+ changeDetection: ChangeDetectionStrategy.OnPush,
33
+ standalone: true,
34
+ imports: [BadgeComponent, NgIconComponent, TranslatePipe],
35
+ providers: [
36
+ provideIcons({
37
+ matWarningAmberOutline,
38
+ }),
39
+ provideNgIconsConfig({
40
+ size: '1.5em',
41
+ }),
42
+ ],
43
+ })
44
+ export class KeywordBadgeComponent implements AfterViewInit {
45
+ private translateService = inject(TranslateService)
46
+
47
+ @Input() keyword: Keyword
48
+ @Input() editable = false // if false, keyword is only clickable
49
+ @Output() keywordClick = new EventEmitter<Keyword>()
50
+ @Output() keywordRemove = new EventEmitter<Keyword>()
51
+ @ViewChild(BadgeComponent, { read: ElementRef }) badgeComponent: ElementRef
52
+
53
+ get segments(): string[] {
54
+ if (this.keyword.hierarchyPath?.length) {
55
+ return this.keyword.hierarchyPath
56
+ }
57
+ if (this.keyword.thesaurus?.name) {
58
+ return [this.keyword.thesaurus.name, this.keyword.label]
59
+ }
60
+ return [
61
+ this.translateService.instant(
62
+ `domain.record.keywordType.${this.keyword.type}`
63
+ ),
64
+ this.keyword.label,
65
+ ]
66
+ }
67
+
68
+ get isPlaceWithoutExtent(): boolean {
69
+ return this.keyword.type === 'place' && !this.keyword.bbox
70
+ }
71
+
72
+ ngAfterViewInit() {
73
+ const content = this.segments
74
+ .map(
75
+ (segment, i, arr) =>
76
+ `<span ${i === arr.length - 1 ? 'class="font-bold"' : ''}>${segment}</span>`
77
+ )
78
+ .join(' &gt; ')
79
+ tippy(this.badgeComponent.nativeElement, {
80
+ appendTo: () => document.body,
81
+ content,
82
+ allowHTML: true,
83
+ })
84
+ }
85
+ }
@@ -1,5 +1,5 @@
1
1
  import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
2
- import { marked } from 'marked'
2
+ import { parse } from 'marked'
3
3
 
4
4
  @Component({
5
5
  selector: 'gn-ui-markdown-parser',
@@ -14,6 +14,6 @@ export class MarkdownParserComponent {
14
14
  @Input() whitoutStyles?: boolean
15
15
 
16
16
  get parsedMarkdown() {
17
- return marked.parse(this.textContent ?? '')
17
+ return parse(this.textContent ?? '')
18
18
  }
19
19
  }
@@ -21,7 +21,7 @@ import {
21
21
  matCallOutline,
22
22
  matLocationOnOutline,
23
23
  } from '@ng-icons/material-icons/outline'
24
-
24
+ import { getAddressLines } from '../../../../../../libs/util/shared/src'
25
25
  import { TranslateDirective } from '@ngx-translate/core'
26
26
 
27
27
  @Component({
@@ -59,10 +59,7 @@ export class MetadataContactComponent {
59
59
  }
60
60
 
61
61
  get address() {
62
- const addressParts = this.contacts[0].address
63
- .split(',')
64
- .map((part) => part.trim())
65
- return addressParts
62
+ return getAddressLines(this.contacts[0]?.address)
66
63
  }
67
64
 
68
65
  onOrganizationClick() {
@@ -17,6 +17,7 @@
17
17
  --gn-ui-badge-text-color: var(--color-primary-darkest);
18
18
  }
19
19
 
20
- :host .metadata-info-keywords ::ng-deep gn-ui-badge:hover {
21
- --gn-ui-badge-text-color: white;
20
+ .metadata-info-keywords {
21
+ --gn-ui-badge-padding: 0.25rem 0.5rem;
22
+ text-transform: lowercase;
22
23
  }
@@ -28,12 +28,10 @@
28
28
  <gn-ui-max-lines [maxLines]="7">
29
29
  <div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
30
30
  @for (keyword of metadata.keywords; track keyword) {
31
- <gn-ui-badge
32
- class="inline-block lowercase"
33
- (click)="onKeywordClick(keyword)"
34
- [clickable]="true"
35
- >{{ keyword.label }}</gn-ui-badge
36
- >
31
+ <gn-ui-keyword-badge
32
+ [keyword]="keyword"
33
+ (keywordClick)="onKeywordClick($event)"
34
+ ></gn-ui-keyword-badge>
37
35
  }
38
36
  </div>
39
37
  </gn-ui-max-lines>
@@ -52,7 +50,7 @@
52
50
  data-test="usage-panel"
53
51
  >
54
52
  <div class="flex flex-col gap-[10px] mr-4 py-[12px] rounded text-gray-900">
55
- @for (license of licenses; track license) {
53
+ @for (license of licenses; track $index) {
56
54
  @if (license.url) {
57
55
  <div class="text-primary">
58
56
  <a
@@ -105,9 +103,29 @@
105
103
  </div>
106
104
  </gn-ui-expandable-panel>
107
105
  }
106
+ @if (metadata.contactsForResource?.length) {
107
+ <gn-ui-expandable-panel
108
+ [title]="'record.metadata.resource.contacts' | translate"
109
+ data-test="contacts-panel"
110
+ >
111
+ <div class="flex flex-col gap-1 pt-3 pb-4">
112
+ @for (group of contactGroups; track group.role) {
113
+ <div class="flex flex-col gap-1 rounded py-4 px-2">
114
+ <p class="text-xs font-normal text-black">
115
+ {{ group.roleLabel | translate }}
116
+ </p>
117
+ <div class="grid gap-1 grid-cols-1 md:grid-cols-2">
118
+ @for (contact of group.contacts; track contact.email) {
119
+ <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
120
+ }
121
+ </div>
122
+ </div>
123
+ }
124
+ </div>
125
+ </gn-ui-expandable-panel>
126
+ }
108
127
  @if (
109
128
  (metadata.kind === 'dataset' && metadata.lineage) ||
110
- resourceContact ||
111
129
  metadata.resourceCreated ||
112
130
  metadata.resourcePublished ||
113
131
  metadata.resourceUpdated ||
@@ -129,66 +147,6 @@
129
147
  </p>
130
148
  </div>
131
149
  }
132
- @if (resourceContact) {
133
- <div
134
- class="flex flex-row gap-6 mt-5 mb-8 resource-contact"
135
- data-test="details-panel-resource-contact"
136
- >
137
- @if (resourceContact.organization?.logoUrl?.href) {
138
- <div
139
- class="flex items-center justify-center border-solid border border-gray-300 rounded-md bg-white h-32 overflow-hidden"
140
- >
141
- <gn-ui-thumbnail
142
- class="relative h-full w-full"
143
- [thumbnailUrl]="resourceContact.organization.logoUrl.href"
144
- fit="contain"
145
- ></gn-ui-thumbnail>
146
- </div>
147
- }
148
- <div class="flex flex-col gap-1">
149
- <p class="text-sm font-medium" translate>record.metadata.producer</p>
150
- <div
151
- class="text-primary font-title text-21 mr-2 cursor-pointer hover:underline"
152
- data-cy="organization-name"
153
- >
154
- {{ resourceContact.organization?.name }}
155
- </div>
156
- @if (resourceContact.organization?.website) {
157
- <div>
158
- <a
159
- [href]="resourceContact.organization.website"
160
- target="_blank"
161
- class="contact-website text-primary text-sm cursor-pointer hover:underline transition-all"
162
- >{{ resourceContact.organization.website }}
163
- <ng-icon
164
- class="!w-[12px] !h-[12px] !text-[12px] opacity-75 shrink-0"
165
- name="matOpenInNew"
166
- ></ng-icon>
167
- </a>
168
- </div>
169
- }
170
- @if (resourceContact.email) {
171
- <div class="mt-4">
172
- <div class="flex">
173
- <ng-icon
174
- class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
175
- name="matMailOutline"
176
- ></ng-icon>
177
- @if (resourceContact.email) {
178
- <a
179
- [href]="'mailto:' + resourceContact.email"
180
- class="text-sm hover:underline ml-2"
181
- target="_blank"
182
- data-cy="contact-email"
183
- >{{ resourceContact?.email }}</a
184
- >
185
- }
186
- </div>
187
- </div>
188
- }
189
- </div>
190
- </div>
191
- }
192
150
  <div
193
151
  class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
194
152
  >
@@ -8,7 +8,10 @@ import {
8
8
  } from '@angular/core'
9
9
  import {
10
10
  CatalogRecord,
11
+ Individual,
11
12
  Keyword,
13
+ Role,
14
+ RoleLabels,
12
15
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
16
  import { DateService, getTemporalRangeUnion } from '../../../../../../libs/util/shared/src'
14
17
  import { MarkdownParserComponent } from '../markdown-parser/markdown-parser.component'
@@ -22,14 +25,15 @@ import {
22
25
  CopyTextButtonComponent,
23
26
  } from '../../../../../../libs/ui/inputs/src'
24
27
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
28
+ import { KeywordBadgeComponent } from '../keyword-badge/keyword-badge.component'
25
29
  import { NgIcon, provideIcons } from '@ng-icons/core'
26
30
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
27
31
  import { matMailOutline } from '@ng-icons/material-icons/outline'
28
- import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
29
32
  import { GnUiLinkifyDirective } from './linkify.directive'
30
33
  import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/src'
31
34
 
32
35
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
36
+ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
33
37
 
34
38
  @Component({
35
39
  selector: 'gn-ui-metadata-info',
@@ -44,13 +48,14 @@ import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
44
48
  ExpandablePanelComponent,
45
49
  BadgeComponent,
46
50
  ContentGhostComponent,
47
- ThumbnailComponent,
48
51
  MaxLinesComponent,
49
52
  CopyTextButtonComponent,
50
53
  NgIcon,
51
54
  GnUiLinkifyDirective,
52
55
  GnUiHumanizeDateDirective,
53
56
  SpatialExtentComponent,
57
+ ContactPillComponent,
58
+ KeywordBadgeComponent,
54
59
  ],
55
60
  viewProviders: [
56
61
  provideIcons({
@@ -132,18 +137,33 @@ export class MetadataInfoComponent {
132
137
  return getTemporalRangeUnion(temporalExtents, this.dateService)
133
138
  }
134
139
 
135
- get shownOrganization() {
136
- return this.metadata.ownerOrganization
137
- }
138
-
139
- get resourceContact() {
140
- return this.metadata.contactsForResource?.[0]
141
- }
142
-
143
140
  fieldReady(propName: string) {
144
141
  return !this.incomplete || propName in this.metadata
145
142
  }
146
143
 
144
+ get contactGroups(): {
145
+ role: Role
146
+ roleLabel: string
147
+ contacts: Individual[]
148
+ }[] {
149
+ const groups: { role: Role; roleLabel: string; contacts: Individual[] }[] =
150
+ []
151
+ const indexByRole = new Map<Role, number>()
152
+ for (const contact of this.metadata.contactsForResource ?? []) {
153
+ if (indexByRole.has(contact.role)) {
154
+ groups[indexByRole.get(contact.role)].contacts.push(contact)
155
+ } else {
156
+ indexByRole.set(contact.role, groups.length)
157
+ groups.push({
158
+ role: contact.role,
159
+ roleLabel: RoleLabels.get(contact.role),
160
+ contacts: [contact],
161
+ })
162
+ }
163
+ }
164
+ return groups
165
+ }
166
+
147
167
  onKeywordClick(keyword: Keyword) {
148
168
  this.keyword.emit(keyword)
149
169
  }
@@ -52,12 +52,11 @@
52
52
  </gn-ui-text-input>
53
53
  @if (!supportOffset) {
54
54
  <div class="flex items-center gap-2 text-orange-500 z-10 ml-3">
55
- <span
56
- class="material-symbols-outlined"
55
+ <ng-icon
56
+ name="matWarningOutline"
57
+ size="24"
57
58
  matTooltip="Not supported on this service"
58
- >
59
- warning
60
- </span>
59
+ ></ng-icon>
61
60
  </div>
62
61
  }
63
62
  </div>
@@ -15,6 +15,8 @@ import {
15
15
  import { CommonModule } from '@angular/common'
16
16
  import { TranslateDirective } from '@ngx-translate/core'
17
17
  import { MatTooltipModule } from '@angular/material/tooltip'
18
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
19
+ import { matWarningOutline } from '@ng-icons/material-icons/outline'
18
20
 
19
21
  const DEFAULT_PARAMS = {
20
22
  OFFSET: '',
@@ -35,7 +37,9 @@ const DEFAULT_PARAMS = {
35
37
  CopyTextButtonComponent,
36
38
  TranslateDirective,
37
39
  MatTooltipModule,
40
+ NgIconComponent,
38
41
  ],
42
+ providers: [provideIcons({ matWarningOutline })],
39
43
  })
40
44
  export class RecordApiFormComponent {
41
45
  @Input() set apiLink(value: DatasetServiceDistribution) {
@@ -1,27 +1,34 @@
1
- <div
2
- class="gn-ui-badge flex items-center gap-1.5"
3
- [ngClass]="
4
- clickable
5
- ? 'hover:bg-primary cursor-pointer transition-colors duration-100'
6
- : ''
7
- "
8
- >
1
+ <ng-template #badgeContent>
9
2
  <span><ng-content></ng-content></span>
10
- @if (removable) {
11
- <gn-ui-button
12
- type="light"
13
- (buttonClick)="removeBadge()"
14
- extraClass="text-xs border-0 px-0 py-0"
15
- style="
16
- --gn-ui-button-padding: 0;
17
- --gn-ui-button-font-size: 0.8em;
18
- --gn-ui-button-width: 1.4em;
19
- --gn-ui-button-height: 1.4em;
20
- --gn-ui-button-rounded: 1.4em;
21
- --gn-ui-button-background: var(--gn-ui-badge-background-color, white);
22
- "
23
- >
24
- <ng-icon name="matClose"></ng-icon>
25
- </gn-ui-button>
26
- }
27
- </div>
3
+ </ng-template>
4
+
5
+ @if (clickable) {
6
+ <button
7
+ type="button"
8
+ class="gn-ui-badge-clickable"
9
+ (click)="handleClick($event)"
10
+ >
11
+ <ng-container [ngTemplateOutlet]="badgeContent"></ng-container>
12
+ </button>
13
+ } @else {
14
+ <span class="gn-ui-badge gap-1.5">
15
+ <ng-container [ngTemplateOutlet]="badgeContent"></ng-container>
16
+ @if (removable) {
17
+ <gn-ui-button
18
+ type="light"
19
+ (buttonClick)="removeBadge()"
20
+ extraClass="text-xs border-0 px-0 py-0"
21
+ style="
22
+ --gn-ui-button-padding: 0;
23
+ --gn-ui-button-font-size: 0.8em;
24
+ --gn-ui-button-width: 1.4em;
25
+ --gn-ui-button-height: 1.4em;
26
+ --gn-ui-button-rounded: 1.4em;
27
+ --gn-ui-button-background: var(--gn-ui-badge-background-color, white);
28
+ "
29
+ >
30
+ <ng-icon name="matClose"></ng-icon>
31
+ </gn-ui-button>
32
+ }
33
+ </span>
34
+ }
@@ -13,6 +13,7 @@ import {
13
13
  provideNgIconsConfig,
14
14
  } from '@ng-icons/core'
15
15
  import { matClose } from '@ng-icons/material-icons/baseline'
16
+ import { propagateToDocumentOnly } from '../../../../../../libs/util/shared/src'
16
17
 
17
18
  @Component({
18
19
  selector: 'gn-ui-badge',
@@ -34,8 +35,16 @@ export class BadgeComponent {
34
35
  @Input() clickable? = false
35
36
  @Input() removable? = false
36
37
  @Output() badgeRemoveClicked = new EventEmitter<void>()
38
+ @Output() badgeClicked = new EventEmitter<void>()
37
39
 
38
40
  removeBadge() {
39
41
  this.badgeRemoveClicked.emit()
40
42
  }
43
+
44
+ handleClick(event: MouseEvent) {
45
+ this.badgeClicked.emit()
46
+ event.preventDefault()
47
+ ;(event.currentTarget as HTMLElement).blur()
48
+ propagateToDocumentOnly(event)
49
+ }
41
50
  }
@@ -25,7 +25,9 @@ export class ButtonComponent {
25
25
  | 'outline'
26
26
  | 'light'
27
27
  | 'gray'
28
+ | 'gray-light'
28
29
  | 'black'
30
+ | 'primary-light'
29
31
  ) {
30
32
  // btn-classes are written in full to be picked up by tailwind
31
33
  switch (value) {
@@ -44,9 +46,15 @@ export class ButtonComponent {
44
46
  case 'gray':
45
47
  this.btnClass = 'gn-ui-btn-gray'
46
48
  break
49
+ case 'gray-light':
50
+ this.btnClass = 'gn-ui-btn-gray-light'
51
+ break
47
52
  case 'black':
48
53
  this.btnClass = 'gn-ui-btn-black'
49
54
  break
55
+ case 'primary-light':
56
+ this.btnClass = 'gn-ui-btn-primary-light'
57
+ break
50
58
  case 'default':
51
59
  default:
52
60
  this.btnClass = 'gn-ui-btn-default'
@@ -2,9 +2,9 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  EventEmitter,
5
+ inject,
5
6
  Input,
6
7
  Output,
7
- inject,
8
8
  } from '@angular/core'
9
9
  import {
10
10
  DateAdapter,
@@ -52,6 +52,6 @@ export class DatePickerComponent {
52
52
  @Output() dateChange = new EventEmitter<Date>()
53
53
 
54
54
  constructor() {
55
- this.dateAdapter.setLocale(this.translate.currentLang)
55
+ this.dateAdapter.setLocale(this.translate.getCurrentLang())
56
56
  }
57
57
  }
@@ -51,7 +51,7 @@
51
51
  <div
52
52
  class="bg-white border border-gray-300 rounded shadow-lg py-2 w-full overflow-x-hidden overflow-y-auto overlay-container"
53
53
  [style.max-height]="overlayMaxHeight"
54
- [style.width]="overlayWidth"
54
+ [style.min-width]="overlayMinWidth"
55
55
  role="listbox"
56
56
  tabindex="-1"
57
57
  [attr.id]="id"
@@ -3,12 +3,12 @@ import {
3
3
  Component,
4
4
  ElementRef,
5
5
  EventEmitter,
6
+ inject,
6
7
  Input,
7
8
  Output,
8
9
  QueryList,
9
10
  ViewChild,
10
11
  ViewChildren,
11
- inject,
12
12
  } from '@angular/core'
13
13
  import {
14
14
  CdkConnectedOverlay,
@@ -85,7 +85,7 @@ export class DropdownMultiselectComponent {
85
85
  ]
86
86
  scrollStrategy = this.scrollStrategies.reposition()
87
87
  overlayOpen = false
88
- overlayWidth = 'auto'
88
+ overlayMinWidth = 'none'
89
89
  overlayMaxHeight = 'none'
90
90
  id = `dropdown-multiselect-${Math.floor(Math.random() * 10000)}`
91
91
 
@@ -120,7 +120,7 @@ export class DropdownMultiselectComponent {
120
120
  }
121
121
 
122
122
  openOverlay() {
123
- this.overlayWidth =
123
+ this.overlayMinWidth =
124
124
  this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
125
125
  .width + 'px'
126
126
  this.overlayMaxHeight = this.maxRows
@@ -6,7 +6,7 @@
6
6
  type="url"
7
7
  [value]="inputValue"
8
8
  (input)="handleInput($event)"
9
- (keydown.enter)="handleUpload(input)"
9
+ (keydown.enter)="handleUpload(input, $event)"
10
10
  [placeholder]="placeholder"
11
11
  [attr.aria-label]="placeholder"
12
12
  [disabled]="disabled"
@@ -25,7 +25,7 @@
25
25
  extraClass="absolute inset-y-[var(--side-padding)] right-[var(--side-padding)]"
26
26
  type="primary"
27
27
  [disabled]="disabled || input.value === '' || !isValidUrl(input.value)"
28
- (buttonClick)="handleUpload(input)"
28
+ (buttonClick)="handleUpload(input, $event)"
29
29
  >
30
30
  <ng-content>
31
31
  <ng-icon name="iconoirArrowUp"></ng-icon>
@@ -62,7 +62,10 @@ export class UrlInputComponent implements OnChanges {
62
62
  inputValue = ''
63
63
 
64
64
  ngOnChanges(changes: SimpleChanges) {
65
- if (changes['resetUrlOnChange']) {
65
+ if (
66
+ changes['resetUrlOnChange'] &&
67
+ !changes['resetUrlOnChange'].firstChange
68
+ ) {
66
69
  this.inputValue = ''
67
70
  }
68
71
  }
@@ -78,7 +81,8 @@ export class UrlInputComponent implements OnChanges {
78
81
  this.valueChange.next(value)
79
82
  }
80
83
 
81
- handleUpload(element: HTMLInputElement) {
84
+ handleUpload(element: HTMLInputElement, event: Event) {
85
+ event.stopPropagation()
82
86
  const value = element.value
83
87
  if (!value || !this.isValidUrl(value)) return
84
88
  this.uploadClick.next(value)
@@ -3,14 +3,14 @@
3
3
  <span class="flex-none font-bold" translate="">{{ label }}</span>
4
4
  <div class="flex-1 flex justify-end items-center">
5
5
  <ng-content select="[form-field-interaction]"></ng-content>
6
- <span
6
+ <ng-icon
7
7
  *ngIf="hint"
8
- class="material-symbols-outlined m-2 gn-ui-icon-small"
8
+ name="matHelpOutline"
9
+ size="16"
10
+ class="m-2"
9
11
  [matTooltip]="hint"
10
12
  matTooltipPosition="above"
11
- >
12
- help
13
- </span>
13
+ ></ng-icon>
14
14
  </div>
15
15
  </div>
16
16
  <div class="flex-1 mt-2">
@@ -2,6 +2,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
2
2
  import { MatTooltipModule } from '@angular/material/tooltip'
3
3
  import { CommonModule } from '@angular/common'
4
4
  import { TranslateDirective } from '@ngx-translate/core'
5
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
6
+ import { matHelpOutline } from '@ng-icons/material-icons/outline'
5
7
 
6
8
  @Component({
7
9
  selector: 'gn-ui-form-field-wrapper',
@@ -9,7 +11,13 @@ import { TranslateDirective } from '@ngx-translate/core'
9
11
  styleUrls: ['./form-field-wrapper.component.css'],
10
12
  changeDetection: ChangeDetectionStrategy.OnPush,
11
13
  standalone: true,
12
- imports: [MatTooltipModule, CommonModule, TranslateDirective],
14
+ imports: [
15
+ MatTooltipModule,
16
+ CommonModule,
17
+ TranslateDirective,
18
+ NgIconComponent,
19
+ ],
20
+ providers: [provideIcons({ matHelpOutline })],
13
21
  })
14
22
  export class FormFieldWrapperComponent {
15
23
  @Input() label?: string
@@ -37,7 +37,7 @@
37
37
  (click)="handleRowClick(item)"
38
38
  data-cy="table-row"
39
39
  [attr.data-cy-title]="item.title"
40
- [title]="getItemTitle(item) | translate"
40
+ [title]="getItemTitle(item) | translate: { recordKind: item['kind'] }"
41
41
  >
42
42
  <div
43
43
  class="relative h-0"