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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/fesm2022/geonetwork-ui.mjs +913 -5829
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +218 -90
  4. package/index.d.ts.map +1 -1
  5. package/material-styles.css +483 -0
  6. package/package.json +25 -35
  7. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +9 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +7 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  20. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +8 -0
  21. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +1 -0
  23. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +5 -4
  24. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  25. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +7 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +8 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +8 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +11 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +33 -0
  31. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +33 -0
  32. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  33. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  34. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +68 -34
  35. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  36. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  37. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  38. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +13 -5
  40. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  41. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  42. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  43. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  44. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +5 -0
  45. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  46. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  47. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  48. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  49. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  50. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  51. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +67 -0
  52. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +39 -0
  53. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  54. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  55. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  56. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +32 -34
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +8 -7
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +6 -6
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.css +0 -0
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.html +5 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-single-link-resource/form-field-online-single-link-resource.component.ts +89 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +11 -9
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +12 -0
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  73. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -1
  75. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +77 -5
  76. package/src/libs/feature/editor/src/lib/fields.config.ts +60 -47
  77. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  78. package/src/libs/feature/editor/src/lib/services/editor.service.ts +4 -9
  79. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  80. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  81. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  82. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  83. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  84. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  85. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  86. package/src/libs/ui/dataviz/src/index.ts +0 -1
  87. package/src/libs/ui/elements/src/index.ts +2 -0
  88. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  89. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  90. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  91. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  92. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  93. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  94. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  95. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  96. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  97. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  98. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  99. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  100. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +39 -68
  101. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +59 -11
  102. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  103. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  104. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  105. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  106. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  107. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  108. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  109. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  110. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  111. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  112. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  113. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  114. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  115. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  116. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  117. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  118. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  119. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  120. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  121. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  122. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  123. package/src/libs/util/i18n/src/index.ts +0 -1
  124. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  125. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  126. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  127. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  128. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  129. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  130. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  131. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  132. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  133. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  134. package/style.css +0 -1
  135. package/tailwind.base.css +19 -2
  136. package/translations/de.json +33 -20
  137. package/translations/en.json +33 -20
  138. package/translations/es.json +33 -20
  139. package/translations/fr.json +33 -20
  140. package/translations/it.json +33 -20
  141. package/translations/nl.json +33 -20
  142. package/translations/pt.json +33 -20
  143. package/translations/sk.json +33 -20
@@ -9,11 +9,11 @@
9
9
  "
10
10
  (buttonClick)="togglePreview()"
11
11
  >
12
- <span
13
- class="material-symbols-outlined gn-ui-icon-small"
12
+ <ng-icon
13
+ [name]="preview ? 'matVisibilityOffOutline' : 'matVisibilityOutline'"
14
+ size="16"
14
15
  [ngClass]="{ 'text-primary': !preview }"
15
- >{{ preview ? 'visibility_off' : 'visibility' }}</span
16
- >
16
+ ></ng-icon>
17
17
  &nbsp;
18
18
  {{ preview ? 'Edit' : 'Preview' }}
19
19
  </gn-ui-button>
@@ -10,6 +10,11 @@ import { ReactiveFormsModule } from '@angular/forms'
10
10
  import { MarkdownEditorComponent } from '../../../../../../../../../libs/ui/elements/src'
11
11
  import { ButtonComponent } from '../../../../../../../../../libs/ui/inputs/src'
12
12
  import { FormFieldWrapperComponent } from '../../../../../../../../../libs/ui/layout/src'
13
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
14
+ import {
15
+ matVisibilityOffOutline,
16
+ matVisibilityOutline,
17
+ } from '@ng-icons/material-icons/outline'
13
18
 
14
19
  @Component({
15
20
  selector: 'gn-ui-form-field-rich',
@@ -23,7 +28,9 @@ import { FormFieldWrapperComponent } from '../../../../../../../../../libs/ui/la
23
28
  MarkdownEditorComponent,
24
29
  FormFieldWrapperComponent,
25
30
  ButtonComponent,
31
+ NgIconComponent,
26
32
  ],
33
+ providers: [provideIcons({ matVisibilityOutline, matVisibilityOffOutline })],
27
34
  })
