geonetwork-ui 2.10.0-dev.b5836aad7 → 2.10.0-dev.bf2e85a24

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 (158) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2285 -6919
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +248 -173
  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 +72 -38
  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 +56 -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-resources/form-field-online-resources.component.ts +1 -1
  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.css +0 -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.html +5 -0
  66. 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
  67. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  68. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  69. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  70. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  71. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  72. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  73. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +3 -0
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +21 -16
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +14 -0
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -0
  77. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  79. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +118 -5
  80. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  81. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  82. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  83. package/src/libs/feature/editor/src/lib/services/editor.service.ts +9 -13
  84. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  85. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  86. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  87. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  88. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  89. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  90. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  91. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  92. package/src/libs/ui/dataviz/src/index.ts +0 -1
  93. package/src/libs/ui/elements/src/index.ts +2 -0
  94. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +88 -0
  95. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.ts +45 -0
  96. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +41 -0
  97. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +70 -0
  98. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  99. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  100. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  101. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  102. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  103. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  104. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  105. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  106. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.ts +2 -5
  107. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  108. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +39 -68
  109. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +59 -11
  110. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  111. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  112. package/src/libs/ui/inputs/src/lib/button/button.component.ts +8 -0
  113. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  114. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  115. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  116. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.html +2 -2
  117. package/src/libs/ui/inputs/src/lib/url-input/url-input.component.ts +2 -1
  118. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  119. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  120. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  121. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  122. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  123. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  124. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  125. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +15 -64
  126. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  127. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  128. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  129. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  130. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  131. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  132. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  133. package/src/libs/util/app-config/src/lib/app-config.ts +70 -9
  134. package/src/libs/util/app-config/src/lib/model.ts +7 -0
  135. package/src/libs/util/app-config/src/lib/parse-utils.ts +50 -1
  136. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  137. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  138. package/src/libs/util/i18n/src/index.ts +0 -1
  139. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  140. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  141. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  142. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  143. package/src/libs/util/shared/src/lib/links/link-utils.ts +23 -1
  144. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  145. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  146. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  147. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  148. package/src/libs/util/shared/src/lib/utils/user-display.ts +32 -0
  149. package/style.css +0 -1
  150. package/tailwind.base.css +19 -2
  151. package/translations/de.json +81 -48
  152. package/translations/en.json +86 -53
  153. package/translations/es.json +55 -22
  154. package/translations/fr.json +83 -50
  155. package/translations/it.json +81 -48
  156. package/translations/nl.json +54 -21
  157. package/translations/pt.json +55 -22
  158. package/translations/sk.json +55 -22
@@ -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[] = []
@@ -12,8 +12,8 @@
12
12
  </ng-container>
13
13
  <ng-template #withGenericWrapper>
14
14
  <gn-ui-form-field-wrapper
15
- [label]="config.labelKey! | translate"
16
- [hint]="config.hintKey! | translate"
15
+ [label]="config.labelKey! | translate: { recordKind }"
16
+ [hint]="config.hintKey! | translate: { recordKind }"
17
17
  >
18
18
  <ng-container *ngTemplateOutlet="fieldContent"></ng-container>
19
19
  </gn-ui-form-field-wrapper>
@@ -34,30 +34,32 @@
34
34
  [value]="valueAsString"
35
35
  (change)="valueChange.emit($event.target.value)"
36
36
  [placeholder]="
37
- 'editor.record.form.field.title.placeholder' | translate
37
+ 'editor.record.form.field.title.placeholder'
38
+ | translate: { recordKind }
38
39
  "
39
40
  ></textarea>
40
41
  <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"
42
+ <ng-icon
43
+ name="matEditOutline"
44
+ size="16"
45
+ class="m-2 cursor-pointer"
43
46
  (click)="focusTitleInput()"
44
- >edit</span
45
- >
46
- <span
47
+ ></ng-icon>
48
+ <ng-icon
47
49
  *ngIf="config.hintKey"
48
- class="material-symbols-outlined gn-ui-icon-small m-2"
49
- [matTooltip]="config.hintKey! | translate"
50
+ name="matHelpOutline"
51
+ size="16"
52
+ class="m-2"
53
+ [matTooltip]="config.hintKey! | translate: { recordKind }"
50
54
  matTooltipPosition="above"
