geonetwork-ui 2.10.0-dev.574947486 → 2.10.0-dev.6183d942b

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 (143) hide show
  1. package/fesm2022/geonetwork-ui.mjs +913 -5829
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +218 -90
  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 +68 -34
  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/lib/model/record/metadata.model.ts +13 -5
  40. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  41. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  42. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  43. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  44. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
  45. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  46. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  47. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  48. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  49. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  50. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  51. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  52. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  53. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  54. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  55. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  56. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +32 -34
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +11 -9
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +12 -0
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  73. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -1
  75. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +77 -5
  76. package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
  77. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  78. package/src/libs/feature/editor/src/lib/services/editor.service.ts +4 -9
  79. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  80. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  81. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  82. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  83. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  84. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  85. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  86. package/src/libs/ui/dataviz/src/index.ts +0 -1
  87. package/src/libs/ui/elements/src/index.ts +2 -0
  88. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  89. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  90. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  91. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  92. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  93. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  94. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  95. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  96. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  97. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  98. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  99. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  100. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +39 -68
  101. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +59 -11
  102. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  103. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  104. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  105. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  106. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  107. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  108. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  109. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  110. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  111. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  112. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  113. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  114. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  115. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  116. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  117. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  118. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  119. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  120. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  121. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  122. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  123. package/src/libs/util/i18n/src/index.ts +0 -1
  124. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  125. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  126. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  127. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  128. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  129. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  130. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  131. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  132. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  133. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  134. package/style.css +0 -1
  135. package/tailwind.base.css +19 -2
  136. package/translations/de.json +33 -20
  137. package/translations/en.json +33 -20
  138. package/translations/es.json +33 -20
  139. package/translations/fr.json +33 -20
  140. package/translations/it.json +33 -20
  141. package/translations/nl.json +33 -20
  142. package/translations/pt.json +33 -20
  143. package/translations/sk.json +33 -20
