geonetwork-ui 2.9.0 → 2.10.0-dev.08a12212e

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 (175) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2574 -6987
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +368 -189
  4. package/index.d.ts.map +1 -1
  5. package/package.json +25 -35
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +20 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +4 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +23 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +8 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +2 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +2 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +2 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +2 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +4 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +23 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +2 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +4 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +2 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +9 -0
  20. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.converter.ts +2 -0
  22. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +6 -5
  23. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  24. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +11 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +21 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +52 -0
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +24 -1
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +73 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +79 -0
  31. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +8 -4
  32. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  33. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +79 -41
  34. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  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 +37 -2
  38. package/src/libs/common/domain/src/index.ts +1 -0
  39. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +23 -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/domain/src/lib/repository/records-repository.interface.ts +2 -1
  45. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +62 -0
  46. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  47. package/src/libs/feature/editor/src/index.ts +3 -0
  48. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +10 -1
  49. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -20
  50. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +15 -2
  51. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  52. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.css +0 -0
  53. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +68 -0
  54. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +41 -0
  55. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  56. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  57. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  58. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  59. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +11 -6
  60. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +18 -2
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +41 -0
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +33 -34
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +13 -13
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +18 -4
  65. 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
  66. 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
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  68. 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
  69. 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
  70. 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
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +28 -16
  79. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +16 -0
  80. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  81. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  82. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  83. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  84. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  85. package/src/libs/feature/editor/src/lib/fields.config.ts +354 -337
  86. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -3
  87. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  88. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  89. package/src/libs/feature/notify-reuse/src/index.ts +2 -0
  90. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  91. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  92. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  93. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  94. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -0
  95. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +234 -0
  96. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  97. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  98. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  99. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  100. package/src/libs/ui/dataviz/src/index.ts +0 -1
  101. package/src/libs/ui/elements/src/index.ts +3 -0
  102. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  103. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  104. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  105. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  106. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  107. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  108. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  109. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  110. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  111. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  112. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  113. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  114. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  115. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  116. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  117. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +3 -2
  118. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +26 -68
  119. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  120. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  121. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  122. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  123. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  124. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  125. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  126. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  127. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  128. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  129. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  130. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  131. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  132. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +6 -2
  133. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  134. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  135. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  136. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  137. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  138. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  139. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  140. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  141. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  142. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  143. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  144. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  145. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  146. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  147. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  148. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  149. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  150. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  151. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  152. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  153. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  154. package/src/libs/util/i18n/src/index.ts +0 -1
  155. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  156. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  157. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  158. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  159. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  160. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  161. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  162. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  163. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  164. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  165. package/style.css +0 -1
  166. package/tailwind.base.css +26 -2
  167. package/translations/de.json +112 -50
  168. package/translations/en.json +117 -55
  169. package/translations/es.json +86 -24
  170. package/translations/fr.json +114 -52
  171. package/translations/it.json +113 -51
  172. package/translations/nl.json +85 -23
  173. package/translations/pt.json +86 -24
  174. package/translations/sk.json +86 -24
  175. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -3,13 +3,13 @@
3
3
  editor.record.form.field.onlineResource.edit.protocol
4
4
  </h3>
5
5
  @if (protocolHint) {
6
- <span
7
- class="material-symbols-outlined m-2 gn-ui-icon-small"
6
+ <ng-icon
7
+ name="matHelpOutline"
8
+ size="16"
9
+ class="m-2"
8
10
  [matTooltip]="protocolHint"
9
11
  matTooltipPosition="above"
10
- >
11
- help
12
- </span>
12
+ ></ng-icon>
13
13
  }
14
14
  </div>
15
15
  <div class="flex flex-row items-center gap-4 h-[48px]">
@@ -20,7 +20,7 @@
20
20
  [disabled]="disabled"
21
21
  (change)="resetAllFormFields()"
22
22
  >