51
- >
52
- help
53
- </span>
55
+ ></ng-icon>
54
56
  </div>
55
57
  </div>
56
58
  </ng-container>
57
59
  <ng-container *ngSwitchCase="'abstract'">
58
60
  <gn-ui-form-field-rich
59
- [label]="config.labelKey! | translate"
60
- [hint]="config.hintKey! | translate"
61
+ [label]="config.labelKey! | translate: { recordKind }"
62
+ [hint]="config.hintKey! | translate: { recordKind }"
61
63
  [value]="valueAsString"
62
64
  (valueChange)="valueChange.emit($event)"
63
65
  ></gn-ui-form-field-rich>
@@ -65,6 +67,7 @@
65
67
  <ng-container *ngSwitchCase="'overviews'">
66
68
  <gn-ui-form-field-overviews
67
69
  [metadataUuid]="uniqueIdentifier"
70
+ [recordKind]="recordKind"
68
71
  [value]="valueAsOverviews"
69
72
  (valueChange)="valueChange.emit($event)"
70
73
  ></gn-ui-form-field-overviews>
@@ -92,6 +95,7 @@
92
95
  <gn-ui-form-field-update-frequency
93
96
  [value]="valueAsUpdateFrequency"
94
97
  (valueChange)="valueChange.emit($event)"
98
+ [recordKind]="recordKind"
95
99
  ></gn-ui-form-field-update-frequency>
96
100
  </ng-container>
97
101
  <ng-container *ngSwitchCase="'temporalExtents'">
@@ -117,7 +121,7 @@
117
121
  </ng-container>
118
122
  <ng-container *ngSwitchCase="'licenses'">
119
123
  <gn-ui-form-field-license
120
- [label]="config.labelKey! | translate"
124
+ [label]="config.labelKey! | translate: { recordKind }"
121
125
  [recordLicences]="valueAsConstraints"
122
126
  (recordLicencesChange)="valueChange.emit($event)"
123
127
  ></gn-ui-form-field-license>
@@ -150,6 +154,7 @@
150
154
  <ng-container *ngSwitchCase="'contacts'">
151
155
  <gn-ui-form-field-contacts
152
156
  [value]="valueAsIndividuals"
157
+ [modelSpecifier]="modelSpecifier"
153
158
  (valueChange)="valueChange.emit($event)"
154
159
  ></gn-ui-form-field-contacts>
155
160
  </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,
@@ -17,11 +18,18 @@ import {
17
18
  Individual,
18
19
  Keyword,
19
20
  OnlineResource,
21
+ RecordKind,
20
22
  UpdateFrequency,
21
23
  } from '../../../../../../../../libs/common/domain/src/lib/model/record'
22
24
  import { FormFieldWrapperComponent } from '../../../../../../../../libs/ui/layout/src'
25
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
26
+ import {
27
+ matEditOutline,
28
+ matHelpOutline,
29
+ } from '@ng-icons/material-icons/outline'
23
30
  import { TranslatePipe } from '@ngx-translate/core'
24
31
  import {
32
+ FieldFocusDirective,
25
33
  FormFieldDateComponent,
26
34
  FormFieldLicenseComponent,
27
35
  FormFieldTemporalExtentsComponent,
@@ -76,10 +84,14 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
76
84
  FormFieldSpatialToggleComponent,
77
85
  FormFieldTopicsComponent,
78
86
  TextFieldModule,
87
+ NgIconComponent,
79
88
  ],
89
+ providers: [provideIcons({ matEditOutline, matHelpOutline })],
90
+ hostDirectives: [FieldFocusDirective],
80
91
  })
