geonetwork-ui 2.10.0-dev.b6696be21 → 2.10.0-dev.c12b52b04

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 (73) hide show
  1. package/fesm2022/geonetwork-ui.mjs +350 -5749
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +89 -60
  4. package/index.d.ts.map +1 -1
  5. package/material-styles.css +483 -0
  6. package/package.json +23 -33
  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/organizations/organizations-from-groups.service.ts +1 -1
  14. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  15. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +2 -2
  16. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -0
  17. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  18. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  19. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  20. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  21. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  22. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  23. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +6 -7
  24. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  25. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  26. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  27. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +10 -9
  28. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +7 -0
  29. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  30. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +5 -1
  31. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +35 -19
  32. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  33. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  34. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  35. package/src/libs/ui/dataviz/src/index.ts +0 -1
  36. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  37. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  38. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  39. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  40. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  41. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +17 -7
  42. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +29 -1
  43. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  44. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  45. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  46. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  47. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  48. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  49. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  50. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  51. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  52. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  53. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  54. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  55. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  56. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  57. package/src/libs/util/i18n/src/index.ts +0 -1
  58. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  59. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  60. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  61. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  62. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  63. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  64. package/style.css +0 -1
  65. package/tailwind.base.css +2 -0
  66. package/translations/de.json +4 -0
  67. package/translations/en.json +4 -0
  68. package/translations/es.json +4 -0
  69. package/translations/fr.json +4 -0
  70. package/translations/it.json +4 -0
  71. package/translations/nl.json +4 -0
  72. package/translations/pt.json +4 -0
  73. package/translations/sk.json +4 -0
@@ -62,6 +62,7 @@ type ThesaurusName = string
62
62
  export interface Thesaurus {
63
63
  id?: string
64
64
  title?: string
65
+ multilingualTitle?: MultilingualField
65
66
  theme?: string
66
67
  link?: string
67
68
  keywords: MultilingualField[]
@@ -1,4 +1,4 @@
1
- import { Injectable, InjectionToken, inject } from '@angular/core'
1
+ import { inject, Injectable, InjectionToken } from '@angular/core'
2
2
  import { toLang2, toLang3 } from '../../../../../../../libs/util/i18n/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { Location } from '@angular/common'
@@ -48,8 +48,8 @@ export class AuthService {
48
48
  '${current_path}',
49
49
  this.location.prepareExternalUrl(this.location.path())
50
50
  )
51
- .replace('${lang2}', toLang2(this.translateService.currentLang))
52
- .replace('${lang3}', toLang3(this.translateService.currentLang))
51
+ .replace('${lang2}', toLang2(this.translateService.getCurrentLang()))
52
+ .replace('${lang3}', toLang3(this.translateService.getCurrentLang()))
53
53
  }
54
54
 
