geonetwork-ui 2.10.0-dev.b6696be21 → 2.10.0-dev.bfea85a63

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 (105) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1016 -6093
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +144 -149
  4. package/index.d.ts.map +1 -1
  5. package/package.json +23 -33
  6. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  7. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +5 -4
  8. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  9. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  10. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -4
  11. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  12. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -5
  13. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  14. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  15. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +2 -2
  16. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -0
  17. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  18. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +51 -0
  19. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  20. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  21. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  22. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  23. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  24. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  25. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  26. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  27. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  28. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  29. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  30. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  31. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  32. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  33. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  34. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  35. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  36. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  37. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +20 -16
  38. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +9 -0
  39. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  40. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  41. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  42. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  43. package/src/libs/feature/editor/src/lib/fields.config.ts +251 -290
  44. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +2 -3
  45. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  46. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  47. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  48. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  49. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  50. package/src/libs/ui/dataviz/src/index.ts +0 -1
  51. package/src/libs/ui/elements/src/index.ts +1 -0
  52. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  53. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  54. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  55. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  56. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  57. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  58. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  59. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  60. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  61. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  62. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +4 -7
  63. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  64. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  65. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  66. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  67. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  68. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  69. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  70. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  71. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  72. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  73. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  74. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  75. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  76. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  77. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  78. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  79. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  80. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  81. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  82. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  83. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  84. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  85. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  86. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  87. package/src/libs/util/i18n/src/index.ts +0 -1
  88. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  89. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  90. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  91. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  92. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  93. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  94. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  95. package/style.css +0 -1
  96. package/tailwind.base.css +9 -0
  97. package/translations/de.json +53 -29
  98. package/translations/en.json +58 -34
  99. package/translations/es.json +27 -3
  100. package/translations/fr.json +55 -31
  101. package/translations/it.json +53 -29
  102. package/translations/nl.json +26 -2
  103. package/translations/pt.json +27 -3
  104. package/translations/sk.json +27 -3
  105. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -431,11 +431,19 @@ export class MapViewComponent implements AfterViewInit {
431
431
  }),
432
432
  withLatestFrom(this.mdViewFacade.metadata$),
433
433
  map(([context, metadata]) => {
434
- if (context.view) return context
434
+ // overlay the record's declared spatial extent on top of the data layers
435
+ const extentLayer = this.mapUtils.getRecordExtentLayer(metadata)
436
+ const layers = extentLayer
437
+ ? [...context.layers, extentLayer]
438
+ : context.layers
439
+ // the view (initial zoom) is derived from the data layer or, as a
440
+ // fallback, from the record extent — independently from the overlay above
441
+ if (context.view) return { ...context, layers }
435
442
  const extent = this.mapUtils.getRecordExtent(metadata)
436
443
  const view = extent ? { extent } : null
437
444
  return {
438
445
  ...context,
446
+ layers,
439
447
  view,
440
448
  }
441
449
  }),
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  Component,
3
3
  EventEmitter,
4
+ inject,
4
5
  Input,
5
6
  OnDestroy,
6
7
  Output,
7
- inject,
8
8
  } from '@angular/core'
9
9
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
10
10
  import { SearchFacade } from '../state/search.facade'
