geonetwork-ui 2.10.0-dev.91673b3ba → 2.10.0-dev.981f59a75

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 (161) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1536 -6196
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +257 -182
  4. package/index.d.ts.map +1 -1
  5. package/package.json +25 -35
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +6 -5
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +12 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +14 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +36 -0
  31. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  32. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  33. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +59 -37
  34. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  35. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  36. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +37 -2
  37. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  38. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  39. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  40. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  41. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  42. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +56 -0
  43. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  44. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  45. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  46. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  47. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  49. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +68 -0
  50. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  51. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  52. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  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 +33 -34
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  62. 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
  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.html +5 -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.ts +89 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  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-update-frequency/form-field-update-frequency.component.html +6 -2
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +21 -16
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +14 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  75. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  76. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  77. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  78. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  79. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  80. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  81. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  82. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  83. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  84. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  85. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -0
  86. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +229 -0
  87. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  88. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  89. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  90. package/src/libs/ui/dataviz/src/index.ts +0 -1
  91. package/src/libs/ui/elements/src/index.ts +2 -0
  92. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  93. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  94. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +29 -4
  95. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +51 -7
  96. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  97. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  98. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  99. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  100. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  101. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  102. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  103. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  104. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  105. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  106. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +7 -10
  107. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  108. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  109. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  110. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  111. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  112. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  113. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  114. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  115. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  116. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  117. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  118. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  119. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  120. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  121. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  122. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  123. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  124. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  125. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  126. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  127. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  128. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  129. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  130. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  131. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  132. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  133. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  134. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  135. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  136. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  137. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  138. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  139. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  140. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  141. package/src/libs/util/i18n/src/index.ts +0 -1
  142. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  143. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  144. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  145. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  146. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  147. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  148. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  149. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  150. package/src/libs/util/shared/src/lib/utils/user-display.ts +9 -0
  151. package/style.css +0 -1
  152. package/tailwind.base.css +20 -2
  153. package/translations/de.json +93 -48
  154. package/translations/en.json +98 -53
  155. package/translations/es.json +67 -22
  156. package/translations/fr.json +95 -50
  157. package/translations/it.json +94 -49
  158. package/translations/nl.json +66 -21
  159. package/translations/pt.json +67 -22
  160. package/translations/sk.json +67 -22
  161. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -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
+ }
@@ -1,16 +1,41 @@
1
1
  <gn-ui-button
2
- type="primary-light"
2
+ [type]="overlayOpen ? 'gray-light' : 'primary-light'"
3
3
  extraClass="group w-full min-h-12 gap-3 justify-between py-2 pl-5 pr-4 rounded"
4
4
  data-test="contact-pill"
5
+ (buttonClick)="toggleOverlay()"
6
+ cdkOverlayOrigin
7
+ #overlayOrigin="cdkOverlayOrigin"
5
8
  >
6
9
  <span
7
- class="font-title font-medium text-base leading-tight truncate group-hover:text-white"
10
+ class="font-title font-medium text-base leading-tight truncate"
11
+ [class]="!overlayOpen ? 'text-primary-black group-hover:text-white' : ''"
8
12
  [title]="displayName"
9
13
  >{{ displayName }}</span
10
14
  >
11
15
  <div
12
- class="gn-ui-card-icon items-center justify-center w-10 h-8 group-hover:border-white group-hover:text-white"
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
+ "
13
20
  >
14
- <ng-icon class="!w-6 !h-6 !text-[24px]" name="matInfoOutline"></ng-icon>
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
+ }
15
26
  </div>
16
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>
@@ -1,26 +1,70 @@
1
- import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
1
+ import { ConnectedPosition, OverlayModule } from '@angular/cdk/overlay'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ ElementRef,
6
+ inject,
7
+ Input,
8
+ } from '@angular/core'
2
9
  import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
3
10
  import { NgIcon, provideIcons } from '@ng-icons/core'
11
+ import { matClose } from '@ng-icons/material-icons/baseline'
4
12
  import { matInfoOutline } from '@ng-icons/material-icons/outline'
5
13
  import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
6
14
  import { getIndividualDisplayName } from '../../../../../../libs/util/shared/src'
15
+ import { ContactDetailsComponent } from '../contact-details/contact-details.component'
7
16
 
8
17
  @Component({
9
18
  selector: 'gn-ui-contact-pill',
10
19
  templateUrl: './contact-pill.component.html',
11
20
  changeDetection: ChangeDetectionStrategy.OnPush,
12
21
  standalone: true,
13
- imports: [NgIcon, ButtonComponent],
14
- viewProviders: [
15
- provideIcons({
16
- matInfoOutline,
17
- }),
18
- ],
22
+ imports: [NgIcon, ButtonComponent, OverlayModule, ContactDetailsComponent],
23
+ viewProviders: [provideIcons({ matClose, matInfoOutline })],
19
24
  })
