geonetwork-ui 2.10.0-dev.6fa5006eb → 2.10.0-dev.842517315

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 (111) hide show
  1. package/fesm2022/geonetwork-ui.mjs +695 -5757
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +165 -78
  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 +54 -32
  35. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  38. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  39. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  40. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  41. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  42. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  43. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
  44. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  45. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  46. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  47. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  49. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  50. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +6 -8
  51. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +9 -2
  52. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +12 -0
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  55. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +1 -0
  56. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  58. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +34 -2
  59. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -2
  60. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +51 -33
  61. package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
  62. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  63. package/src/libs/feature/editor/src/lib/services/editor.service.ts +1 -1
  64. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  65. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  66. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  67. package/src/libs/ui/dataviz/src/index.ts +0 -1
  68. package/src/libs/ui/elements/src/index.ts +1 -0
  69. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  70. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  71. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +29 -4
  72. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +51 -7
  73. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  74. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  75. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  76. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  77. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  78. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +20 -10
  79. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +29 -1
  80. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  81. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  82. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  83. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  84. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  85. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  86. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  87. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  88. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  89. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  90. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  91. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  92. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  93. package/src/libs/util/i18n/src/index.ts +0 -1
  94. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  95. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  96. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  97. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  98. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  99. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  100. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  101. package/src/libs/util/shared/src/lib/utils/user-display.ts +9 -0
  102. package/style.css +0 -1
  103. package/tailwind.base.css +8 -0
  104. package/translations/de.json +31 -20
  105. package/translations/en.json +31 -20
  106. package/translations/es.json +31 -20
  107. package/translations/fr.json +31 -20
  108. package/translations/it.json +31 -20
  109. package/translations/nl.json +31 -20
  110. package/translations/pt.json +31 -20
  111. package/translations/sk.json +31 -20
@@ -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,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() {
@@ -28,13 +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-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
+ <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>
38
48
  }
39
49
  </div>
40
50
  </gn-ui-max-lines>
@@ -53,7 +63,7 @@
53
63
  data-test="usage-panel"
54
64
  >
55
65
  <div class="flex flex-col gap-[10px] mr-4 py-[12px] rounded text-gray-900">