@@ -0,0 +1,88 @@
1
+ <div
2
+ class="bg-gray-50 text-black rounded border border-gray-200 shadow-md p-4 flex flex-col gap-2 w-full"
3
+ data-test="contact-details"
4
+ >
5
+ @if (displayName) {
6
+ <div class="flex items-center gap-2.5">
7
+ @if (organization?.logoUrl?.href) {
8
+ <div
9
+ class="flex items-center justify-center rounded-lg bg-white w-14 h-14 shrink-0 overflow-hidden"
10
+ >
11
+ <gn-ui-thumbnail
12
+ class="relative h-full w-full"
13
+ [thumbnailUrl]="organization.logoUrl.href"
14
+ fit="contain"
15
+ ></gn-ui-thumbnail>
16
+ </div>
17
+ }
18
+ <span
19
+ class="font-title text-xl leading-tight"
20
+ data-test="contact-details-name"
21
+ >{{ displayName }}</span
22
+ >
23
+ </div>
24
+ }
25
+
26
+ @if (contact.email || contact.phone) {
27
+ <div
28
+ class="grid gap-1 items-start"
29
+ [class.grid-cols-2]="contact.email && contact.phone"
30
+ >
31
+ @if (contact.email) {
32
+ <div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
33
+ <ng-icon
34
+ class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
35
+ name="matMailOutline"
36
+ ></ng-icon>
37
+ <a
38
+ [href]="'mailto:' + contact.email"
39
+ class="text-sm break-all hover:underline"
40
+ data-test="contact-details-email"
41
+ >{{ contact.email }}</a
42
+ >
43
+ </div>
44
+ }
45
+ @if (contact.phone) {
46
+ <div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
47
+ <ng-icon
48
+ class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
49
+ name="matCallOutline"
50
+ ></ng-icon>
51
+ <span class="text-sm" data-test="contact-details-phone">{{
52
+ contact.phone
53
+ }}</span>
54
+ </div>
55
+ }
56
+ </div>
57
+ }
58
+
59
+ @if (addressLines.length) {
60
+ <div class="bg-gray-100 rounded-lg flex items-start gap-2 px-3 py-2">
61
+ <ng-icon
62
+ class="!w-5 !h-5 !text-[20px] shrink-0 mt-0.5 opacity-30"
63
+ name="matLocationOnOutline"
64
+ ></ng-icon>
65
+ <div class="flex flex-col" data-test="contact-details-address">
66
+ @for (line of addressLines; track line) {
67
+ <p class="text-sm m-0">{{ line }}</p>
68
+ }
69
+ </div>
70
+ </div>
71
+ }
72
+
73
+ @if (organization?.website) {
74
+ <div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
75
+ <ng-icon
76
+ class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
77
+ name="matOpenInNew"
78
+ ></ng-icon>
79
+ <a
80
+ [href]="organization.website.href"
81
+ target="_blank"
82
+ class="text-sm break-all hover:underline"
83
+ data-test="contact-details-website"
84
+ >{{ organization.website.href }}</a
85
+ >
86
+ </div>
87
+ }
88
+ </div>
@@ -0,0 +1,45 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
2
+ import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+ import {
4
+ getAddressLines,
5
+ getIndividualDisplayName,
6
+ } from '../../../../../../libs/util/shared/src'
7
+ import { NgIcon, provideIcons } from '@ng-icons/core'
8
+ import { matMailOutline, matOpenInNew } from '@ng-icons/material-icons/baseline'
9
+ import {
10
+ matCallOutline,
11
+ matLocationOnOutline,
12
+ } from '@ng-icons/material-icons/outline'
13
+ import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
14
+ import { TranslateModule } from '@ngx-translate/core'
15
+
16
+ @Component({
17
+ selector: 'gn-ui-contact-details',
18
+ templateUrl: './contact-details.component.html',
19
+ changeDetection: ChangeDetectionStrategy.OnPush,
20
+ standalone: true,
21
+ imports: [NgIcon, ThumbnailComponent, TranslateModule],
22
+ viewProviders: [
23
+ provideIcons({
24
+ matCallOutline,
25
+ matLocationOnOutline,
26
+ matMailOutline,
27
+ matOpenInNew,
28
+ }),
29
+ ],
30
+ })
31
+ export class ContactDetailsComponent {
32
+ @Input() contact: Individual
33
+
34
+ get organization() {
35
+ return this.contact?.organization
36
+ }
37
+
38
+ get displayName(): string {
39
+ return getIndividualDisplayName(this.contact)
40
+ }
41
+
42
+ get addressLines(): string[] {
43
+ return getAddressLines(this.contact?.address)
44
+ }
45
+ }
@@ -0,0 +1,41 @@
1
+ <gn-ui-button
2
+ [type]="overlayOpen ? 'gray-light' : 'primary-light'"
3
+ extraClass="group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded"
4
+ data-test="contact-pill"
5
+ (buttonClick)="toggleOverlay()"
6
+ cdkOverlayOrigin
7
+ #overlayOrigin="cdkOverlayOrigin"
8
+ >
9
+ <span
10
+ class="font-title font-medium text-base leading-tight truncate"
11
+ [class]="!overlayOpen ? 'text-primary-black group-hover:text-white' : ''"
12
+ [title]="displayName"
13
+ >{{ displayName }}</span
14
+ >
15
+ <div
16
+ class="gn-ui-card-icon items-center justify-center w-10 h-8"
17
+ [class]="
18
+ !overlayOpen ? 'group-hover:border-white group-hover:text-white' : ''
19
+ "
20
+ >
21
+ @if (overlayOpen) {
22
+ <ng-icon class="!w-6 !h-6 !text-[24px]" name="matClose"></ng-icon>
23
+ } @else {
24
+ <ng-icon class="!w-6 !h-6 !text-[24px]" name="matInfoOutline"></ng-icon>
25
+ }
26
+ </div>
27
+ </gn-ui-button>
28
+
29
+ <ng-template
30
+ cdkConnectedOverlay
31
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
32
+ [cdkConnectedOverlayOpen]="overlayOpen"
33
+ [cdkConnectedOverlayPositions]="overlayPositions"
34
+ [cdkConnectedOverlayOffsetX]="overlayOffsetX"
35
+ (overlayOutsideClick)="closeOverlay()"
36
+ (detach)="closeOverlay()"
37
+ >
38
+ <div [style.width.px]="overlayWidth">
39
+ <gn-ui-contact-details [contact]="contact"></gn-ui-contact-details>
40
+ </div>
41
+ </ng-template>
@@ -0,0 +1,70 @@
1
+ import { ConnectedPosition, OverlayModule } from '@angular/cdk/overlay'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ElementRef,
6
+ inject,
7
+ Input,
8
+ } from '@angular/core'
9
+ import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
10
+ import { NgIcon, provideIcons } from '@ng-icons/core'
11
+ import { matClose } from '@ng-icons/material-icons/baseline'
12
+ import { matInfoOutline } from '@ng-icons/material-icons/outline'
13
+ import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
14
+ import { getIndividualDisplayName } from '../../../../../../libs/util/shared/src'
15
+ import { ContactDetailsComponent } from '../contact-details/contact-details.component'
16
+
17
+ @Component({
18
+ selector: 'gn-ui-contact-pill',
19
+ templateUrl: './contact-pill.component.html',
20
+ changeDetection: ChangeDetectionStrategy.OnPush,
21
+ standalone: true,
22
+ imports: [NgIcon, ButtonComponent, OverlayModule, ContactDetailsComponent],
23
+ viewProviders: [provideIcons({ matClose, matInfoOutline })],
24
+ })
25
+ export class ContactPillComponent {
26
+ @Input() contact: Individual
27
+
28
+ private host = inject(ElementRef<HTMLElement>)
29
+
30
+ overlayOpen = false
31
+ overlayWidth = 0
32
+ overlayOffsetX = 0
33
+ overlayPositions: ConnectedPosition[] = [
34
+ {
35
+ originX: 'start',
36
+ originY: 'bottom',
37
+ overlayX: 'start',
38
+ overlayY: 'top',
39
+ offsetY: 4,
40
+ },
41
+ {
42
+ originX: 'start',
43
+ originY: 'top',
44
+ overlayX: 'start',
45
+ overlayY: 'bottom',
46
+ offsetY: -4,
47
+ },
48
+ ]
49
+
50
+ get displayName(): string {
51
+ return getIndividualDisplayName(this.contact)
52
+ }
53
+
54
+ toggleOverlay() {
55
+ if (!this.overlayOpen) {
56
+ // Calculate the width and horizontal offset of the overlay to align it with the parent element
57
+ const parent =
58
+ this.host.nativeElement.parentElement.getBoundingClientRect()
59
+ const pill = this.host.nativeElement.getBoundingClientRect()
60
+ this.overlayWidth = parent.width
61
+ this.overlayOffsetX = parent.left - pill.left
62
+ }
63
+
64
+ this.overlayOpen = !this.overlayOpen
65
+ }
66
+
67
+ closeOverlay() {
68
+ this.overlayOpen = false
69
+ }
70
+ }
@@ -0,0 +1,13 @@
1
+ /** the animation values are made to be identical to the cdk overlay values */
2
+ .lightbox-image {
3
+ animation: show 400ms cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
4
+ opacity: 0;
5
+ transform: scale(0.9);
6
+ }
7
+
8
+ @keyframes show {
9
+ 100% {
10
+ opacity: 1;
11
+ transform: none;
12
+ }
13
+ }
@@ -1,5 +1,5 @@
1
1
  <gn-ui-content-ghost