@@ -67,6 +67,7 @@ export class ResultsTableContainerComponent implements OnDestroy {
67
67
 
68
68
  async handleDeleteRecord(item: unknown) {
69
69
  const uniqueIdentifier = (item as CatalogRecord).uniqueIdentifier
70
+ const recordKind = (item as CatalogRecord).kind
70
71
  this.subscription.add(
71
72
  this.recordsRepository.deleteRecord(uniqueIdentifier).subscribe({
72
73
  next: () => {
@@ -76,10 +77,12 @@ export class ResultsTableContainerComponent implements OnDestroy {
76
77
  {
77
78
  type: 'success',
78
79
  title: this.translateService.instant(
79
- 'editor.record.deleteSuccess.title'
80
+ 'editor.record.deleteSuccess.title',
81
+ { recordKind }
80
82
  ),
81
83
  text: `${this.translateService.instant(
82
- 'editor.record.deleteSuccess.body'
84
+ 'editor.record.deleteSuccess.body',
85
+ { recordKind }
83
86
  )}`,
84
87
  },
85
88
  2500
@@ -90,10 +93,12 @@ export class ResultsTableContainerComponent implements OnDestroy {
90
93
  {
91
94
  type: 'error',
92
95
  title: this.translateService.instant(
93
- 'editor.record.deleteError.title'
96
+ 'editor.record.deleteError.title',
97
+ { recordKind }
94
98
  ),
95
99
  text: `${this.translateService.instant(
96
- 'editor.record.deleteError.body'
100
+ 'editor.record.deleteError.body',
101
+ { recordKind }
97
102
  )} ${error}`,
98
103
  closeMessage: this.translateService.instant(
99
104
  'editor.record.deleteError.closeMessage'
@@ -1,4 +1,4 @@
1
- import { Component, InjectionToken, inject } from '@angular/core'
1
+ import { Component, inject, InjectionToken } from '@angular/core'
2
2
  import { LANGUAGE_STORAGE_KEY } from '../../../../../../libs/util/i18n/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { DropdownSelectorComponent } from '../../../../../../libs/ui/inputs/src'
@@ -26,7 +26,7 @@ export class LanguageSwitcherComponent {
26
26
  )
27
27
 
28
28
  get currentLang() {
29
- return this.translate.currentLang
29
+ return this.translate.getCurrentLang()
30
30
  }
31
31
 
32
32
  changeLanguage(value: unknown) {
@@ -1,4 +1,3 @@
1
1
  export * from './lib/chart/chart.component'
2
2
  export * from './lib/data-table/data-table.component'
3
- export * from './lib/data-table/data-table.fixtures'
4
3
  export * from './lib/figure/figure.component'
@@ -11,6 +11,7 @@ export * from './lib/image-overlay-preview/image-overlay-preview.component'
11
11
  export * from './lib/kind-badge/kind-badge.component'
12
12
  export * from './lib/external-link-card/external-link-card.component'
13
13
  export * from './lib/markdown-editor/markdown-editor.component'
14
+ export * from './lib/keyword-badge/keyword-badge.component'
14
15
  export * from './lib/markdown-parser/markdown-parser.component'
15
16
  export * from './lib/metadata-catalog/metadata-catalog.component'
16
17
  export * from './lib/metadata-contact/metadata-contact.component'
@@ -1,12 +1,12 @@
1
1
  <div
2
- class="bg-gray-50 text-black rounded border border-gray-200 shadow-md p-4 flex flex-col gap-3 w-full"
2
+ class="bg-gray-50 text-black rounded border border-gray-200 shadow-md p-4 flex flex-col gap-2 w-full"
3
3
  data-test="contact-details"
4
4
  >
5
5
  @if (displayName) {
6
- <div class="flex items-center gap-3">
6
+ <div class="flex items-center gap-2.5">
7
7
  @if (organization?.logoUrl?.href) {
8
8
  <div
9
- class="flex items-center justify-center rounded-md bg-white w-14 h-14 shrink-0 overflow-hidden"
9
+ class="flex items-center justify-center rounded-lg bg-white w-14 h-14 shrink-0 overflow-hidden"
10
10
  >
11
11
  <gn-ui-thumbnail
12
12
  class="relative h-full w-full"
@@ -23,74 +23,66 @@
23
23
  </div>
24
24
  }
25
25
 
26
- <!-- Email + phone: always shown; icon dimmed when field is absent -->
27
- <div class="grid grid-cols-2 gap-1 rounded-md overflow-hidden">
28
- <div class="bg-gray-100 flex items-center gap-2 px-3 py-2">
29
- <ng-icon
30
- class="!w-5 !h-5 !text-[20px] shrink-0"
31
- [class.opacity-30]="!contact.email"
32
- [class.opacity-75]="!!contact.email"
33
- name="matMailOutline"
34
- ></ng-icon>
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
+ >
35
31
  @if (contact.email) {
36
- <a
37
- [href]="'mailto:' + contact.email"
38
- class="text-sm break-all hover:underline"
39
- data-test="contact-details-email"
40
- >{{ contact.email }}</a
41
- >
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>
42
44
  }
43
- </div>
44
- <div class="bg-gray-100 flex items-center gap-2 px-3 py-2">
45
- <ng-icon
46
- class="!w-5 !h-5 !text-[20px] shrink-0"
47
- [class.opacity-30]="!contact.phone"
48
- [class.opacity-75]="!!contact.phone"
49
- name="matCallOutline"
50
- ></ng-icon>
51
45
  @if (contact.phone) {
52
- <span class="text-sm" data-test="contact-details-phone">{{
53
- contact.phone
54
- }}</span>
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
55
  }
56
56
  </div>
57
- </div>
57
+ }
58
58
 
59
- <!-- Address: always shown; icon dimmed when absent -->
60
- <div class="bg-gray-100 rounded-md 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"
63
- [class.opacity-30]="!addressLines.length"
64
- [class.opacity-75]="!!addressLines.length"
65
- name="matLocationOnOutline"
66
- ></ng-icon>
67
- @if (addressLines.length) {
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>
68
65
  <div class="flex flex-col" data-test="contact-details-address">
69
66
  @for (line of addressLines; track line) {
70
67
  <p class="text-sm m-0">{{ line }}</p>
71
68
  }
72
69
  </div>
73
- }
74
- </div>
70
+ </div>
71
+ }
75
72
 
76
- <!-- Website: only shown when org exists; icon dimmed when absent -->
77
- @if (organization) {
78
- <div class="bg-gray-100 rounded-md flex items-center gap-2 px-3 py-2">
73
+ @if (organization?.website) {
74
+ <div class="bg-gray-100 rounded-lg flex items-center gap-2 px-3 py-2">
79
75
  <ng-icon
80
- class="!w-5 !h-5 !text-[20px] shrink-0"
81
- [class.opacity-30]="!organization.website"
82
- [class.opacity-75]="!!organization.website"
76
+ class="!w-5 !h-5 !text-[20px] shrink-0 opacity-30"
83
77
  name="matOpenInNew"
84
78
  ></ng-icon>
85
- @if (organization.website) {
86
- <a
87
- [href]="organization.website.href"
88
- target="_blank"
89
- class="text-sm break-all hover:underline"
90
- data-test="contact-details-website"
91
- >{{ organization.website.href }}</a
92
- >
93
- }
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
+ >
94
86
  </div>
95
87
  }
96
88
  </div>
@@ -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
  }
@@ -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>
@@ -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
+ }