geonetwork-ui 2.10.0-dev.f143bfdfa → 2.10.0-dev.f1c00d5fe

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 (142) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1331 -6228
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +186 -170
  4. package/index.d.ts.map +1 -1
  5. package/package.json +23 -33
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +11 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/eu.dcat-ap.records.ts +2 -0
  8. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +22 -0
  9. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+ongules.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.reuse+roilaye.ts +1 -0
  11. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +1 -0
  12. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +1 -0
  13. package/src/libs/api/metadata-converter/src/lib/fixtures/georhena.records.ts +1 -0
  14. package/src/libs/api/metadata-converter/src/lib/fixtures/metadata-for-i18n.records.ts +2 -0
  15. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +22 -0
  16. package/src/libs/api/metadata-converter/src/lib/fixtures/opendataswiss.records.ts +1 -0
  17. package/src/libs/api/metadata-converter/src/lib/fixtures/sextant.records.ts +2 -0
  18. package/src/libs/api/metadata-converter/src/lib/fixtures/vlaanderen.dcat-ap.records.ts +1 -0
  19. package/src/libs/api/metadata-converter/src/lib/fixtures/wallonie.records.reuse.ts +1 -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 +1 -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 +4 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +13 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +45 -1
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -1
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +40 -0
  30. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +47 -1
  31. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -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 +12 -8
  34. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  35. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  36. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -22
  37. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -5
  38. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  39. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  40. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +57 -0
  41. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  42. package/src/libs/feature/editor/src/index.ts +1 -0
  43. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  44. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  45. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  46. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  47. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +7 -6
  48. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +10 -8
  49. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  50. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  51. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  52. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  55. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  56. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  57. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  58. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  59. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  60. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  61. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  62. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  63. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +27 -16
  64. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -0
  65. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  66. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  67. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  68. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  69. package/src/libs/feature/editor/src/lib/fields.config.ts +354 -337
  70. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  71. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  72. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  73. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  74. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  75. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  76. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  77. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  78. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +221 -8
  79. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  80. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  81. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  82. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  83. package/src/libs/ui/dataviz/src/index.ts +0 -1
  84. package/src/libs/ui/elements/src/index.ts +1 -0
  85. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  86. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  87. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  88. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  89. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  90. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  91. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  92. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  93. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  94. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  95. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +4 -7
  96. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  97. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  98. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  99. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  100. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  101. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  102. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  103. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  104. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  105. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  106. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  107. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  108. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  109. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  110. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  111. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  112. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  113. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  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/results-table/action-menu/action-menu.component.html +12 -8
  117. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  118. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  119. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  120. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  121. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  122. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  123. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  124. package/src/libs/util/i18n/src/index.ts +0 -1
  125. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  126. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  127. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  128. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  129. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  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/style.css +0 -1
  133. package/tailwind.base.css +9 -0
  134. package/translations/de.json +102 -49
  135. package/translations/en.json +112 -59
  136. package/translations/es.json +76 -23
  137. package/translations/fr.json +109 -56
  138. package/translations/it.json +103 -50
  139. package/translations/nl.json +75 -22
  140. package/translations/pt.json +76 -23
  141. package/translations/sk.json +76 -23
  142. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -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[] = []
@@ -1,37 +1,3 @@
1
1
  :host {
2
2
  scroll-margin-top: 90px;
3
3
  }
4
-
5
- :host.gn-ui-field-focus-glow {
6
- position: relative;
7
- }
8
-
9
- :host.gn-ui-field-focus-glow::after {
10
- content: '';
11
- position: absolute;
12
- inset: -8px;
13
- border-radius: 8px;
14
- background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
15
- pointer-events: none;
16
- animation: gn-ui-field-focus-glow 3s ease-out forwards;
17
- }
18
-
19
- :host.gn-ui-field-focus-glow::before {
20
- content: '';
21
- position: absolute;
22
- inset: -16px;
23
- border: 2px dashed var(--color-primary);
24
- border-radius: 10px;
25
- pointer-events: none;
26
- animation: gn-ui-field-focus-glow 3s ease-out forwards;
27
- }
28
-
29
- @keyframes gn-ui-field-focus-glow {
30
- 0%,
31
- 55% {
32
- opacity: 1;
33
- }
34
- 100% {
35
- opacity: 0;
36
- }
37
- }
@@ -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>
@@ -170,6 +174,13 @@
170
174
  (valueChange)="valueChange.emit($event)"