81
92
  export class FormFieldComponent {
82
93
  @Input() uniqueIdentifier: string
94
+ @Input() recordKind: RecordKind
83
95
  @Input() model: CatalogRecordKeys
84
96
  @Input() modelSpecifier: FieldModelSpecifier
85
97
  @Input() componentName: FormFieldComponentName
@@ -92,6 +104,8 @@ export class FormFieldComponent {
92
104
  @ViewChild('titleInput') titleInput: ElementRef
93
105
  isOpenData = false
94
106
 
107
+ fieldFocus = inject(FieldFocusDirective)
108
+
95
109
  toggleIsOpenData(event: boolean) {
96
110
  this.isOpenData = event
97
111
  }
@@ -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
+ }
@@ -3,16 +3,28 @@
3
3
  section of facade.currentSections$ | async;
4
4
  track sectionTracker($index, section)
5
5
  ) {
6
- @if (!section.hidden) {
7
- <div class="flex flex-col gap-6 border p-8 rounded-[8px] shadow">
6
+ @if (evaluateExpression(section.hidden)() !== true) {
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
- <div class="text-2xl font-title text-black" translate>
14
+ <div
15
+ class="text-2xl font-title text-black"
16
+ translate
17
+ [translateParams]="{ recordKind: recordKind$ | async }"
18
+ >
11
19
  {{ section.labelKey }}
12
20
  </div>
13
21
  }
14
22
  @if (section.descriptionKey) {
15
- <div class="text-gray-800 text-sm" translate>
23
+ <div
24
+ class="text-gray-800 text-sm"
25
+ translate
26
+ [translateParams]="{ recordKind: recordKind$ | async }"
27
+ >
16
28
  {{ section.descriptionKey }}
17
29
  </div>
18
30
  }
@@ -22,7 +34,7 @@
22
34
  field of section.fieldsWithValues;
23
35
  track fieldTracker($index, field)
24
36
  ) {
25
- @if (!field.config.hidden) {
37
+ @if (evaluateExpression(field.config.hidden)() !== true) {
26
38
  <gn-ui-form-field
27
39
  [ngClass]="
28
40
  field.config.gridColumnSpan === 1
@@ -30,6 +42,7 @@
30
42
  : 'col-span-2'
31
43
  "
32
44
  [uniqueIdentifier]="recordUniqueIdentifier$ | async"
45
+ [recordKind]="recordKind$ | async"
33
46
  [model]="field.config.model!"
34
47
  [modelSpecifier]="field.config.modelSpecifier!"
35
48
  [config]="field.config.formFieldConfig"
@@ -1,15 +1,29 @@
1
1
  import { CommonModule } from '@angular/common'
2
- import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ computed,
6
+ inject,
7
+ OnDestroy,
8
+ OnInit,
9
+ Signal,
10
+ signal,
11
+ viewChildren,
12
+ } from '@angular/core'
3
13
  import { EditorFacade } from '../../+state/editor.facade'
4
14
  import { EditorFieldValue } from '../../models'
5
- import { FormFieldComponent } from './form-field'
15
+ import { FieldFocusDirective, FormFieldComponent } from './form-field'
6
16
  import { TranslateDirective } from '@ngx-translate/core'
7
17
  import {
8
18
  EditorFieldWithValue,
9
19
  EditorSectionWithValues,
10
20
  } from '../../+state/editor.models'
11
- import { map } from 'rxjs'
21
+ import { firstValueFrom, map, Subscription, withLatestFrom } from 'rxjs'
12
22
  import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
23
+ import { switchMap } from 'rxjs/operators'
24
+ import { toSignal } from '@angular/core/rxjs-interop'
25
+ import { evaluate, isExpression } from '../../expressions'
26
+ import { NotificationsService } from '../../../../../../../libs/feature/notifications/src'
13
27
 
14
28
  @Component({
15
29
  selector: 'gn-ui-record-form',
@@ -17,14 +31,64 @@ import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/l
17
31
  styleUrls: ['./record-form.component.css'],
18
32
  changeDetection: ChangeDetectionStrategy.OnPush,
19
33
  standalone: true,
20
- imports: [CommonModule, FormFieldComponent, TranslateDirective],
34
+ imports: [
35
+ CommonModule,
36
+ FormFieldComponent,
37
+ FieldFocusDirective,
38
+ TranslateDirective,
39
+ ],
21
40
  })
22
- export class RecordFormComponent {
41
+ export class RecordFormComponent implements OnInit, OnDestroy {
23
42
  facade = inject(EditorFacade)
43
+ notifications = inject(NotificationsService)
44
+ subscription = new Subscription()
24
45
 
25
46
  recordUniqueIdentifier$ = this.facade.record$.pipe(
26
47
  map((record) => record.uniqueIdentifier)
27
48
  )
49
+ recordKind$ = this.facade.record$.pipe(map((record) => record.kind))
50
+
51
+ focusFieldWithPage$ = this.facade.focusedField$.pipe(
52
+ switchMap(async (field) => {
53
+ const location = await this.getFieldLocation(field)
54
+ return [field, location?.page ?? null, location?.section ?? -1] as const
55
+ })
56
+ )
57
+
58
+ formFields = viewChildren(FormFieldComponent)
59
+ sectionFocusDirectives = viewChildren<FieldFocusDirective>('sectionFocus')
60
+
61
+ focusField(model: CatalogRecordKeys, sectionIndex: number) {
62
+ const field = this.formFields().find((f) => f.model === model)
63
+ if (field) {
64
+ field.fieldFocus.focusField()
65
+ return
66
+ }
67
+ this.sectionFocusDirectives()[sectionIndex]?.focusField(false)
68
+ }
69
+
70
+ ngOnInit() {
71
+ this.subscription.add(
72
+ this.focusFieldWithPage$
73
+ .pipe(
74
+ withLatestFrom(
75
+ this.facade.currentPage$,
76
+ ([field, fieldPage, fieldSection], currentPage) =>
77
+ [field, fieldPage, fieldSection, currentPage] as const
78
+ )
79
+ )
80
+ .subscribe(([field, fieldPage, fieldSection, currentPage]) => {
81
+ if (fieldPage !== null && fieldPage !== currentPage) {
82
+ this.facade.setCurrentPage(fieldPage)
83
+ }
84
+ setTimeout(() => this.focusField(field, fieldSection))
85
+ })
86
+ )
87
+ }
88
+
89
+ ngOnDestroy() {
90
+ this.subscription.unsubscribe()
91
+ }
28
92
 
29
93
  handleFieldValueChange(model: CatalogRecordKeys, newValue: EditorFieldValue) {
30
94
  if (!model) {
@@ -40,4 +104,53 @@ export class RecordFormComponent {
40
104
  sectionTracker(index: number, section: EditorSectionWithValues) {
41
105
  return section.labelKey
42
106
  }
107
+
108
+ async getFieldLocation(
109
+ model: CatalogRecordKeys
110
+ ): Promise<{ page: number; section: number } | null> {
111
+ const config = await firstValueFrom(this.facade.editorConfig$)
112
+ const page = config.pages.findIndex((p) =>
113
+ p.sections.some((s) => s.fields.some((f) => f.model === model))
114
+ )
115
+ if (page < 0) {
116
+ return null
117
+ }
118
+ const section = config.pages[page].sections
119
+ .filter((s) => !s.hidden)
120
+ .findIndex((s) => s.fields.some((f) => f.model === model))
121
+ return { page, section }
122
+ }
123
+
124
+ private recordSignal = toSignal(this.facade.record$, { requireSync: true })
125
+ private expressionCache = new Map<string, Signal<EditorFieldValue>>()
126
+
127
+ // only compiles the expression into an evaluator the first time;
128
+ // then we store a computed() signal in the cache so that the evaluator is
129
+ // run again if the record changes
130
+ evaluateExpression(
131
+ expressionOrValue: string | EditorFieldValue
132
+ ): Signal<EditorFieldValue> {
133
+ if (!isExpression(expressionOrValue)) {
134
+ return signal(expressionOrValue as EditorFieldValue)
135
+ }
136
+ const expression = expressionOrValue as string
137
+ if (this.expressionCache.has(expression)) {
138
+ return this.expressionCache.get(expression)
139
+ }
140
+ const { evaluator, errors } = evaluate(expression)
141
+ if (errors.length) {
142
+ console.error(`The following errors happened while evaluating the expression '${expression}':
143
+ ${errors.map((err) => `> ${err}`).join('\n')}`)
144
+ this.notifications.showNotification({
145
+ type: 'warning',
146
+ title: 'Technical error',
147
+ text: 'An error happened while evaluating an expression inside the editor configuration; open the developer console for more information',
148
+ })
149
+ }
150
+ this.expressionCache.set(
151
+ expression,
152
+ computed(() => evaluator({ globals: { record: this.recordSignal() } }))
153
+ )
154
+ return this.expressionCache.get(expression)
155
+ }
43
156
  }