55
55
  get logoutUrl() {
@@ -59,7 +59,7 @@ export class AuthService {
59
59
  get settingsUrl() {
60
60
  return this.baseSettingsUrl.replace(
61
61
  '${lang3}',
62
- toLang3(this.translateService.currentLang)
62
+ toLang3(this.translateService.getCurrentLang())
63
63
  )
64
64
  }
65
65
  }
@@ -1,4 +1,4 @@
1
- import { Injectable, Injector, inject } from '@angular/core'
1
+ import { inject, Injectable, Injector } from '@angular/core'
2
2
  import type { Geometry } from 'geojson'
3
3
  import {
4
4
  ES_QUERY_FIELDS_PRIORITY,
@@ -234,7 +234,7 @@ export class ElasticsearchService {
234
234
  if (this.metadataLang) {
235
235
  const lang3 = toLang3(
236
236
  this.isCurrentSearchLang()
237
- ? this.translateService.currentLang
237
+ ? this.translateService.getCurrentLang()
238
238
  : this.metadataLang
239
239
  )
240
240
  return `lang${lang3}`
@@ -52,7 +52,7 @@ export class OrganizationsFromGroupsService
52
52
  organisationsCount$ = this.organisations$.pipe(map((orgs) => orgs.length))
53
53
 
54
54
  private get lang3() {
55
- return toLang3(this.translateService.currentLang)
55
+ return toLang3(this.translateService.getCurrentLang())
56
56
  }
57
57
 
58
58
  constructor(
@@ -128,7 +128,7 @@ export class OrganizationsFromMetadataService
128
128
  ) {}
129
129
 
130
130
  private get langIndex() {
131
- return `lang${toLang3(this.translateService.currentLang)}`
131
+ return `lang${toLang3(this.translateService.getCurrentLang())}`
132
132
  }
133
133
 
134
134
  equalsNormalizedStrings(
@@ -1,5 +1,5 @@
1
1
  import { HttpClient, HttpEventType } from '@angular/common/http'
2
- import { Injectable, InjectionToken, inject } from '@angular/core'
2
+ import { inject, Injectable, InjectionToken } from '@angular/core'
3
3
  import {
4
4
  KeywordApiResponse,
5
5
  ThesaurusApiResponse,
@@ -100,7 +100,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
100
100
  private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
101
101
 
102
102
  private get lang3() {
103
- return toLang3(this.translateService.currentLang)
103
+ return toLang3(this.translateService.getCurrentLang())
104
104
  }
105
105
 
106
106
  constructor() {
@@ -104,6 +104,8 @@ export interface Keyword {
104
104
  type: KeywordType
105
105
  thesaurus?: ThesaurusModel
106
106
  bbox?: [number, number, number, number]
107
+ // thesaurus hierarchy path incl. the keyword as last element; unset for root-level keywords
108
+ hierarchyPath?: string[]
107
109
 
108
110
  translations?: KeywordTranslations
109
111
  }
@@ -1,7 +1,8 @@
1
1
  export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
2
2
 
3
3
  export interface ThesaurusModel {
4
- id: string
4
+ // absent when the source provides no identifier for the thesaurus
5
+ id?: string
5
6
  name?: string
6
7
  url?: URL
7
8
  thesaurusKey?: string
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import { SourcesApiService } from '../../../../../../libs/data-access/gn4/src'
3
3
  import { Observable } from 'rxjs'
4
4
  import { filter, map, shareReplay } from 'rxjs/operators'
@@ -21,7 +21,10 @@ export class SourcesService {
21
21
  return this.sources$.pipe(
22
22
  map((sources) => sources.filter((source) => source.uuid === uuid)[0]),
23
23
  filter((source) => !!source),
24
- map((source) => source.label[toLang3(this.translateService.currentLang)])
24
+ map(
25
+ (source) =>
26
+ source.label[toLang3(this.translateService.getCurrentLang())]
27
+ )
25
28
  )
26
29
  }
27
30
  }
@@ -8,7 +8,6 @@ import {
8
8
 
9
9
  import { FormFieldRichComponent } from '../record-form/form-field/form-field-rich/form-field-rich.component'
10
10
  import { ButtonComponent, UrlInputComponent } from '../../../../../../../libs/ui/inputs/src'
11
- import { MatIconModule } from '@angular/material/icon'
12
11
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
13
12
  import { Constraint } from '../../../../../../../libs/common/domain/src/lib/model/record'
14
13
  import {
@@ -26,7 +25,6 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
26
25
  FormFieldRichComponent,
27
26
  UrlInputComponent,
28
27
  ButtonComponent,
29
- MatIconModule,
30
28
  TranslateDirective,
31
29
  TranslatePipe,
32
30
  NgIconComponent,
@@ -3,13 +3,13 @@
3
3
  editor.record.form.field.onlineResource.edit.protocol
4
4
  </h3>
5
5
  @if (protocolHint) {
6
- <span
7
- class="material-symbols-outlined m-2 gn-ui-icon-small"
6
+ <ng-icon
7
+ name="matHelpOutline"
8
+ size="16"
9
+ class="m-2"
8
10
  [matTooltip]="protocolHint"
9
11
  matTooltipPosition="above"
10
- >
11
- help
12
- </span>
12
+ ></ng-icon>
13
13
  }
14
14
  </div>
15
15
  <div class="flex flex-row items-center gap-4 h-[48px]">
@@ -30,6 +30,7 @@ import {
30
30
  provideNgIconsConfig,
31
31
  } from '@ng-icons/core'
32
32
  import { iconoirRefresh } from '@ng-icons/iconoir'
33
+ import { matHelpOutline } from '@ng-icons/material-icons/outline'
33
34
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
34
35
  import {
35
36
  BehaviorSubject,
@@ -65,7 +66,7 @@ marker(
65
66
  UrlInputComponent,
66
67
  ],
67
68
  providers: [
68
- provideIcons({ iconoirRefresh }),
69
+ provideIcons({ iconoirRefresh, matHelpOutline }),
69
70
  provideNgIconsConfig({
70
71
  size: '1.5em',
71
72
  }),
@@ -10,7 +10,7 @@ export class FieldFocusDirective {
10
10
 
11
11
  private el = inject(ElementRef)
12
12
 
13
- public focusField() {
13
+ public focusField(focusInnerTarget = true) {
14
14
  setTimeout(() => {
15
15
  const host = this.el.nativeElement as HTMLElement
16
16
  const glowClass = this.gnUiFieldFocusGlowClass
@@ -25,6 +25,9 @@ export class FieldFocusDirective {
25
25
  )
26
26
 
27
27
  host.scrollIntoView({ behavior: 'smooth', block: 'start' })
28
+ if (!focusInnerTarget) {
29
+ return
30
+ }
28
31
  const target =
29
32
  host.querySelector<HTMLElement>(
30
33
  'input, textarea, select, [contenteditable="true"]'
@@ -131,13 +131,12 @@ export class FormFieldConstraintsShortcutsComponent implements OnDestroy {
131
131
  map((c) => c.length > 0),
132
132
  distinctUntilChanged()
133
133
  )
134
- combineLatest([
135
- isConstraintNotEmpty$,
136
- this.anyToggleActivated$,
137
- ]).subscribe(([isNotEmpty, anyToggleActivated]) => {
138
- const visible = isNotEmpty && !anyToggleActivated
139
- this.editorFacade.setFieldVisibility({ model }, visible)
140
- })
134
+ combineLatest([isConstraintNotEmpty$, this.anyToggleActivated$])
135
+ .pipe(takeUntil(this.onDestroy$))
136
+ .subscribe(([isNotEmpty, anyToggleActivated]) => {
137
+ const visible = isNotEmpty && !anyToggleActivated
138
+ this.editorFacade.setFieldVisibility({ model }, visible)
139
+ })
141
140
  }
142
141
  hideEmptyConstraints(this.securityConstraints$, 'securityConstraints')
143
142
  hideEmptyConstraints(this.otherConstraints$, 'otherConstraints')
@@ -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
@@ -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
- }
@@ -38,19 +38,20 @@
38
38
  "
39
39
  ></textarea>
40
40
  <div class="flex flex-row justify-between self-start mt-0.5">
41
- <span
42
- class="material-symbols-outlined gn-ui-icon-small m-2 cursor-pointer"
41
+ <ng-icon
42
+ name="matEditOutline"
43
+ size="16"
44
+ class="m-2 cursor-pointer"
43
45
  (click)="focusTitleInput()"
44
- >edit</span
45
- >
46
- <span
46
+ ></ng-icon>
47
+ <ng-icon
47
48
  *ngIf="config.hintKey"
48
- class="material-symbols-outlined gn-ui-icon-small m-2"
49
+ name="matHelpOutline"
50
+ size="16"
51
+ class="m-2"
49
52
  [matTooltip]="config.hintKey! | translate"
50
53
  matTooltipPosition="above"
51
- >
52
- help
53
- </span>
54
+ ></ng-icon>
54
55
  </div>
55
56
  </div>
56
57
  </ng-container>
@@ -21,6 +21,11 @@ import {
21
21
  UpdateFrequency,
22
22
  } from '../../../../../../../../libs/common/domain/src/lib/model/record'
23
23
  import { FormFieldWrapperComponent } from '../../../../../../../../libs/ui/layout/src'
24
+ import { NgIconComponent, provideIcons } from '@ng-icons/core'
25
+ import {
26
+ matEditOutline,
27
+ matHelpOutline,
28
+ } from '@ng-icons/material-icons/outline'
24
29
  import { TranslatePipe } from '@ngx-translate/core'
25
30
  import {
26
31
  FieldFocusDirective,
@@ -78,7 +83,9 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
78
83
  FormFieldSpatialToggleComponent,
79
84
  FormFieldTopicsComponent,
80
85
  TextFieldModule,
86
+ NgIconComponent,
81
87
  ],
88
+ providers: [provideIcons({ matEditOutline, matHelpOutline })],
82
89
  hostDirectives: [FieldFocusDirective],
83
90
  })
84
91
  export class FormFieldComponent {
@@ -0,0 +1,35 @@
1
+ /* Glow applied by FieldFocusDirective (gnUiFieldFocus) to a focused field or,
2
+ when the field is hidden, to its whole section. */
3
+ .gn-ui-field-focus-glow {
4
+ position: relative;
5
+ }
6
+
7
+ .gn-ui-field-focus-glow::after {
8
+ content: '';
9
+ position: absolute;
10
+ inset: -8px;
11
+ border-radius: 8px;
12
+ background-color: color-mix(in srgb, var(--color-primary) 12%, transparent);
13
+ pointer-events: none;
14
+ animation: gn-ui-field-focus-glow 3s ease-out forwards;
15
+ }
16
+
17
+ .gn-ui-field-focus-glow::before {
18
+ content: '';
19
+ position: absolute;
20
+ inset: -16px;
21
+ border: 2px dashed var(--color-primary);
22
+ border-radius: 10px;
23
+ pointer-events: none;
24
+ animation: gn-ui-field-focus-glow 3s ease-out forwards;
25
+ }
26
+
27
+ @keyframes gn-ui-field-focus-glow {
28
+ 0%,
29
+ 55% {
30
+ opacity: 1;
31
+ }
32
+ 100% {
33
+ opacity: 0;
34
+ }
35
+ }
@@ -4,7 +4,11 @@
4
4
  track sectionTracker($index, section)
5
5
  ) {
6
6
  @if (!section.hidden) {
7
- <div class="flex flex-col gap-6 border p-8 rounded-[8px] shadow">
7
+ <div
8
+ class="flex flex-col gap-6 border p-8 rounded-[8px] shadow scroll-mt-[90px]"
9
+ gnUiFieldFocus
10
+ #sectionFocus="fieldFocus"
11
+ >
8
12
  <div class="flex flex-col gap-2">
9
13
  @if (section.labelKey) {
10
14
  <div class="text-2xl font-title text-black" translate>
@@ -9,7 +9,7 @@ import {
9
9
  } from '@angular/core'
10
10
  import { EditorFacade } from '../../+state/editor.facade'
11
11
  import { EditorFieldValue } from '../../models'
12
- import { FormFieldComponent } from './form-field'
12
+ import { FieldFocusDirective, FormFieldComponent } from './form-field'
13
13
  import { TranslateDirective } from '@ngx-translate/core'
14
14
  import {
15
15
  EditorFieldWithValue,
@@ -25,7 +25,12 @@ import { switchMap } from 'rxjs/operators'
25
25
  styleUrls: ['./record-form.component.css'],
26
26
  changeDetection: ChangeDetectionStrategy.OnPush,
27
27
  standalone: true,
28
- imports: [CommonModule, FormFieldComponent, TranslateDirective],
28
+ imports: [
29
+ CommonModule,
30
+ FormFieldComponent,
31
+ FieldFocusDirective,
32
+ TranslateDirective,
33
+ ],
29
34
  })
30
35
  export class RecordFormComponent implements OnInit, OnDestroy {
31
36
  facade = inject(EditorFacade)
@@ -36,17 +41,22 @@ export class RecordFormComponent implements OnInit, OnDestroy {
36
41
  )
37
42
 
38
43
  focusFieldWithPage$ = this.facade.focusedField$.pipe(
39
- switchMap(
40
- async (field) => [field, await this.getPageIndexForField(field)] as const
41
- )
44
+ switchMap(async (field) => {
45
+ const location = await this.getFieldLocation(field)
46
+ return [field, location?.page ?? null, location?.section ?? -1] as const
47
+ })
42
48
  )
43
49
 
44
50
  formFields = viewChildren(FormFieldComponent)
51
+ sectionFocusDirectives = viewChildren<FieldFocusDirective>('sectionFocus')
45
52
 
46
- focusField(model: CatalogRecordKeys) {
47
- const fields = this.formFields()
48
- const field = fields.find((f) => f.model === model)
49
- field?.fieldFocus.focusField()
53
+ focusField(model: CatalogRecordKeys, sectionIndex: number) {
54
+ const field = this.formFields().find((f) => f.model === model)
55
+ if (field) {
56
+ field.fieldFocus.focusField()
57
+ return
58
+ }
59
+ this.sectionFocusDirectives()[sectionIndex]?.focusField(false)
50
60
  }
51
61
 
52
62
  ngOnInit() {
@@ -55,15 +65,15 @@ export class RecordFormComponent implements OnInit, OnDestroy {
55
65
  .pipe(
56
66
  withLatestFrom(
57
67
  this.facade.currentPage$,
58
- ([field, fieldPage], currentPage) =>
59
- [field, fieldPage, currentPage] as const
68
+ ([field, fieldPage, fieldSection], currentPage) =>
69
+ [field, fieldPage, fieldSection, currentPage] as const
60
70
  )
61
71
  )
62
- .subscribe(([field, fieldPage, currentPage]) => {
72
+ .subscribe(([field, fieldPage, fieldSection, currentPage]) => {
63
73
  if (fieldPage !== null && fieldPage !== currentPage) {
64
74
  this.facade.setCurrentPage(fieldPage)
65
75
  }
66
- setTimeout(() => this.focusField(field))
76
+ setTimeout(() => this.focusField(field, fieldSection))
67
77
  })
68
78
  )
69
79
  }
@@ -87,13 +97,19 @@ export class RecordFormComponent implements OnInit, OnDestroy {
87
97
  return section.labelKey
88
98
  }
89
99
 
90
- async getPageIndexForField(model: CatalogRecordKeys): Promise<number | null> {
100
+ async getFieldLocation(
101
+ model: CatalogRecordKeys
102
+ ): Promise<{ page: number; section: number } | null> {
91
103
  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
- )
104
+ const page = config.pages.findIndex((p) =>
105
+ p.sections.some((s) => s.fields.some((f) => f.model === model))
96
106
  )
97
- return pageIndex >= 0 ? pageIndex : null
107
+ if (page < 0) {
108
+ return null
109
+ }
110
+ const section = config.pages[page].sections
111
+ .filter((s) => !s.hidden)
112
+ .findIndex((s) => s.fields.some((f) => f.model === model))
113
+ return { page, section }
98
114
  }
99
115
  }
@@ -2,6 +2,23 @@ import { Injectable } from '@angular/core'
2
2
  import { extend } from 'ol/extent.js'
3
3
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
4
4
  import { BoundingBox, getGeometryBoundingBox } from '../../../../../../libs/util/shared/src'
5
+ import { MapContextLayer } from '@geospatial-sdk/core'
6
+ import {
7
+ createSpatialExtentLayer,
8
+ SpatialExtentLayerStyle,
9
+ } from '../../../../../../libs/ui/map/src'
10
+
11
+ /**
12
+ * Style of the extent overlay drawn on top of the previewed data: a dashed
13
+ * black outline over a very light fill, so the extent stays readable without
14
+ * hiding the data underneath.
15
+ */
16
+ const RECORD_EXTENT_OVERLAY_STYLE: SpatialExtentLayerStyle = {
17
+ 'stroke-color': 'rgba(0, 0, 0, 0.6)',
18
+ 'stroke-width': 2,
19
+ 'stroke-line-dash': [8, 6],
20
+ 'fill-color': 'rgba(0, 0, 0, 0.03)',
21
+ }
5
22
 
6
23
  @Injectable({
7
24
  providedIn: 'root',
@@ -26,4 +43,20 @@ export class MapUtilsService {
26
43
  [Infinity, Infinity, -Infinity, -Infinity]
27
44
  )
28
45
  }
46
+
47
+ /**
48
+ * Builds a non-interactive overlay layer drawing the spatial extent(s)
49
+ * declared in the record's metadata (bounding boxes and/or geometries).
50
+ * Returns null when the record has no usable spatial extent.
51
+ *
52
+ * This is purely for display and is independent from the map's initial view,
53
+ * which is derived separately (see {@link getRecordExtent}).
54
+ */
55
+ getRecordExtentLayer(record: Partial<CatalogRecord>): MapContextLayer | null {
56
+ return createSpatialExtentLayer(record.spatialExtents ?? [], {
57
+ label: 'Spatial extent',
58
+ clickable: false,
59
+ style: RECORD_EXTENT_OVERLAY_STYLE,
60
+ })
61
+ }
29
62
  }
@@ -431,11 +431,19 @@ export class MapViewComponent implements AfterViewInit {
431
431
  }),
432
432
  withLatestFrom(this.mdViewFacade.metadata$),
433
433
  map(([context, metadata]) => {
434
- if (context.view) return context
434
+ // overlay the record's declared spatial extent on top of the data layers
435
+ const extentLayer = this.mapUtils.getRecordExtentLayer(metadata)
436
+ const layers = extentLayer
437
+ ? [...context.layers, extentLayer]
438
+ : context.layers
439
+ // the view (initial zoom) is derived from the data layer or, as a
440
+ // fallback, from the record extent — independently from the overlay above
441
+ if (context.view) return { ...context, layers }
435
442
  const extent = this.mapUtils.getRecordExtent(metadata)
436
443
  const view = extent ? { extent } : null
437
444
  return {
438
445
  ...context,
446
+ layers,
439
447
  view,
440
448
  }
441
449
  }),
@@ -1,4 +1,4 @@
1
- import { Component, InjectionToken, inject } from '@angular/core'
1
+ import { Component, inject, InjectionToken } from '@angular/core'
2
2
  import { LANGUAGE_STORAGE_KEY } from '../../../../../../libs/util/i18n/src'
3
3
  import { TranslateService } from '@ngx-translate/core'
4
4
  import { DropdownSelectorComponent } from '../../../../../../libs/ui/inputs/src'
@@ -26,7 +26,7 @@ export class LanguageSwitcherComponent {
26
26
  )
27
27
 
28
28
  get currentLang() {
29
- return this.translate.currentLang
29
+ return this.translate.getCurrentLang()
30
30
  }
31
31
 
32
32
  changeLanguage(value: unknown) {
@@ -1,4 +1,3 @@
1
1
  export * from './lib/chart/chart.component'
2
2
  export * from './lib/data-table/data-table.component'
3
- export * from './lib/data-table/data-table.fixtures'
4
3
  export * from './lib/figure/figure.component'