geonetwork-ui 2.9.0-dev.fc19cbfed → 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 (174) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2382 -6943
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +378 -196
  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 -37
  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 +2 -0
  40. package/src/libs/common/domain/src/lib/model/index.ts +3 -0
  41. package/src/libs/common/domain/src/lib/model/record/index.ts +2 -2
  42. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  43. package/src/libs/common/domain/src/lib/model/search/index.ts +1 -1
  44. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  45. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  46. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  47. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  48. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +56 -0
  49. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  50. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.html +3 -3
  51. package/src/libs/feature/editor/src/index.ts +2 -0
  52. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  53. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  54. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  55. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  56. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  57. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  58. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  59. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  60. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  61. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  62. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  63. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +11 -6
  64. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +18 -2
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +33 -34
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  69. 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
  70. 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
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  72. 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
  73. 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
  74. 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
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  79. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  80. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  81. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  82. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +21 -16
  83. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +14 -0
  84. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  85. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  86. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  87. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  88. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  89. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  90. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  91. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  92. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  93. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  94. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  95. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  96. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  97. package/src/libs/feature/record/src/lib/gpf-api-dl/gpf-api-dl.component.ts +8 -4
  98. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  99. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  100. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  101. package/src/libs/ui/dataviz/src/index.ts +0 -1
  102. package/src/libs/ui/elements/src/index.ts +3 -0
  103. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  104. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  105. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  106. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  107. package/src/libs/ui/elements/src/lib/downloads-list/downloads-list.component.ts +5 -3
  108. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  109. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  110. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  111. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  112. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  113. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  114. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  115. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  116. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  117. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  118. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  119. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +3 -2
  120. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +26 -68
  121. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  122. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  123. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  124. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  125. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  126. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  127. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  128. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  129. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  130. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.ts +1 -1
  131. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  132. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +6 -2
  133. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  134. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  135. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  136. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  137. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  138. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  139. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  140. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  141. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  142. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  143. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  144. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  145. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  146. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  147. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  148. package/src/libs/util/app-config/src/lib/app-config.ts +72 -9
  149. package/src/libs/util/app-config/src/lib/model.ts +8 -0
  150. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  151. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  152. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  153. package/src/libs/util/i18n/src/index.ts +0 -1
  154. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  155. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  156. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  157. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  158. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  159. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  160. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  161. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  162. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  163. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  164. package/style.css +0 -1
  165. package/tailwind.base.css +28 -2
  166. package/translations/de.json +82 -48
  167. package/translations/en.json +87 -53
  168. package/translations/es.json +56 -22
  169. package/translations/fr.json +84 -50
  170. package/translations/it.json +82 -48
  171. package/translations/nl.json +55 -21
  172. package/translations/pt.json +56 -22
  173. package/translations/sk.json +56 -22
  174. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -1,10 +1,19 @@
1
- import { Component, EventEmitter, Input, Output } from '@angular/core'
2
- import * as basicLightbox from 'basiclightbox'
1
+ import {
2
+ Component,
3
+ inject,
4
+ input,
5
+ output,
6
+ TemplateRef,
7
+ viewChild,
8
+ ViewContainerRef,
9
+ } from '@angular/core'
3
10
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
4
11
  import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
5
12
  import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
6
13
  import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
7
14
  import { matZoomOutMap } from '@ng-icons/material-icons/baseline'
15
+ import { Overlay, OverlayRef } from '@angular/cdk/overlay'
16
+ import { TemplatePortal } from '@angular/cdk/portal'
8
17
 
9
18
  @Component({
10
19
  selector: 'gn-ui-image-overlay-preview',
@@ -20,9 +29,37 @@ import { matZoomOutMap } from '@ng-icons/material-icons/baseline'
20
29
  ],
21
30
  })
22
31
  export class ImageOverlayPreviewComponent {
23
- @Input() imageUrl: string
24
- @Output() isPlaceholderShown = new EventEmitter<boolean>()
25
- openLightbox(src: string) {
26
- basicLightbox.create(`<img src="${src}"/>`).show()
32
+ imageUrl = input<string>()
33
+ isPlaceholderShown = output<boolean>({})
34
+
35
+ private imageTemplate = viewChild<TemplateRef<HTMLElement>>('imageTemplate')
36
+ private overlay = inject(Overlay)
37
+ private viewContainerRef = inject(ViewContainerRef)
38
+ private overlayRef: OverlayRef
39
+
40
+ openLightbox() {
41
+ const positionStrategy = this.overlay
42
+ .position()
43
+ .global()
44
+ .centerHorizontally()
45
+ .centerVertically()
46
+ const scrollStrategy = this.overlay.scrollStrategies.block()
47
+
48
+ this.overlayRef = this.overlay.create({
49
+ hasBackdrop: true,
50
+ backdropClass: 'bg-black/80',
51
+ positionStrategy,
52
+ scrollStrategy,
53
+ })
54
+ this.overlayRef.backdropClick().subscribe(() => this.overlayRef.dispose())
55
+ const portal = new TemplatePortal(
56
+ this.imageTemplate(),
57
+ this.viewContainerRef
58
+ )
59
+ this.overlayRef.attach(portal)
60
+ }
61
+
62
+ closeLightbox() {
63
+ this.overlayRef?.dispose()
27
64
  }
28
65
  }
@@ -1,6 +1,6 @@
1
1
  <a
2
2
  [attr.href]="linkHref"
3
- [target]="linkTarget"
3
+ target="_self"
4
4
  class="record-card"
5
5
  [ngClass]="cardClass"
6
6
  >
@@ -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
@@ -102,7 +102,7 @@ export class DropdownSelectorComponent implements OnInit {
102
102
  }
103
103
 
104
104
  getChoiceLabel(): string {
105
- return this.selectedChoice?.label
105
+ return this.selectedChoice?.label ?? '\u00A0'
106
106
  }
107
107
 
108
108
  ngOnInit(): void {