28
35
  export class FormFieldRichComponent {
29
36
  @Input() label: string
@@ -5,7 +5,7 @@ import {
5
5
  DropdownMultiselectComponent,
6
6
  } from '../../../../../../../../../libs/ui/inputs/src'
7
7
  import { TranslatePipe, TranslateService } from '@ngx-translate/core'
8
- import { INSPIRE_TOPICS } from '../../../../fields.config'
8
+ import { ISO_TOPICS } from '../../../../fields.config'
9
9
 
10
10
  @Component({
11
11
  selector: 'gn-ui-form-field-topics',
@@ -22,7 +22,7 @@ export class FormFieldTopicsComponent {
22
22
  this.topics = topics
23
23
  }
24
24
  @Output() valueChange: EventEmitter<string[]> = new EventEmitter()
25
- availableTopics = INSPIRE_TOPICS.map((topic) => {
25
+ availableTopics = ISO_TOPICS.map((topic) => {
26
26
  return {
27
27
  label: this.translateService.instant(topic.label),
28
28
  value: topic.value,
@@ -38,19 +38,20 @@
38
38
  "
39
39
  ></textarea>
40
40
  <div class="flex flex-row justify-between self-start mt-0.5">
41
- <span
42
- class="material-symbols-outlined gn-ui-icon-small m-2 cursor-pointer"
41
+ <ng-icon
42
+ name="matEditOutline"
43
+ size="16"
44
+ class="m-2 cursor-pointer"
43
45
  (click)="focusTitleInput()"
44
- >edit</span
45
- >
46
- <span
46
+ ></ng-icon>
47
+ <ng-icon
47
48
  *ngIf="config.hintKey"
48
- class="material-symbols-outlined gn-ui-icon-small m-2"
49
+ name="matHelpOutline"
50
+ size="16"
51
+ class="m-2"
49
52
  [matTooltip]="config.hintKey! | translate"
50
53
  matTooltipPosition="above"
51
- >
52
- help
53
- </span>
54
+ ></ng-icon>
54
55
  </div>
55
56
  </div>
56
57
  </ng-container>
@@ -150,6 +151,7 @@
150
151
  <ng-container *ngSwitchCase="'contacts'">
151
152
  <gn-ui-form-field-contacts
152
153
  [value]="valueAsIndividuals"
154
+ [modelSpecifier]="modelSpecifier"
153
155
  (valueChange)="valueChange.emit($event)"
154
156
  ></gn-ui-form-field-contacts>
155
157
  </ng-container>
@@ -4,6 +4,7 @@ import {
4
4
  Component,
5
5
  ElementRef,
6
6
  EventEmitter,
7
+ inject,
7
8
  Input,
8
9
  Output,
9
10
  ViewChild,
@@ -20,8 +21,14 @@ import {
20
21
  UpdateFrequency,
21
22
  } from '../../../../../../../../libs/common/domain/src/lib/model/record'
22
23
  import { FormFieldWrapperComponent } from '../../../../../../../../libs/ui/layout/src'
24
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
25
+ import {
26
+ matEditOutline,
27
+ matHelpOutline,
28
+ } from '@ng-icons/material-icons/outline'
23
29
  import { TranslatePipe } from '@ngx-translate/core'
24
30
  import {
31
+ FieldFocusDirective,
25
32
  FormFieldDateComponent,
26
33
  FormFieldLicenseComponent,
27
34
  FormFieldTemporalExtentsComponent,
@@ -76,7 +83,10 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
76
83
  FormFieldSpatialToggleComponent,
77
84
  FormFieldTopicsComponent,
78
85
  TextFieldModule,
86
+ NgIconComponent,
79
87
  ],
88
+ providers: [provideIcons({ matEditOutline, matHelpOutline })],
89
+ hostDirectives: [FieldFocusDirective],
80
90
  })
81
91
  export class FormFieldComponent {
82
92
  @Input() uniqueIdentifier: string
@@ -92,6 +102,8 @@ export class FormFieldComponent {
92
102
  @ViewChild('titleInput') titleInput: ElementRef
93
103
  isOpenData = false
94
104
 
105
+ fieldFocus = inject(FieldFocusDirective)
106
+
95
107
  toggleIsOpenData(event: boolean) {
96
108
  this.isOpenData = event
97
109
  }
@@ -1,3 +1,4 @@
1
+ export * from './field-focus.directive'
1
2
  export * from './form-field-keywords/form-field-keywords.component'
2
3
  export * from './form-field-license/form-field-license.component'
3
4
  export * from './form-field-date/form-field-date.component'
@@ -0,0 +1,35 @@
1
+ /* Glow applied by FieldFocusDirective (gnUiFieldFocus) to a focused field or,
2
+ when the field is hidden, to its whole section. */
3
+ .gn-ui-field-focus-glow {
4
+ position: relative;
5
+ }
6
+
7
+ .gn-ui-field-focus-glow::after {
8
+ content: '';
9
+ position: absolute;
10
+ inset: -8px;
11
+ border-radius: 8px;
12
+ background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
13
+ pointer-events: none;
14
+ animation: gn-ui-field-focus-glow 3s ease-out forwards;
15
+ }
16
+
17
+ .gn-ui-field-focus-glow::before {
18
+ content: '';
19
+ position: absolute;
20
+ inset: -16px;
21
+ border: 2px dashed var(--color-primary);
22
+ border-radius: 10px;
23
+ pointer-events: none;
24
+ animation: gn-ui-field-focus-glow 3s ease-out forwards;
25
+ }
26
+
27
+ @keyframes gn-ui-field-focus-glow {
28
+ 0%,
29
+ 55% {
30
+ opacity: 1;
31
+ }
32
+ 100% {
33
+ opacity: 0;
34
+ }
35
+ }
@@ -4,7 +4,11 @@
4
4
  track sectionTracker($index, section)
5
5
  ) {
6
6
  @if (!section.hidden) {
7
- <div class="flex flex-col gap-6 border p-8 rounded-[8px] shadow">
7
+ <div
8
+ class="flex flex-col gap-6 border p-8 rounded-[8px] shadow scroll-mt-[90px]"
9
+ gnUiFieldFocus
10
+ #sectionFocus="fieldFocus"
11
+ >
8
12
  <div class="flex flex-col gap-2">
9
13
  @if (section.labelKey) {
10
14
  <div class="text-2xl font-title text-black" translate>
@@ -1,15 +1,23 @@
1
1
  import { CommonModule } from '@angular/common'
2
- import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ inject,
6
+ OnDestroy,
7
+ OnInit,
8
+ viewChildren,
9
+ } from '@angular/core'
3
10
  import { EditorFacade } from '../../+state/editor.facade'
4
11
  import { EditorFieldValue } from '../../models'
5
- import { FormFieldComponent } from './form-field'
12
+ import { FieldFocusDirective, FormFieldComponent } from './form-field'
6
13
  import { TranslateDirective } from '@ngx-translate/core'
7
14
  import {
8
15
  EditorFieldWithValue,
9
16
  EditorSectionWithValues,
10
17
  } from '../../+state/editor.models'
11
- import { map } from 'rxjs'
18
+ import { firstValueFrom, map, Subscription, withLatestFrom } from 'rxjs'
12
19
  import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
20
+ import { switchMap } from 'rxjs/operators'
13
21
 
14
22
  @Component({
15
23
  selector: 'gn-ui-record-form',
@@ -17,15 +25,63 @@ import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/l
17
25
  styleUrls: ['./record-form.component.css'],
18
26
  changeDetection: ChangeDetectionStrategy.OnPush,
19
27
  standalone: true,
20
- imports: [CommonModule, FormFieldComponent, TranslateDirective],
28
+ imports: [
29
+ CommonModule,
30
+ FormFieldComponent,
31
+ FieldFocusDirective,
32
+ TranslateDirective,
33
+ ],
21
34
  })
22
- export class RecordFormComponent {
35
+ export class RecordFormComponent implements OnInit, OnDestroy {
23
36
  facade = inject(EditorFacade)
37
+ subscription = new Subscription()
24
38
 
25
39
  recordUniqueIdentifier$ = this.facade.record$.pipe(
26
40
  map((record) => record.uniqueIdentifier)
27
41
  )
28
42
 
43
+ focusFieldWithPage$ = this.facade.focusedField$.pipe(
44
+ switchMap(async (field) => {
45
+ const location = await this.getFieldLocation(field)
46
+ return [field, location?.page ?? null, location?.section ?? -1] as const
47
+ })
48
+ )
49
+
50
+ formFields = viewChildren(FormFieldComponent)
51
+ sectionFocusDirectives = viewChildren<FieldFocusDirective>('sectionFocus')
52
+
53
+ focusField(model: CatalogRecordKeys, sectionIndex: number) {
54
+ const field = this.formFields().find((f) => f.model === model)
55
+ if (field) {
56
+ field.fieldFocus.focusField()
57
+ return
58
+ }
59
+ this.sectionFocusDirectives()[sectionIndex]?.focusField(false)
60
+ }
61
+
62
+ ngOnInit() {
63
+ this.subscription.add(
64
+ this.focusFieldWithPage$
65
+ .pipe(
66
+ withLatestFrom(
67
+ this.facade.currentPage$,
68
+ ([field, fieldPage, fieldSection], currentPage) =>
69
+ [field, fieldPage, fieldSection, currentPage] as const
70
+ )
71
+ )
72
+ .subscribe(([field, fieldPage, fieldSection, currentPage]) => {
73
+ if (fieldPage !== null && fieldPage !== currentPage) {
74
+ this.facade.setCurrentPage(fieldPage)
75
+ }
76
+ setTimeout(() => this.focusField(field, fieldSection))
77
+ })
78
+ )
79
+ }
80
+
81
+ ngOnDestroy() {
82
+ this.subscription.unsubscribe()
83
+ }
84
+
29
85
  handleFieldValueChange(model: CatalogRecordKeys, newValue: EditorFieldValue) {
30
86
  if (!model) {
31
87
  return
@@ -40,4 +96,20 @@ export class RecordFormComponent {
40
96
  sectionTracker(index: number, section: EditorSectionWithValues) {
41
97
  return section.labelKey
42
98
  }
99
+
100
+ async getFieldLocation(
101
+ model: CatalogRecordKeys
102
+ ): Promise<{ page: number; section: number } | null> {
103
+ const config = await firstValueFrom(this.facade.editorConfig$)
104
+ const page = config.pages.findIndex((p) =>
105
+ p.sections.some((s) => s.fields.some((f) => f.model === model))
106
+ )
107
+ if (page < 0) {
108
+ return null
109
+ }
110
+ const section = config.pages[page].sections
111
+ .filter((s) => !s.hidden)
112
+ .findIndex((s) => s.fields.some((f) => f.model === model))
113
+ return { page, section }
114
+ }
43
115
  }
@@ -4,10 +4,12 @@ import {
4
4
  EditorField,
5
5
  EditorSection,
6
6
  } from './models/editor-config.model'
7
- import {
8
- INSPIRE_topic,
9
- Keyword,
10
- } from '../../../../../libs/common/domain/src/lib/model/record'
7
+ import { Keyword } from '../../../../../libs/common/domain/src/lib/model/record'
8
+
9
+ export interface ISOTopic {
10
+ value: string
11
+ label: string
12
+ }
11
13
 
12
14
  /**
13
15
  * This file contains the configuration of the fields that will be displayed in the editor.
@@ -359,75 +361,86 @@ export const SPATIAL_SCOPES: Keyword[] = [
359
361
  ]
360
362
 
361
363
  /************************************************************
362
- *************** INSPIRE TOPICS **************
364
+ *************** ISO TOPICS **************
363
365
  ************************************************************
364
366
  */
365
367
 
366
- export const INSPIRE_TOPICS: INSPIRE_topic[] = [
367
- { value: 'biota', label: 'editor.record.form.topics.inspire.biota' },
368
+ // label keys mirror the ISO MD_TopicCategoryCode value for consistency
369
+ // TODO: correctly handle code lists; for instance, this code list is specific to ISO 19139
370
+ export const ISO_TOPICS: ISOTopic[] = [
371
+ { value: 'biota', label: marker('editor.record.form.topics.iso.biota') },
368
372
  {
369
373
  value: 'boundaries',
370
- label: 'editor.record.form.topics.inspire.boundaries',
374
+ label: marker('editor.record.form.topics.iso.boundaries'),
371
375
  },
372
376
  {
373
377
  value: 'climatologyMeteorologyAtmosphere',
374
- label: 'editor.record.form.topics.inspire.climatology',
378
+ label: marker(
379
+ 'editor.record.form.topics.iso.climatologyMeteorologyAtmosphere'
380
+ ),
381
+ },
382
+ {
383
+ value: 'economy',
384
+ label: marker('editor.record.form.topics.iso.economy'),
385
+ },
386
+ {
387
+ value: 'elevation',
388
+ label: marker('editor.record.form.topics.iso.elevation'),
375
389
  },
376
- { value: 'economy', label: 'editor.record.form.topics.inspire.economy' },
377
- { value: 'elevation', label: 'editor.record.form.topics.inspire.elevation' },
378
390
  {
379
391
  value: 'environment',
380
- label: 'editor.record.form.topics.inspire.environnement',
392
+ label: marker('editor.record.form.topics.iso.environment'),
393
+ },
394
+ {
395
+ value: 'farming',
396
+ label: marker('editor.record.form.topics.iso.farming'),
397
+ },
398
+ {
399
+ value: 'geoscientificInformation',
400
+ label: marker('editor.record.form.topics.iso.geoscientificInformation'),
381
401
  },
382
- { value: 'farming', label: 'editor.record.form.topics.inspire.farming' },
383
402
  {
384
- value: 'geoscientific information',
385
- label: 'editor.record.form.topics.inspire.geoscientific',
403
+ value: 'health',
404
+ label: marker('editor.record.form.topics.iso.health'),
386
405
  },
387
- { value: 'health', label: 'editor.record.form.topics.inspire.health' },
388
406
  {
389
407
  value: 'imageryBaseMapsEarthCover',
390
- label: 'editor.record.form.topics.inspire.imagery',
408
+ label: marker('editor.record.form.topics.iso.imageryBaseMapsEarthCover'),
409
+ },
410
+ {
411
+ value: 'inlandWaters',
412
+ label: marker('editor.record.form.topics.iso.inlandWaters'),
391
413
  },
392
- { value: 'inlandWaters', label: 'editor.record.form.topics.inspire.waters' },
393
414
  {
394
415
  value: 'intelligenceMilitary',
395
- label: 'editor.record.form.topics.inspire.intelligence',
416
+ label: marker('editor.record.form.topics.iso.intelligenceMilitary'),
417
+ },
418
+ {
419
+ value: 'location',
420
+ label: marker('editor.record.form.topics.iso.location'),
421
+ },
422
+ {
423
+ value: 'oceans',
424
+ label: marker('editor.record.form.topics.iso.oceans'),
396
425
  },
397
- { value: 'Location', label: 'editor.record.form.topics.inspire.location' },
398
- { value: 'Oceans', label: 'editor.record.form.topics.inspire.oceans' },
399
426
  {
400
427
  value: 'planningCadastre',
401
- label: 'editor.record.form.topics.inspire.planning',
428
+ label: marker('editor.record.form.topics.iso.planningCadastre'),
402
429
  },
403
- { value: 'Society', label: 'editor.record.form.topics.inspire.society' },
404
- { value: 'Structure', label: 'editor.record.form.topics.inspire.structure' },
405
430
  {
406
- value: 'Transportation',
407
- label: 'editor.record.form.topics.inspire.transportation',
431
+ value: 'society',
432
+ label: marker('editor.record.form.topics.iso.society'),
433
+ },
434
+ {
435
+ value: 'structure',
436
+ label: marker('editor.record.form.topics.iso.structure'),
437
+ },
438
+ {
439
+ value: 'transportation',
440
+ label: marker('editor.record.form.topics.iso.transportation'),
408
441
  },
409
442
  {
410
443
  value: 'utilitiesCommunication',
411
- label: 'editor.record.form.topics.inspire.utilities',
444
+ label: marker('editor.record.form.topics.iso.utilitiesCommunication'),
412
445
  },
413
446
  ]
414
-
415
- marker('editor.record.form.topics.inspire.biota')
416
- marker('editor.record.form.topics.inspire.boundaries')
417
- marker('editor.record.form.topics.inspire.climatology')
418
- marker('editor.record.form.topics.inspire.economy')
419
- marker('editor.record.form.topics.inspire.elevation')
420
- marker('editor.record.form.topics.inspire.environnement')
421
- marker('editor.record.form.topics.inspire.farming')
422
- marker('editor.record.form.topics.inspire.geoscientific')
423
- marker('editor.record.form.topics.inspire.health')
424
- marker('editor.record.form.topics.inspire.imagery')
425
- marker('editor.record.form.topics.inspire.intelligence')
426
- marker('editor.record.form.topics.inspire.location')
427
- marker('editor.record.form.topics.inspire.oceans')
428
- marker('editor.record.form.topics.inspire.planning')
429
- marker('editor.record.form.topics.inspire.society')
430
- marker('editor.record.form.topics.inspire.structure')
431
- marker('editor.record.form.topics.inspire.transportation')
432
- marker('editor.record.form.topics.inspire.utilities')
433
- marker('editor.record.form.topics.inspire.waters')
@@ -19,9 +19,13 @@ export interface FormFieldConfig {
19
19
  // This is used for instance to target only certain online resources in a field
20
20
  type OnlineLinkResourceSpecifier = `onlineResourceType:link`
21
21
  type DatasetDistributionsSpecifier = `onlineResourceType:!link`
22
+ // When set on the `contacts` field, contacts are rendered as editable detail
23
+ // fields (ContactDetailsFormComponent) instead of cards (ContactCardComponent)
24
+ type EditableContactDetailsSpecifier = `contact:editableDetails`
22
25
  export type FieldModelSpecifier =
23
26
  | OnlineLinkResourceSpecifier
24
27
  | DatasetDistributionsSpecifier
28
+ | EditableContactDetailsSpecifier
25
29
 
26
30
  export type FormFieldComponentName =
27
31
  | 'form-field-constraints-shortcuts'
@@ -1,5 +1,5 @@
1
1
  import { Injectable, inject } from '@angular/core'
2
- import { forkJoin, Observable, of, switchMap } from 'rxjs'
2
+ import { Observable, switchMap } from 'rxjs'
3
3
  import { map, tap } from 'rxjs/operators'
4
4
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
5
5
  import { EditorConfig } from '../models/'
@@ -16,7 +16,8 @@ export class EditorService {
16
16
  saveRecord(
17
17
  record: CatalogRecord,
18
18
  recordSource: string,
19
- fieldsConfig: EditorConfig
19
+ fieldsConfig: EditorConfig,
20
+ publish: boolean
20
21
  ): Observable<[CatalogRecord, string]> {
21
22
  const savedRecord = { ...record }
22
23
 
@@ -34,15 +35,9 @@ export class EditorService {
34
35
  })
35
36
  }
36
37
  }
37
- let publishToAll = true
38
- // if the record is new, generate a new unique identifier and pass publishToAll as false
39
- if (!record.uniqueIdentifier) {
40
- savedRecord.uniqueIdentifier = null
41
- publishToAll = false
42
- }
43
38
 
44
39
  return this.recordsRepository
45
- .saveRecord(savedRecord, recordSource, publishToAll)
40
+ .saveRecord(savedRecord, recordSource, publish)
46
41
  .pipe(
47
42
  switchMap((uniqueIdentifier) =>
48
43
  this.recordsRepository.openRecordForEdition(uniqueIdentifier)
@@ -2,6 +2,23 @@ import { Injectable } from '@angular/core'
2
2
  import { extend } from 'ol/extent.js'
3
3
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
4
4
  import { BoundingBox, getGeometryBoundingBox } from '../../../../../../libs/util/shared/src'
5
+ import { MapContextLayer } from '@geospatial-sdk/core'
6
+ import {
7
+ createSpatialExtentLayer,
8
+ SpatialExtentLayerStyle,
9
+ } from '../../../../../../libs/ui/map/src'
10
+
11
+ /**
12
+ * Style of the extent overlay drawn on top of the previewed data: a dashed
13
+ * black outline over a very light fill, so the extent stays readable without
14
+ * hiding the data underneath.
15
+ */
16
+ const RECORD_EXTENT_OVERLAY_STYLE: SpatialExtentLayerStyle = {
17
+ 'stroke-color': 'rgba(0, 0, 0, 0.6)',
18
+ 'stroke-width': 2,
19
+ 'stroke-line-dash': [8, 6],
20
+ 'fill-color': 'rgba(0, 0, 0, 0.03)',
21
+ }
5
22
 
6
23
  @Injectable({
7
24
  providedIn: 'root',
@@ -26,4 +43,20 @@ export class MapUtilsService {
26
43
  [Infinity, Infinity, -Infinity, -Infinity]
27
44
  )
28
45
  }
46
+
47
+ /**
48
+ * Builds a non-interactive overlay layer drawing the spatial extent(s)
49
+ * declared in the record's metadata (bounding boxes and/or geometries).
50
+ * Returns null when the record has no usable spatial extent.
51
+ *
52
+ * This is purely for display and is independent from the map's initial view,
53
+ * which is derived separately (see {@link getRecordExtent}).
54
+ */
55
+ getRecordExtentLayer(record: Partial<CatalogRecord>): MapContextLayer | null {
56
+ return createSpatialExtentLayer(record.spatialExtents ?? [], {
57
+ label: 'Spatial extent',
58
+ clickable: false,
59
+ style: RECORD_EXTENT_OVERLAY_STYLE,
60
+ })
61
+ }
29
62
  }
@@ -0,0 +1 @@
1
+ export * from './lib/notify-reuse-form/notify-reuse-form.component'
@@ -0,0 +1,21 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ } from '@angular/core'
8
+ import { ReuseRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
9
+
10
+ @Component({
11
+ selector: 'gn-ui-notify-reuse-form',
12
+ standalone: true,
13
+ imports: [],
14
+ templateUrl: './notify-reuse-form.component.html',
15
+ styleUrl: './notify-reuse-form.component.css',
16
+ changeDetection: ChangeDetectionStrategy.OnPush,
17
+ })
18
+ export class NotifyReuseFormComponent {
19
+ @Input() record: ReuseRecord | null = null
20
+ @Output() recordChange = new EventEmitter<ReuseRecord>()
21
+ }
@@ -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,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'
@@ -18,6 +18,8 @@ export * from './lib/metadata-doi/metadata-doi.component'
18
18
  export * from './lib/metadata-info/metadata-info.component'
19
19
  export * from './lib/metadata-quality-item/metadata-quality-item.component'
20
20
  export * from './lib/metadata-quality/metadata-quality.component'
21
+ export * from './lib/contact-pill/contact-pill.component'
22
+ export * from './lib/contact-details/contact-details.component'
21
23
  export * from './lib/notification/notification.component'
22
24
  export * from './lib/record-api-form/record-api-form.component'
23
25
  export * from './lib/thumbnail/thumbnail.component'