2
- [showContent]="imageUrl !== undefined"
2
+ [showContent]="imageUrl() !== undefined"
3
3
  ghostClass="h-full w-full"
4
4
  >
5
5
  @if (imageUrl) {
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <gn-ui-thumbnail
11
11
  class="relative h-full w-full"
12
- [thumbnailUrl]="imageUrl"
12
+ [thumbnailUrl]="imageUrl()"
13
13
  fit="cover"
14
14
  (placeholderShown)="isPlaceholderShown.emit($event)"
15
15
  ></gn-ui-thumbnail>
@@ -18,7 +18,7 @@
18
18
  class="absolute bottom-0 right-0 z-10 mr-2 mb-2"
19
19
  [type]="'outline'"
20
20
  [extraClass]="'!py-2 !px-0'"
21
- (buttonClick)="openLightbox(imageUrl)"
21
+ (buttonClick)="openLightbox()"
22
22
  >
23
23
  <ng-icon name="matZoomOutMap"></ng-icon>
24
24
  </gn-ui-button>
@@ -26,3 +26,13 @@
26
26
  </div>
27
27
  }
28
28
  </gn-ui-content-ghost>
29
+
30
+ <ng-template #imageTemplate>
31
+ <img
32
+ data-test="lightbox-image"
33
+ class="lightbox-image bg-white max-h-[90vh] max-w-[90vw]"
34
+ [src]="imageUrl()"
35
+ alt="Preview image"
36
+ (click)="closeLightbox()"
37
+ />
38
+ </ng-template>
@@ -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 }>
@@ -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() {
@@ -16,7 +16,3 @@
16
16
  --gn-ui-badge-background-color: var(--color-primary-white);
17
17
  --gn-ui-badge-text-color: var(--color-primary-darkest);
18
18
  }