23
- @for (protocolOption of protocolOptions; track protocolOption) {
23
+ @for (protocolOption of availableProtocolOptions; track protocolOption) {
24
24
  <mat-radio-button [value]="protocolOption.value">
25
25
  {{ protocolOption.label | translate }}
26
26
  </mat-radio-button>
@@ -45,6 +45,11 @@
45
45
  }
46
46
  </gn-ui-url-input>
47
47
 
48
+ @if (loading) {
49
+ <div class="flex justify-center w-full py-4">
50
+ <gn-ui-spinning-loader></gn-ui-spinning-loader>
51
+ </div>
52
+ }
48
53
  @if (errorMessage) {
49
54
  <p class="text-sm text-red-500 pl-4" translate>
50
55
  editor.record.form.field.onlineResource.edit.identifier.error
@@ -22,6 +22,7 @@ import {
22
22
  TextInputComponent,
23
23
  UrlInputComponent,
24
24
  } from '../../../../../../../libs/ui/inputs/src'
25
+ import { SpinningLoaderComponent } from '../../../../../../../libs/ui/widgets/src'
25
26
  import { createFuzzyFilter, getLayers } from '../../../../../../../libs/util/shared/src'
26
27
  import {
27
28
  NgIconComponent,
@@ -29,6 +30,7 @@ import {
29
30
  provideNgIconsConfig,
30
31
  } from '@ng-icons/core'
31
32
  import { iconoirRefresh } from '@ng-icons/iconoir'
33
+ import { matHelpOutline } from '@ng-icons/material-icons/outline'
32
34
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
33
35
  import {
34
36
  BehaviorSubject,
@@ -57,13 +59,14 @@ marker(
57
59
  MatTooltipModule,
58
60
  MatRadioModule,
59
61
  NgIconComponent,
62
+ SpinningLoaderComponent,
60
63
  TextInputComponent,
61
64
  TranslateDirective,
62
65
  TranslatePipe,
63
66
  UrlInputComponent,
64
67
  ],
65
68
  providers: [
66
- provideIcons({ iconoirRefresh }),
69
+ provideIcons({ iconoirRefresh, matHelpOutline }),
67
70
  provideNgIconsConfig({
68
71
  size: '1.5em',
69
72
  }),
@@ -79,17 +82,19 @@ export class OnlineServiceResourceInputComponent {
79
82
  @Input() protocolHint?: string
80
83
  @Input() disabled? = false
81
84
  @Input() modifyMode? = false
85
+ @Input() protocolOptions?: ServiceProtocol[]
82
86
  @Output() serviceChange: EventEmitter<DatasetServiceDistribution> =
83
87
  new EventEmitter()
84
88
 
85
89
  errorMessage = false
90
+ loading = false
86
91
  resetUrlOnChange = Math.random()
87
92
 
88
93
  layersSubject = new BehaviorSubject<{ name?: string; title?: string }[]>([])
89
94
  layers$: Observable<{ name?: string; title?: string }[]> =
90
95
  this.layersSubject.asObservable()
91
96
 
92
- protocolOptions: {
97
+ allProtocolOptions: {
93
98
  label: string
94
99
  value: ServiceProtocol
95
100
  }[] = [
@@ -123,6 +128,13 @@ export class OnlineServiceResourceInputComponent {
123
128
  },
124
129
  ]
125
130
 
131
+ get availableProtocolOptions() {
132
+ if (!this.protocolOptions) return this.allProtocolOptions
133
+ return this.protocolOptions.flatMap(
134
+ (v) => this.allProtocolOptions.find((o) => o.value === v) ?? []
135
+ )
136
+ }
137
+
126
138
  get activeLayerSuggestion() {
127
139
  return !['wps', 'GPFDL', 'esriRest', 'other'].includes(
128
140
  this._service.accessServiceProtocol
@@ -135,6 +147,8 @@ export class OnlineServiceResourceInputComponent {
135
147
  }
136
148
 
137
149
  async handleUploadClick(url: string) {
150
+ this.loading = true
151
+ this.cdr.detectChanges()
138
152
  try {
139
153
  const layers = await getLayers(url, this._service.accessServiceProtocol)
140
154
 
@@ -148,6 +162,7 @@ export class OnlineServiceResourceInputComponent {
148
162
  this.layersSubject.next([])
149
163
  }
150
164
 
165
+ this.loading = false
151
166
  this.cdr.detectChanges()
152
167
  }
153
168
 
@@ -159,6 +174,7 @@ export class OnlineServiceResourceInputComponent {
159
174
 
160
175
  resetLayersSuggestion() {
161
176
  this.errorMessage = false
177
+ this.loading = false
162
178
  this.layersSubject.next([])
163
179
  this._service.identifierInService = null
164
180
  }
@@ -0,0 +1,41 @@
1
+ import { Directive, ElementRef, inject, Input } from '@angular/core'
2
+
3
+ @Directive({
4
+ selector: '[gnUiFieldFocus]',
5
+ standalone: true,
6
+ exportAs: 'fieldFocus',
7
+ })
8
+ export class FieldFocusDirective {
9
+ @Input() gnUiFieldFocusGlowClass = 'gn-ui-field-focus-glow'
10
+
11
+ private el = inject(ElementRef)
12
+
13
+ public focusField(focusInnerTarget = true) {
14
+ setTimeout(() => {
15
+ const host = this.el.nativeElement as HTMLElement
16
+ const glowClass = this.gnUiFieldFocusGlowClass
17
+
18
+ host.classList.remove(glowClass)
19
+ void host.offsetWidth
20
+ host.classList.add(glowClass)
21
+ host.addEventListener(
22
+ 'animationend',
23
+ () => host.classList.remove(glowClass),
24
+ { once: true }
25
+ )
26
+
27
+ host.scrollIntoView({ behavior: 'smooth', block: 'start' })
28
+ if (!focusInnerTarget) {
29
+ return
30
+ }
31
+ const target =
32
+ host.querySelector<HTMLElement>(
33
+ 'input, textarea, select, [contenteditable="true"]'
34
+ ) ??
35
+ host.querySelector<HTMLElement>(
36
+ 'button:not([disabled]), [tabindex]:not([tabindex="-1"])'
37
+ )
38
+ target?.focus({ preventScroll: true })
39
+ })
40
+ }
41
+ }
@@ -2,7 +2,7 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  OnDestroy,
5
- OnInit,
5
+ afterNextRender,
6
6
  inject,
7
7
  } from '@angular/core'
8
8
  import { CommonModule } from '@angular/common'
@@ -67,9 +67,7 @@ export type ConstraintChoice =
67
67
  styleUrls: ['./form-field-constraints-shortcuts.component.css'],
68
68
  changeDetection: ChangeDetectionStrategy.OnPush,
69
69
  })
70
- export class FormFieldConstraintsShortcutsComponent
71
- implements OnInit, OnDestroy
72
- {
70
+ export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
73
71
  private editorFacade = inject(EditorFacade)
74
72
 
75
73
  legalConstraints$ = this.editorFacade.record$.pipe(
@@ -112,37 +110,38 @@ export class FormFieldConstraintsShortcutsComponent
112
110
 
113
111
  onDestroy$ = new Subject<void>()
114
112
 
115
- ngOnInit(): void {
116
- // hide all constraints if any toggle is activated
117
- this.anyToggleActivated$
118
- .pipe(takeUntil(this.onDestroy$), distinctUntilChanged())
119
- .subscribe((anyToggleActivated) => {
120
- if (anyToggleActivated) {
121
- this.hideAllConstraintSections()
122
- }
123
- })
113
+ constructor() {
114
+ // Deferred to afterNextRender to avoid dispatching store actions
115
+ // synchronously during Angular's change detection cycle (NG0100)
116
+ afterNextRender(() => {
117
+ this.anyToggleActivated$
118
+ .pipe(takeUntil(this.onDestroy$), distinctUntilChanged())
119
+ .subscribe((anyToggleActivated) => {
120
+ if (anyToggleActivated) {
121
+ this.hideAllConstraintSections()
122
+ }
123
+ })
124
124
 
125
- // also hide constraints which are empty arrays
126
- const hideEmptyConstraints = (
127
- constraints$: Observable<Constraint[]>,
128
- model: ConstraintChoice
129
- ) => {
130
- const isConstraintNotEmpty$ = constraints$.pipe(
131
- takeUntil(this.onDestroy$),
132
- map((c) => c.length > 0),
133
- distinctUntilChanged()
134
- )
135
- combineLatest([
136
- isConstraintNotEmpty$,
137
- this.anyToggleActivated$,
138
- ]).subscribe(([isNotEmpty, anyToggleActivated]) => {
139
- const visible = isNotEmpty && !anyToggleActivated
140
- this.editorFacade.setFieldVisibility({ model }, visible)
141
- })
142
- }
143
- hideEmptyConstraints(this.legalConstraints$, 'legalConstraints')
144
- hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
145
- hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
125
+ const hideEmptyConstraints = (
126
+ constraints$: Observable<Constraint[]>,
127
+ model: ConstraintChoice
128
+ ) => {
129
+ const isConstraintNotEmpty$ = constraints$.pipe(
130
+ takeUntil(this.onDestroy$),
131
+ map((c) => c.length > 0),
132
+ distinctUntilChanged()
133
+ )
134
+ combineLatest([isConstraintNotEmpty$, this.anyToggleActivated$])
135
+ .pipe(takeUntil(this.onDestroy$))
136
+ .subscribe(([isNotEmpty, anyToggleActivated]) => {
137
+ const visible = isNotEmpty && !anyToggleActivated
138
+ this.editorFacade.setFieldVisibility({ model }, visible)
139
+ })
140
+ }
141
+ hideEmptyConstraints(this.legalConstraints$, 'legalConstraints')
142
+ hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
143
+ hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
144
+ })
146
145
  }
147
146
 
148
147
  ngOnDestroy() {
@@ -10,21 +10,21 @@
10
10
  </gn-ui-autocomplete>
11
11
 
12
12
  @if (contacts.length > 0) {
13
- @if (contacts.length === 1) {
14
- @for (contact of contacts; track contact; let index = $index) {
13
+ <gn-ui-sortable-list
14
+ [items]="contacts"
15
+ (itemsOrderChange)="handleContactsChanged($event)"
16
+ [elementTemplate]="contactTemplate"
17
+ ></gn-ui-sortable-list>
18
+ <ng-template #contactTemplate let-contact let-index="index">
19
+ @if (modelSpecifier === 'contact:editableDetails') {
20
+ <gn-ui-contact-details-form
21
+ [contact]="contact"
22
+ (contactChange)="handleContactChanged($event, index)"
23
+ ></gn-ui-contact-details-form>
24
+ } @else {
15
25
  <gn-ui-contact-card [contact]="contact"></gn-ui-contact-card>
16
26
  }
17
- }
18
- @if (contacts.length > 1) {
19
- <gn-ui-sortable-list
20
- [items]="contacts"
21
- (itemsOrderChange)="handleContactsChanged($event)"
22
- [elementTemplate]="contactTemplate"
23
- ></gn-ui-sortable-list>
24
- <ng-template #contactTemplate let-contact>
25
- <gn-ui-contact-card [contact]="contact"></gn-ui-contact-card>
26
- </ng-template>
27
- }
27
+ </ng-template>
28
28
  } @else {
29
29
  <div
30
30
  class="p-4 text-sm border border-primary bg-primary-lightest rounded-lg"
@@ -28,9 +28,15 @@ import { UserModel } from '../../../../../../../../../libs/common/domain/src/lib
28
28
  import { PlatformServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/platform.service.interface'
29
29
  import { OrganizationsServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/organizations.service.interface'
30
30
  import { ContactCardComponent } from '../../../contact-card/contact-card.component'
31
- import { createFuzzyFilter } from '../../../../../../../../../libs/util/shared/src'
31
+ import { ContactDetailsFormComponent } from '../../../contact-details/contact-details-form.component'
32
+ import {
33
+ createFuzzyFilter,
34
+ getIndividualDisplayName,
35
+ toIndividual,
36
+ } from '../../../../../../../../../libs/util/shared/src'
32
37
  import { map } from 'rxjs/operators'
33
38
  import { SortableListComponent } from '../../../../../../../../../libs/ui/layout/src'
39
+ import { FieldModelSpecifier } from '../../../../models/editor-config.model'
34
40
 
35
41
  @Component({
36
42
  selector: 'gn-ui-form-field-contacts',
@@ -43,6 +49,7 @@ import { SortableListComponent } from '../../../../../../../../../libs/ui/layout
43
49
  TranslateDirective,
44
50
  TranslatePipe,
45
51
  ContactCardComponent,
52
+ ContactDetailsFormComponent,
46
53
  SortableListComponent,
47
54
  ],
48
55
  })
@@ -52,6 +59,7 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges {
52
59
  private changeDetectorRef = inject(ChangeDetectorRef)
53
60
 
54
61
  @Input() value: Individual[]
62
+ @Input() modelSpecifier: FieldModelSpecifier
55
63
  @Output() valueChange: EventEmitter<Individual[]> = new EventEmitter()
56
64
 
57
65
  contacts: Individual[] = []
@@ -113,13 +121,19 @@ export class FormFieldContactsComponent implements OnDestroy, OnChanges {
113
121
  this.valueChange.emit(contacts)
114
122
  }
115
123
 
124
+ handleContactChanged(updatedContact: Individual, index: number) {
125
+ const contacts = this.contacts.map((contact, i) =>
126
+ i === index ? updatedContact : contact
127
+ )
128
+
129
+ this.handleContactsChanged(contacts)
130
+ }
131
+
116
132
  /**
117
133
  * gn-ui-autocomplete
118
134
  */
119
135
  displayWithFn: (user: UserModel) => string = (user) =>
120
- `${user.name} ${user.surname} ${
121
- user.organisation ? `(${user.organisation})` : ''
122
- }`
136
+ getIndividualDisplayName(toIndividual(user))
123
137
 
124
138
  /**
125
139
  * gn-ui-autocomplete
@@ -8,6 +8,14 @@
8
8
  </gn-ui-button>
9
9
  }
10
10
  </div>
11
+ @if (value.length === 0) {
12
+ <div
13
+ class="p-4 border border-primary bg-primary-lightest rounded-lg"
14
+ translate
15
+ >
16
+ editor.record.form.field.contactsForResource.noContact
17
+ </div>
18
+ }
11
19
  @if (roleSectionsToDisplay && roleSectionsToDisplay.length > 0) {
12
20
  <div class="mt-8" data-test="displayedRoles">
13
21
  @for (
@@ -53,12 +61,5 @@
53
61
  </div>
54
62
  }
55
63
  </div>
56
- } @else {
57
- <div
58
- class="p-4 border border-primary bg-primary-lightest rounded-lg"
59
- translate
60
- >
61
- editor.record.form.field.contactsForResource.noContact
62
- </div>
63
64
  }
64
65
  </div>
@@ -23,7 +23,11 @@ import {
23
23
  AutocompleteComponent,
24
24
  ButtonComponent,
25
25
  } from '../../../../../../../../../libs/ui/inputs/src'
26
- import { createFuzzyFilter } from '../../../../../../../../../libs/util/shared/src'
26
+ import {
27
+ createFuzzyFilter,
28
+ getIndividualDisplayName,
29
+ toIndividual,
30
+ } from '../../../../../../../../../libs/util/shared/src'
27
31
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
28
32
  import {
29
33
  debounceTime,
@@ -169,11 +173,7 @@ export class FormFieldContactsForResourceComponent
169
173
  * gn-ui-autocomplete
170
174
  */
171
175
  displayWithFn: (user: UserModel) => string = (user) =>
172
- user.name
173
- ? `${user.name} ${user.surname} ${
174
- user.organisation ? `(${user.organisation})` : ''
175
- }`
176
- : ``
176
+ getIndividualDisplayName(toIndividual(user))
177
177
 
178
178
  /**
179
179
  * gn-ui-autocomplete
@@ -3,11 +3,11 @@ import {
3
3
  ChangeDetectorRef,
4
4
  Component,
5
5
  EventEmitter,
6
+ inject,
6
7
  Input,
7
8
  Output,
8
9
  TemplateRef,
9
10
  ViewChild,
10
- inject,
11
11
  } from '@angular/core'
12
12
  import { MatDialog } from '@angular/material/dialog'
13
13
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
@@ -0,0 +1,5 @@
1
+ <gn-ui-url-input
2
+ [value]="displayUrl"
3
+ [showValidateButton]="false"
4
+ (valueChange)="handleUrlChange($event)"
5
+ ></gn-ui-url-input>
@@ -0,0 +1,89 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ Output,
7
+ inject,
8
+ } from '@angular/core'
9
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
10
+ import {
11
+ OnlineLinkResource,
12
+ OnlineResource,
13
+ } from '../../../../../../../../../libs/common/domain/src/lib/model/record'
14
+ import { NotificationsService } from '../../../../../../../../../libs/feature/notifications/src'
15
+ import { UrlInputComponent } from '../../../../../../../../../libs/ui/inputs/src'
16
+ import { TranslateService } from '@ngx-translate/core'
17
+
18
+ marker('editor.record.form.field.onlineLinkageResource.defaultName')
19
+
20
+ @Component({
21
+ selector: 'gn-ui-form-field-online-single-link-resource',
22
+ templateUrl: './form-field-online-single-link-resource.component.html',
23
+ styleUrls: ['./form-field-online-single-link-resource.component.css'],
24
+ changeDetection: ChangeDetectionStrategy.OnPush,
25
+ standalone: true,
26
+ imports: [UrlInputComponent],
27
+ })
28
+ export class FormFieldOnlineSingleLinkResourceComponent {
29
+ private notificationsService = inject(NotificationsService)
30
+ private translateService = inject(TranslateService)
31
+
32
+ @Input() set value(onlineResources: Array<OnlineResource>) {
33
+ this.allResources = onlineResources ?? []
34
+ const firstResource = this.allResources[0]
35
+ this.displayUrl = firstResource?.url?.toString() ?? ''
36
+ }
37
+ @Output() valueChange: EventEmitter<Array<OnlineResource>> =
38
+ new EventEmitter()
39
+
40
+ private allResources: OnlineResource[] = []
41
+ displayUrl = ''
42
+
43
+ handleUrlChange(url: string | null) {
44
+ if (!url) return
45
+
46
+ try {
47
+ const parsedUrl = new URL(url)
48
+
49
+ if (this.allResources.length === 0) {
50
+ const defaultName = this.translateService.instant(
51
+ 'editor.record.form.field.onlineLinkageResource.defaultName'
52
+ )
53
+ const newResource: OnlineLinkResource = {
54
+ type: 'link',
55
+ url: parsedUrl,
56
+ name: defaultName,
57
+ }
58
+ this.valueChange.emit([newResource])
59
+ } else {
60
+ const updatedFirst: OnlineResource = {
61
+ ...this.allResources[0],
62
+ url: parsedUrl,
63
+ }
64
+ this.valueChange.emit([updatedFirst, ...this.allResources.slice(1)])
65
+ }
66
+ } catch (error) {
67
+ this.handleError(error as Error)
68
+ }
69
+ }
70
+
71
+ private handleError(error: Error) {
72
+ this.notificationsService.showNotification(
73
+ {
74
+ type: 'error',
75
+ title: this.translateService.instant(
76
+ 'editor.record.onlineResourceError.title'
77
+ ),
78
+ text: `${this.translateService.instant(
79
+ 'editor.record.onlineResourceError.body'
80
+ )} ${error.message}`,
81
+ closeMessage: this.translateService.instant(
82
+ 'editor.record.onlineResourceError.closeMessage'
83
+ ),
84
+ },
85
+ undefined,
86
+ error
87
+ )
88
+ }
89
+ }
@@ -3,11 +3,14 @@ import {
3
3
  ChangeDetectorRef,
4
4
  Component,
5
5
  EventEmitter,
6
+ inject,
6
7
  Input,
7
8
  Output,
8
- inject,
9
9
  } from '@angular/core'
10
- import { GraphicOverview } from '../../../../../../../../../libs/common/domain/src/lib/model/record'
10
+ import {
11
+ GraphicOverview,
12
+ RecordKind,
13
+ } from '../../../../../../../../../libs/common/domain/src/lib/model/record'
11
14
  import { ImageInputComponent } from '../../../../../../../../../libs/ui/elements/src'
12
15
  import { PlatformServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/platform.service.interface'
13
16
  import { NotificationsService } from '../../../../../../../../../libs/feature/notifications/src'
@@ -30,6 +33,7 @@ export class FormFieldOverviewsComponent {
30
33
  private cd = inject(ChangeDetectorRef)
31
34
 
32
35
  @Input() metadataUuid: string
36
+ @Input() recordKind: RecordKind
33
37
  @Input() value: Array<GraphicOverview>
34
38
  @Output() valueChange: EventEmitter<Array<GraphicOverview>> =
35
39
  new EventEmitter()
@@ -111,13 +115,16 @@ export class FormFieldOverviewsComponent {
111
115
  {
112
116
  type: 'error',
113
117
  title: this.translateService.instant(
114
- 'editor.record.resourceError.title'
118
+ 'editor.record.resourceError.title',
119
+ { recordKind: this.recordKind }
115
120
  ),
116
121
  text: `${this.translateService.instant(
117
- 'editor.record.resourceError.body'
122
+ 'editor.record.resourceError.body',
123
+ { recordKind: this.recordKind }
118
124
  )} ${error.message}`,
119
125
  closeMessage: this.translateService.instant(
120
- 'editor.record.resourceError.closeMessage'
126
+ 'editor.record.resourceError.closeMessage',
127
+ { recordKind: this.recordKind }
121
128
  ),
122
129
  },
123
130
  undefined,
@@ -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,
@@ -1,11 +1,15 @@
1
1
  <div class="flex flex-col gap-2">
2
2
  <gn-ui-check-toggle
3
- [label]="'editor.record.form.updateFrequency.planned' | translate"
3
+ [label]="
4
+ 'editor.record.form.updateFrequency.planned' | translate: { recordKind }
5
+ "
4
6
  [value]="planned"
5
7
  (toggled)="onPlannedToggled()"
6
8
  ></gn-ui-check-toggle>
7
9
  <gn-ui-dropdown-selector
8
- [title]="'editor.record.form.field.updateFrequency' | translate"
10
+ [title]="
11
+ 'editor.record.form.field.updateFrequency' | translate: { recordKind }
12
+ "
9
13
  [showTitle]="false"
10
14
  [choices]="choices"
11
15
  [selected]="selectedFrequency"
@@ -2,12 +2,13 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  EventEmitter,
5
+ inject,
5
6
  Input,
6
7
  OnInit,
7
8
  Output,
8
- inject,
9
9
  } from '@angular/core'
10
10
  import {
11
+ RecordKind,
11
12
  UpdateFrequency,
12
13
  UpdateFrequencyCode,
13
14
  updateFrequencyCodeValues,
@@ -37,6 +38,7 @@ export class FormFieldUpdateFrequencyComponent implements OnInit {
37
38
  private translateService = inject(TranslateService)
38
39
 
39
40
  @Input() value: UpdateFrequency
41
+ @Input() recordKind: RecordKind
40
42
  @Output() valueChange: EventEmitter<UpdateFrequency> = new EventEmitter()
41
43
 
42
44
  protected choices: DropdownChoice[] = []