56
- @for (license of licenses; track license) {
66
+ @for (license of licenses; track $index) {
57
67
  @if (license.url) {
58
68
  <div class="text-primary">
59
69
  <a
@@ -113,11 +123,11 @@
113
123
  >
114
124
  <div class="flex flex-col gap-1 pt-3 pb-4">
115
125
  @for (group of contactGroups; track group.role) {
116
- <div class="flex flex-col gap-1 rounded bg-gray-50 py-4 px-2">
126
+ <div class="flex flex-col gap-1 rounded py-4 px-2">
117
127
  <p class="text-xs font-normal text-black">
118
128
  {{ group.roleLabel | translate }}
119
129
  </p>
120
- <div class="grid gap-0.5 grid-cols-1 md:grid-cols-2">
130
+ <div class="grid gap-1 grid-cols-1 md:grid-cols-2">
121
131
  @for (contact of group.contacts; track contact.email) {
122
132
  <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
123
133
  }
@@ -19,12 +19,18 @@ import {
19
19
  ExpandablePanelComponent,
20
20
  MaxLinesComponent,
21
21
  } from '../../../../../../libs/ui/layout/src'
22
- import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
22
+ import {
23
+ TranslateDirective,
24
+ TranslatePipe,
25
+ TranslateService,
26
+ } from '@ngx-translate/core'
27
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
23
28
  import {
24
29
  BadgeComponent,
25
30
  ButtonComponent,
26
31
  CopyTextButtonComponent,
27
32
  } from '../../../../../../libs/ui/inputs/src'
33
+ import { PopoverComponent } from '../../../../../../libs/ui/widgets/src'
28
34
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
29
35
  import { NgIcon, provideIcons } from '@ng-icons/core'
30
36
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
@@ -35,6 +41,11 @@ import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/sr
35
41
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
36
42
  import { ContactPillComponent } from '../contact-pill/contact-pill.component'
37
43
 
44
+ marker('domain.record.keywordType.theme')
45
+ marker('domain.record.keywordType.place')
46
+ marker('domain.record.keywordType.temporal')
47
+ marker('domain.record.keywordType.other')
48
+
38
49
  @Component({
39
50
  selector: 'gn-ui-metadata-info',
40
51
  templateUrl: './metadata-info.component.html',
@@ -56,6 +67,7 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
56
67
  GnUiHumanizeDateDirective,
57
68
  SpatialExtentComponent,
58
69
  ContactPillComponent,
70
+ PopoverComponent,
59
71
  ],
60
72
  viewProviders: [
61
73
  provideIcons({
@@ -66,6 +78,7 @@ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
66
78
  })
67
79
  export class MetadataInfoComponent {
68
80
  private dateService = inject(DateService)
81
+ private translateService = inject(TranslateService)
69
82
 
70
83
  @Input() metadata: Partial<CatalogRecord>
71
84
  @Input() incomplete: boolean
@@ -167,4 +180,19 @@ export class MetadataInfoComponent {
167
180
  onKeywordClick(keyword: Keyword) {
168
181
  this.keyword.emit(keyword)
169
182
  }
183
+
184
+ keywordTooltipSegments(keyword: Keyword): string[] {
185
+ if (keyword.hierarchyPath?.length) {
186
+ return keyword.hierarchyPath
187
+ }
188
+ if (keyword.thesaurus?.name) {
189
+ return [keyword.thesaurus.name, keyword.label]
190
+ }
191
+ return [
192
+ this.translateService.instant(
193
+ `domain.record.keywordType.${keyword.type}`
194
+ ),
195
+ keyword.label,
196
+ ]
197
+ }
170
198
  }
@@ -25,6 +25,7 @@ export class ButtonComponent {
25
25
  | 'outline'
26
26
  | 'light'
27
27
  | 'gray'
28
+ | 'gray-light'
28
29
  | 'black'
29
30
  | 'primary-light'
30
31
  ) {
@@ -45,6 +46,9 @@ export class ButtonComponent {
45
46
  case 'gray':
46
47
  this.btnClass = 'gn-ui-btn-gray'
47
48
  break
49
+ case 'gray-light':
50
+ this.btnClass = 'gn-ui-btn-gray-light'
51
+ break
48
52
  case 'black':
49
53
  this.btnClass = 'gn-ui-btn-black'
50
54
  break
@@ -2,9 +2,9 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  EventEmitter,
5
+ inject,
5
6
  Input,
6
7
  Output,
7
- inject,
8
8
  } from '@angular/core'
9
9
  import {
10
10
  DateAdapter,
@@ -52,6 +52,6 @@ export class DatePickerComponent {
52
52
  @Output() dateChange = new EventEmitter<Date>()
53
53
 
54
54
  constructor() {
55
- this.dateAdapter.setLocale(this.translate.currentLang)
55
+ this.dateAdapter.setLocale(this.translate.getCurrentLang())
56
56
  }
57
57
  }
@@ -6,7 +6,7 @@
6
6
  type="url"
7
7
  [value]="inputValue"
8
8
  (input)="handleInput($event)"
9
- (keydown.enter)="handleUpload(input)"
9
+ (keydown.enter)="handleUpload(input, $event)"
10
10
  [placeholder]="placeholder"
11
11
  [attr.aria-label]="placeholder"
12
12
  [disabled]="disabled"
@@ -25,7 +25,7 @@
25
25
  extraClass="absolute inset-y-[var(--side-padding)] right-[var(--side-padding)]"
26
26
  type="primary"
27
27
  [disabled]="disabled || input.value === '' || !isValidUrl(input.value)"
28
- (buttonClick)="handleUpload(input)"
28
+ (buttonClick)="handleUpload(input, $event)"
29
29
  >
30
30
  <ng-content>
31
31
  <ng-icon name="iconoirArrowUp"></ng-icon>
@@ -81,7 +81,8 @@ export class UrlInputComponent implements OnChanges {
81
81
  this.valueChange.next(value)
82
82
  }
83
83
 
84
- handleUpload(element: HTMLInputElement) {
84
+ handleUpload(element: HTMLInputElement, event: Event) {
85
+ event.stopPropagation()
85
86
  const value = element.value
86
87
  if (!value || !this.isValidUrl(value)) return
87
88
  this.uploadClick.next(value)
@@ -1,18 +1,11 @@
1
1
  import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'
2
2
  import { CommonModule } from '@angular/common'
3
- import { Geometry } from 'geojson'
4
- import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON.js'
5
- import GeoJSON from 'ol/format/GeoJSON.js'
6
- import { Polygon } from 'ol/geom.js'
7
- import {
8
- createViewFromLayer,
9
- MapContext,
10
- MapContextLayer,
11
- } from '@geospatial-sdk/core'
3
+ import { createViewFromLayer, MapContext } from '@geospatial-sdk/core'
12
4
  import { MapContainerComponent } from '../map-container/map-container.component'
13
5
  import { BehaviorSubject, from, Observable, of } from 'rxjs'
14
6
  import { map, switchMap, tap } from 'rxjs/operators'
15
7
  import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/src/lib/model/record'
8
+ import { createSpatialExtentLayer } from '../../map-utils'
16
9
 
17
10
  @Component({
18
11
  selector: 'gn-ui-spatial-extent',
@@ -30,39 +23,10 @@ export class SpatialExtentComponent {
30
23
  spatialExtents$ = new BehaviorSubject<DatasetSpatialExtent[]>([])
31
24
  mapContext$: Observable<MapContext> = this.spatialExtents$.pipe(
32
25
  switchMap((extents) => {
33
- if (extents.length === 0) {
26
+ const layer = createSpatialExtentLayer(extents)
27
+ if (!layer) {
34
28
  return of(null)
35
29
  }
36
- const featureCollection: GeoJSONFeatureCollection = {
37
- type: 'FeatureCollection',
38
- features: [],
39
- }
40
- extents.forEach((extent) => {
41
- if (extent.geometry) {
42
- featureCollection.features.push({
43
- type: 'Feature',
44
- properties: {},
45
- geometry: extent.geometry,
46
- })
47
- } else if (extent.bbox?.length >= 0) {
48
- featureCollection.features.push({
49
- type: 'Feature',
50
- properties: {},
51
- geometry: this.bboxCoordsToGeometry(extent.bbox),
52
- })
53
- }
54
- })
55
-
56
- const layer: MapContextLayer = {
57
- type: 'geojson',
58
- data: featureCollection,
59
- label: 'Spatial extents',
60
- style: {
61
- 'stroke-color': 'black',
62
- 'stroke-width': 2,
63
- 'fill-color': 'rgba(153, 153, 153, 0.3)',
64
- },
65
- }
66
30
  return from(createViewFromLayer(layer)).pipe(
67
31
  map((view) => ({ view, layers: [layer] }) as MapContext),
68
32
  tap(() => this._cdr.markForCheck())
@@ -71,18 +35,4 @@ export class SpatialExtentComponent {
71
35
  )
72
36
 
73
37
  error = ''
74
-
75
- bboxCoordsToGeometry(bbox: [number, number, number, number]): Geometry {
76
- const geometry = new Polygon([
77
- [
78
- [bbox[0], bbox[1]],
79
- [bbox[0], bbox[3]],
80
- [bbox[2], bbox[3]],
81
- [bbox[2], bbox[1]],
82
- [bbox[0], bbox[1]],
83
- ],
84
- ])
85
-
86
- return new GeoJSON().writeGeometryObject(geometry)
87
- }
88
38
  }
@@ -12,6 +12,54 @@ import {
12
12
  MouseWheelZoom,
13
13
  } from 'ol/interaction.js'
14
14
  import MapBrowserEvent from 'ol/MapBrowserEvent.js'
15
+ import type { DatasetSpatialExtent } from '../../../../../libs/common/domain/src/lib/model/record'
16
+ import type {
17
+ MapContextLayer,
18
+ MapContextLayerGeojson,
19
+ } from '@geospatial-sdk/core'
20
+ import { spatialExtentsToFeatureCollection } from '../../../../../libs/util/shared/src'
21
+
22
+ export type SpatialExtentLayerStyle = NonNullable<
23
+ MapContextLayerGeojson['style']
24
+ >
25
+
26
+ /**
27
+ * Default style for a spatial-extent layer: a solid black outline over a
28
+ * translucent grey fill.
29
+ */
30
+ export const DEFAULT_SPATIAL_EXTENT_STYLE: SpatialExtentLayerStyle = {
31
+ 'stroke-color': 'black',
32
+ 'stroke-width': 2,
33
+ 'fill-color': 'rgba(153, 153, 153, 0.3)',
34
+ }
35
+
36
+ /**
37
+ * Builds a GeoJSON map layer drawing the given spatial extents: each extent is
38
+ * rendered from its own geometry or, when only a bounding box is available,
39
+ * from a polygon derived from that box.
40
+ *
41
+ * @returns the layer, or `null` when none of the extents can be represented.
42
+ */
43
+ export function createSpatialExtentLayer(
44
+ extents: DatasetSpatialExtent[],
45
+ overrides?: {
46
+ label?: string
47
+ clickable?: boolean
48
+ style?: SpatialExtentLayerStyle
49
+ }
50
+ ): MapContextLayer | null {
51
+ const data = spatialExtentsToFeatureCollection(extents)
52
+ if (data.features.length === 0) {
53
+ return null
54
+ }
55
+ return {
56
+ type: 'geojson',
57
+ data,
58
+ label: 'Spatial extents',
59
+ style: DEFAULT_SPATIAL_EXTENT_STYLE,
60
+ ...overrides,
61
+ }
62
+ }
15
63
 
16
64
  export function prioritizePageScroll(interactions: Collection<Interaction>) {
17
65
  interactions.clear()
@@ -52,7 +52,7 @@ export class RecordPreviewFeedComponent extends RecordPreviewComponent {
52
52
  protected elementRef: ElementRef
53
53
  private translate = inject(TranslateService)
54
54
 
55
- timeFormat = new Duration(this.translate.currentLang, {})
55
+ timeFormat = new Duration(this.translate.getCurrentLang(), {})
56
56
 
57
57
  constructor() {
58
58
  const elementRef = inject(ElementRef)
@@ -28,7 +28,13 @@ export class PopoverComponent implements AfterViewInit, OnChanges, OnDestroy {
28
28
 
29
29
  @ViewChild('popoverContent', { static: false }) popoverContent: ElementRef
30
30
  @Input() content: string | TemplateRef<any>
31
- @Input() theme: 'light' | 'light-border' | 'translucent' | 'material' | ''
31
+ @Input() theme:
32
+ | 'light'
33
+ | 'light-border'
34
+ | 'translucent'
35
+ | 'material'
36
+ | 'dark'
37
+ | ''
32
38
 
33
39
  private tippyInstance: Instance
34
40
  private view: EmbeddedViewRef<any>
@@ -1,6 +1,8 @@
1
1
  import * as TOML from '@ltd/j-toml'
2
2
  import {
3
3
  checkMetadataLanguage,
4
+ checkNewRecordDefaultLanguage,
5
+ checkNewRecordStandard,
4
6
  parseConfigSection,
5
7
  parseMultiConfigSection,
6
8
  parseTranslationsConfigSection,
@@ -8,6 +10,7 @@ import {
8
10
  import {
9
11
  CustomTranslations,
10
12
  CustomTranslationsAllLanguages,
13
+ EditorConfig,
11
14
  GlobalConfig,
12
15
  LayerConfig,
13
16
  MapConfig,
@@ -15,10 +18,15 @@ import {
15
18
  SearchConfig,
16
19
  ThemeConfig,
17
20
  } from './model'
18
- import { TranslateCompiler, TranslateLoader } from '@ngx-translate/core'
21
+ import {
22
+ TranslateCompiler,
23
+ TranslateLoader,
24
+ TranslateModuleConfig,
25
+ } from '@ngx-translate/core'
19
26
  import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'
20
- import { HttpClient } from '@angular/common/http'
21
27
  import { FileWithOverridesTranslateLoader } from './i18n/file-with-overrides.translate.loader'
28
+ import { TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader'
29
+ import { HttpClient } from '@angular/common/http'
22
30
 
23
31
  const MISSING_CONFIG_ERROR = `Application configuration was not initialized correctly.
24
32
  This error might show up in case of an invalid/malformed configuration file.
@@ -51,6 +59,12 @@ export function getOptionalSearchConfig(): SearchConfig | null {
51
59
  return searchConfig
52
60
  }
53
61
 
62
+ let editorConfig: EditorConfig = null
63
+
64
+ export function getOptionalEditorConfig(): EditorConfig | null {
65
+ return editorConfig
66
+ }
67
+
54
68
  let metadataQualityConfig: MetadataQualityConfig = null
55
69
  export function getMetadataQualityConfig(): MetadataQualityConfig {
56
70
  return (
@@ -285,6 +299,45 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
285
299
  SORTABLE: parsedMetadataQualitySection.sortable,
286
300
  } as MetadataQualityConfig)
287
301
 
302
+ let parsedEditingSection = parseConfigSection(
303
+ parsed,
304
+ 'editing',
305
+ [],
306
+ ['new_record_default_language', 'new_record_standard'],
307
+ warnings,
308
+ errors
309
+ )
310
+ if (
311
+ parsedEditingSection !== null &&
312
+ parsedEditingSection.new_record_default_language !== undefined
313
+ ) {
314
+ parsedEditingSection = checkNewRecordDefaultLanguage(
315
+ parsedEditingSection,
316
+ warnings
317
+ )
318
+ }
319
+ if (
320
+ parsedEditingSection !== null &&
321
+ parsedEditingSection.new_record_standard !== undefined
322
+ ) {
323
+ parsedEditingSection = checkNewRecordStandard(
324
+ parsedEditingSection,
325
+ warnings
326
+ )
327
+ }
328
+ editorConfig =
329
+ parsedEditingSection === null
330
+ ? null
331
+ : ({
332
+ NEW_RECORD_DEFAULT_LANGUAGE:
333
+ parsedEditingSection.new_record_default_language as
334
+ | string
335
+ | undefined,
336
+ NEW_RECORD_STANDARD: parsedEditingSection.new_record_standard as
337
+ | EditorConfig['NEW_RECORD_STANDARD']
338
+ | undefined,
339
+ } as EditorConfig)
340
+
288
341
  customTranslations = parseTranslationsConfigSection(
289
342
  parsed,
290
343
  'translations'
@@ -309,19 +362,27 @@ export function isConfigLoaded() {
309
362
  export function _reset() {
310
363
  globalConfig = null
311
364
  themeConfig = null
365
+ editorConfig = null
312
366
  customTranslations = null
313
367
  }
314
368
 
315
- export const TRANSLATE_WITH_OVERRIDES_CONFIG = {
369
+ export const TRANSLATE_WITH_OVERRIDES_CONFIG: TranslateModuleConfig = {
316
370
  compiler: {
317
371
  provide: TranslateCompiler,
318
372
  useClass: TranslateMessageFormatCompiler,
319
373
  },
320
- loader: {
321
- provide: TranslateLoader,
322
- useFactory: function HttpLoaderFactory(http: HttpClient) {
323
- return new FileWithOverridesTranslateLoader(http, './assets/i18n/')
374
+ loader: [
375
+ {
376
+ provide: TRANSLATE_HTTP_LOADER_CONFIG,
377
+ useValue: {
378
+ prefix: './assets/i18n/',
379
+ suffix: '.json',
380
+ },
324
381
  },
325
- deps: [HttpClient],
326
- },
382
+ {
383
+ provide: TranslateLoader,
384
+ useClass: FileWithOverridesTranslateLoader,
385
+ deps: [HttpClient, TRANSLATE_HTTP_LOADER_CONFIG],
386
+ },
387
+ ],
327
388
  }
@@ -69,6 +69,13 @@ export interface MetadataQualityConfig {
69
69
  ENABLED: boolean
70
70
  }
71
71
 
72
+ export type NewRecordStandard = 'iso19139' | 'iso19115-3'
73
+
74
+ export interface EditorConfig {
75
+ NEW_RECORD_DEFAULT_LANGUAGE?: string
76
+ NEW_RECORD_STANDARD?: NewRecordStandard
77
+ }
78
+
72
79
  export type CustomTranslations = { [translationKey: string]: string }
73
80
  export type CustomTranslationsAllLanguages = {
74
81
  [lang: string]: CustomTranslations