19
-
20
- :host .metadata-info-keywords ::ng-deep gn-ui-badge:hover {
21
- --gn-ui-badge-text-color: white;
22
- }
@@ -28,12 +28,23 @@
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
+ <ng-template #keywordTooltip>
32
+ @for (segment of keywordTooltipSegments(keyword); track $index) {
33
+ <span [class.font-bold]="$last">{{ segment }}</span>
34
+ @if (!$last) {
35
+ <span> &gt; </span>
36
+ }
37
+ }
38
+ </ng-template>
39
+ <gn-ui-popover [content]="keywordTooltip" theme="dark">
40
+ <gn-ui-button
41
+ type="primary-light"
42
+ class="inline-block opacity-70"
43
+ extraClass="lowercase text-sm py-1 px-2"
44
+ (buttonClick)="onKeywordClick(keyword)"
45
+ >{{ keyword.label }}</gn-ui-button
46
+ >
47
+ </gn-ui-popover>
37
48
  }
38
49
  </div>
39
50
  </gn-ui-max-lines>
@@ -52,7 +63,7 @@
52
63
  data-test="usage-panel"
53
64
  >
54
65
  <div class="flex flex-col gap-[10px] mr-4 py-[12px] rounded text-gray-900">
55
- @for (license of licenses; track license) {
66
+ @for (license of licenses; track $index) {
56
67
  @if (license.url) {
57
68
  <div class="text-primary">
58
69
  <a
@@ -105,9 +116,29 @@
105
116
  </div>
106
117
  </gn-ui-expandable-panel>
107
118
  }
119
+ @if (metadata.contactsForResource?.length) {
120
+ <gn-ui-expandable-panel
121
+ [title]="'record.metadata.resource.contacts' | translate"
122
+ data-test="contacts-panel"
123
+ >
124
+ <div class="flex flex-col gap-1 pt-3 pb-4">
125
+ @for (group of contactGroups; track group.role) {
126
+ <div class="flex flex-col gap-1 rounded py-4 px-2">
127
+ <p class="text-xs font-normal text-black">
128
+ {{ group.roleLabel | translate }}
129
+ </p>
130
+ <div class="grid gap-1 grid-cols-1 md:grid-cols-2">
131
+ @for (contact of group.contacts; track contact.email) {
132
+ <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
133
+ }
134
+ </div>
135
+ </div>
136
+ }
137
+ </div>
138
+ </gn-ui-expandable-panel>
139
+ }
108
140
  @if (
109
141
  (metadata.kind === 'dataset' && metadata.lineage) ||
110
- resourceContact ||
111
142
  metadata.resourceCreated ||
112
143
  metadata.resourcePublished ||
113
144
  metadata.resourceUpdated ||
@@ -129,66 +160,6 @@
129
160
  </p>
130
161
  </div>
131
162
  }
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
163
  <div
193
164
  class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
194
165
  >