geonetwork-ui 2.10.0-dev.cf0577fae → 2.10.0-dev.d26e9afe1

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 (101) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1035 -6108
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +147 -154
  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/gn4/atomic-operations.ts +48 -7
  8. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +5 -4
  9. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  10. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  11. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -4
  12. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  13. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +49 -35
  14. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  15. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  16. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +48 -2
  17. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -5
  18. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  19. package/src/libs/common/domain/src/lib/model/user/group.model.ts +8 -0
  20. package/src/libs/common/domain/src/lib/model/user/index.ts +1 -0
  21. package/src/libs/common/domain/src/lib/platform.service.interface.ts +2 -0
  22. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +51 -0
  23. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  24. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  25. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  26. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  27. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  28. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  29. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  30. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  31. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +6 -7
  32. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  33. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  34. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  35. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  36. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  37. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  38. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  39. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  40. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +20 -16
  41. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +9 -0
  42. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  43. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  44. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  45. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  46. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  47. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +2 -3
  48. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  49. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  50. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  51. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  52. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  53. package/src/libs/ui/dataviz/src/index.ts +0 -1
  54. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  55. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
  56. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  57. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  58. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  59. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  60. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +18 -8
  61. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +29 -1
  62. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  63. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  64. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  65. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  66. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  67. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  68. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  69. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  70. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  71. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  72. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  73. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  74. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  75. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  76. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  77. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  78. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  79. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  80. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  81. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  82. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  83. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  84. package/src/libs/util/i18n/src/index.ts +0 -1
  85. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  86. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  87. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  88. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  89. package/src/libs/util/shared/src/lib/links/link-utils.ts +1 -1
  90. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  91. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  92. package/style.css +0 -1
  93. package/tailwind.base.css +8 -0
  94. package/translations/de.json +50 -47
  95. package/translations/en.json +55 -52
  96. package/translations/es.json +24 -21
  97. package/translations/fr.json +50 -47
  98. package/translations/it.json +50 -47
  99. package/translations/nl.json +23 -20
  100. package/translations/pt.json +24 -21
  101. package/translations/sk.json +24 -21
@@ -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>
@@ -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,
@@ -78,11 +84,14 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
78
84
  FormFieldSpatialToggleComponent,
79
85
  FormFieldTopicsComponent,
80
86
  TextFieldModule,
87
+ NgIconComponent,
81
88
  ],
89
+ providers: [provideIcons({ matEditOutline, matHelpOutline })],
82
90
  hostDirectives: [FieldFocusDirective],
83
91
  })
84
92
  export class FormFieldComponent {
85
93
  @Input() uniqueIdentifier: string
94
+ @Input() recordKind: RecordKind
86
95
  @Input() model: CatalogRecordKeys
87
96
  @Input() modelSpecifier: FieldModelSpecifier
88
97
  @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]"
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
  }