171
175
  ></gn-ui-form-field-online-link-resources>
172
176
  </ng-container>
177
+ <ng-container *ngSwitchCase="'onlineResources'">
178
+ <gn-ui-form-field-online-single-link-resource
179
+ *ngIf="modelSpecifier === 'onlineResourceType:singleLink'"
180
+ [value]="valueAsOnlineResources"
181
+ (valueChange)="valueChange.emit($event)"
182
+ ></gn-ui-form-field-online-single-link-resource>
183
+ </ng-container>
173
184
  </ng-container>
174
185
 
175
186
  <ng-container *ngIf="!model">
@@ -18,9 +18,15 @@ import {
18
18
  Individual,
19
19
  Keyword,
20
20
  OnlineResource,
21
+ RecordKind,
21
22
  UpdateFrequency,
22
23
  } from '../../../../../../../../libs/common/domain/src/lib/model/record'
23
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'
24
30
  import { TranslatePipe } from '@ngx-translate/core'
25
31
  import {
26
32
  FieldFocusDirective,
@@ -38,6 +44,7 @@ import { FormFieldContactsComponent } from './form-field-contacts/form-field-con
38
44
  import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component'
39
45
  import { FormFieldOnlineLinkResourcesComponent } from './form-field-online-link-resources/form-field-online-link-resources.component'
40
46
  import { FormFieldOnlineResourcesComponent } from './form-field-online-resources/form-field-online-resources.component'
47
+ import { FormFieldOnlineSingleLinkResourceComponent } from './form-field-online-single-link-resource/form-field-online-single-link-resource.component'
41
48
  import { FormFieldOverviewsComponent } from './form-field-overviews/form-field-overviews.component'
42
49
  import { FormFieldRichComponent } from './form-field-rich/form-field-rich.component'
43
50
  import { FormFieldSimpleComponent } from './form-field-simple/form-field-simple.component'
@@ -72,17 +79,21 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
72
79
  FormFieldContactsForResourceComponent,
73
80
  FormFieldOnlineResourcesComponent,
74
81
  FormFieldOnlineLinkResourcesComponent,
82
+ FormFieldOnlineSingleLinkResourceComponent,
75
83
  FormFieldContactsComponent,
76
84
  FormFieldConstraintsComponent,
77
85
  FormFieldConstraintsShortcutsComponent,
78
86
  FormFieldSpatialToggleComponent,
79
87
  FormFieldTopicsComponent,
80
88
  TextFieldModule,
89
+ NgIconComponent,
81
90
  ],
91
+ providers: [provideIcons({ matEditOutline, matHelpOutline })],
82
92
  hostDirectives: [FieldFocusDirective],
83
93
  })
84
94
  export class FormFieldComponent {
85
95
  @Input() uniqueIdentifier: string
96
+ @Input() recordKind: RecordKind
86
97
  @Input() model: CatalogRecordKeys
87
98
  @Input() modelSpecifier: FieldModelSpecifier
88
99
  @Input() componentName: FormFieldComponentName
@@ -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] bg-white"
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"
@@ -2,14 +2,17 @@ import { CommonModule } from '@angular/common'
2
2
  import {
3
3
  ChangeDetectionStrategy,
4
4
  Component,
5
+ computed,
5
6
  inject,
6
7
  OnDestroy,
7
8
  OnInit,
9
+ Signal,
10
+ signal,
8
11
  viewChildren,
9
12
  } from '@angular/core'
10
13
  import { EditorFacade } from '../../+state/editor.facade'
11
14
  import { EditorFieldValue } from '../../models'
12
- import { FormFieldComponent } from './form-field'
15
+ import { FieldFocusDirective, FormFieldComponent } from './form-field'
13
16
  import { TranslateDirective } from '@ngx-translate/core'