20
25
  export class ContactPillComponent {
21
26
  @Input() contact: Individual
22
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
+
23
50
  get displayName(): string {
24
51
  return getIndividualDisplayName(this.contact)
25
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
+ }
26
70
  }
@@ -54,8 +54,9 @@
54
54
  class="inline-flex items-center justify-center px-2 py-1 text-13 font-medium leading-none text-white rounded transition-opacity opacity-70 group-hover:opacity-100"
55
55
  [style.background-color]="getLinkColor(link)"
56
56
  data-cy="download-format"
57
+ translate
57
58
  >
58
- {{ getLinkFormat(link) || ('downloads.format.unknown' | translate) }}
59
+ downloads.format.{{ getLinkFormat(link) || 'unknown' }}
59
60
  </span>
60
61
  </div>
61
62
  </div>
@@ -9,7 +9,7 @@ import {
9
9
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
10
10
  import { iconoirDatabase } from '@ng-icons/iconoir'
11
11
  import { getBadgeColor, getFileFormat } from '../../../../../../libs/util/shared/src'
12
- import { TranslatePipe } from '@ngx-translate/core'
12
+ import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
13
13
 
14
14
  type CardSize = 'L' | 'M' | 'S' | 'XS'
15
15
  @Component({
@@ -18,7 +18,7 @@ type CardSize = 'L' | 'M' | 'S' | 'XS'
18
18
  styleUrls: ['./external-link-card.component.css'],
19
19
  changeDetection: ChangeDetectionStrategy.OnPush,
20
20
  standalone: true,
21
- imports: [CommonModule, NgIconComponent, TranslatePipe],
21
+ imports: [CommonModule, NgIconComponent, TranslateDirective, TranslatePipe],
22
22
  providers: [
23
23
  provideIcons({
24
24
  matOpenInNew,
@@ -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
  }
@@ -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() {
@@ -16,3 +16,8 @@
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
+ .metadata-info-keywords {
21
+ --gn-ui-badge-padding: 0.25rem 0.5rem;
22
+ text-transform: lowercase;
23
+ }
@@ -28,13 +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-button
32
- type="primary-light"
33
- class="inline-block opacity-70"
34
- extraClass="lowercase text-sm py-1 px-2"
35
- (buttonClick)="onKeywordClick(keyword)"
36
- >{{ keyword.label }}</gn-ui-button
37
- >
31
+ <gn-ui-keyword-badge
32
+ [keyword]="keyword"
33
+ (keywordClick)="onKeywordClick($event)"
34
+ ></gn-ui-keyword-badge>
38
35
  }
39
36
  </div>
40
37
  </gn-ui-max-lines>
@@ -53,7 +50,7 @@
53
50
  data-test="usage-panel"
54
51
  >
55
52
  <div class="flex flex-col gap-[10px] mr-4 py-[12px] rounded text-gray-900">
56
- @for (license of licenses; track license) {
53
+ @for (license of licenses; track $index) {
57
54
  @if (license.url) {
58
55
  <div class="text-primary">
59
56
  <a
@@ -113,11 +110,11 @@
113
110
  >
114
111
  <div class="flex flex-col gap-1 pt-3 pb-4">
115
112
  @for (group of contactGroups; track group.role) {
116
- <div class="flex flex-col gap-1 rounded bg-gray-50 py-4 px-2">
113
+ <div class="flex flex-col gap-1 rounded py-4 px-2">
117
114
  <p class="text-xs font-normal text-black">
118
115
  {{ group.roleLabel | translate }}
119
116
  </p>
120
- <div class="grid gap-0.5 grid-cols-1 md:grid-cols-2">
117
+ <div class="grid gap-1 grid-cols-1 md:grid-cols-2">
121
118
  @for (contact of group.contacts; track contact.email) {
122
119
  <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
123
120
  }
@@ -22,10 +22,10 @@ import {
22
22
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
23
23
  import {
24
24
  BadgeComponent,
25
- ButtonComponent,
26
25
  CopyTextButtonComponent,
27
26
  } from '../../../../../../libs/ui/inputs/src'
28
27
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
28
+ import { KeywordBadgeComponent } from '../keyword-badge/keyword-badge.component'
29
29
  import { NgIcon, provideIcons } from '@ng-icons/core'
30
30
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
31
31
  import { matMailOutline } from '@ng-icons/material-icons/outline'
@@ -46,7 +46,6 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
46
46
  TranslatePipe,
47
47
  MarkdownParserComponent,
48
48
  ExpandablePanelComponent,
49
- ButtonComponent,
50
49
  BadgeComponent,
51
50
  ContentGhostComponent,
52
51
  MaxLinesComponent,
@@ -56,6 +55,7 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
56
55
  GnUiHumanizeDateDirective,
57
56
  SpatialExtentComponent,
58
57
  ContactPillComponent,
58
+ KeywordBadgeComponent,
59
59
  ],
60
60
  viewProviders: [
61
61
  provideIcons({
@@ -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
  }