14
17
  import {
15
18
  EditorFieldWithValue,
@@ -18,6 +21,9 @@ import {
18
21
  import { firstValueFrom, map, Subscription, withLatestFrom } from 'rxjs'
19
22
  import { CatalogRecordKeys } from '../../../../../../../libs/common/domain/src/lib/model/record'
20
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'
21
27
 
22
28
  @Component({
23
29
  selector: 'gn-ui-record-form',
@@ -25,28 +31,40 @@ import { switchMap } from 'rxjs/operators'
25
31
  styleUrls: ['./record-form.component.css'],
26
32
  changeDetection: ChangeDetectionStrategy.OnPush,
27
33
  standalone: true,
28
- imports: [CommonModule, FormFieldComponent, TranslateDirective],
34
+ imports: [
35
+ CommonModule,
36
+ FormFieldComponent,
37
+ FieldFocusDirective,
38
+ TranslateDirective,
39
+ ],
29
40
  })
30
41
  export class RecordFormComponent implements OnInit, OnDestroy {
31
42
  facade = inject(EditorFacade)
43
+ notifications = inject(NotificationsService)
32
44
  subscription = new Subscription()
33
45
 
34
46
  recordUniqueIdentifier$ = this.facade.record$.pipe(
35
47
  map((record) => record.uniqueIdentifier)
36
48
  )
49
+ recordKind$ = this.facade.record$.pipe(map((record) => record.kind))
37
50
 
38
51
  focusFieldWithPage$ = this.facade.focusedField$.pipe(
39
- switchMap(
40
- async (field) => [field, await this.getPageIndexForField(field)] as const
41
- )
52
+ switchMap(async (field) => {
53
+ const location = await this.getFieldLocation(field)
54
+ return [field, location?.page ?? null, location?.section ?? -1] as const
55
+ })
42
56
  )
43
57
 
44
58
  formFields = viewChildren(FormFieldComponent)
59
+ sectionFocusDirectives = viewChildren<FieldFocusDirective>('sectionFocus')
45
60
 
46
- focusField(model: CatalogRecordKeys) {
47
- const fields = this.formFields()
48
- const field = fields.find((f) => f.model === model)
49
- field?.fieldFocus.focusField()
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)
50
68
  }
51
69
 
52
70
  ngOnInit() {
@@ -55,15 +73,15 @@ export class RecordFormComponent implements OnInit, OnDestroy {
55
73
  .pipe(
56
74
  withLatestFrom(
57
75
  this.facade.currentPage$,
58
- ([field, fieldPage], currentPage) =>
59
- [field, fieldPage, currentPage] as const
76
+ ([field, fieldPage, fieldSection], currentPage) =>
77
+ [field, fieldPage, fieldSection, currentPage] as const
60
78
  )
61
79
  )
62
- .subscribe(([field, fieldPage, currentPage]) => {
80
+ .subscribe(([field, fieldPage, fieldSection, currentPage]) => {
63
81
  if (fieldPage !== null && fieldPage !== currentPage) {
64
82
  this.facade.setCurrentPage(fieldPage)
65
83
  }
66
- setTimeout(() => this.focusField(field))
84
+ setTimeout(() => this.focusField(field, fieldSection))
67
85
  })
68
86
  )
69
87
  }
@@ -87,13 +105,52 @@ export class RecordFormComponent implements OnInit, OnDestroy {
87
105
  return section.labelKey
88
106
  }
89
107
 
90
- async getPageIndexForField(model: CatalogRecordKeys): Promise<number | null> {
108
+ async getFieldLocation(
109
+ model: CatalogRecordKeys
110
+ ): Promise<{ page: number; section: number } | null> {
91
111
  const config = await firstValueFrom(this.facade.editorConfig$)
92
- const pageIndex = config.pages.findIndex((page) =>
93
- page.sections.some((section) =>
94
- section.fields.some((field) => field.model === model)
95
- )
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() } }))
96
153
  )
97
- return pageIndex >= 0 ? pageIndex : null
154
+ return this.expressionCache.get(expression)
98
155
  }
99
156
  }