geonetwork-ui 2.10.0-dev.8f8114b9e → 2.10.0-dev.981f59a75

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 (118) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1158 -6203
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +164 -168
  4. package/index.d.ts.map +1 -1
  5. package/package.json +25 -35
  6. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +48 -7
  7. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +6 -5
  8. package/src/libs/api/metadata-converter/src/lib/gn4/metadata-url.service.ts +2 -2
  9. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  10. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +5 -1
  11. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +3 -0
  12. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +4 -1
  13. package/src/libs/api/repository/src/lib/gn4/auth/auth.service.ts +4 -4
  14. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +2 -2
  15. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -5
  16. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-groups.service.ts +1 -1
  17. package/src/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.ts +1 -1
  18. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -22
  19. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +2 -5
  20. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  21. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +51 -0
  22. package/src/libs/feature/catalog/src/lib/sources/sources.service.ts +5 -2
  23. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  24. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  25. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  26. package/src/libs/feature/editor/src/lib/components/constraint-card/constraint-card.component.ts +0 -2
  27. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +1 -0
  28. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  29. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  30. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  31. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  32. package/src/libs/feature/editor/src/lib/components/record-form/form-field/field-focus.directive.ts +4 -1
  33. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +7 -7
  34. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  35. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  36. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  37. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  38. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-topics/form-field-topics.component.ts +2 -2
  39. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  40. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  41. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.css +0 -34
  42. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +20 -16
  43. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +9 -0
  44. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +35 -0
  45. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +18 -5
  46. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +76 -19
  47. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  48. package/src/libs/feature/editor/src/lib/fields.config.ts +310 -336
  49. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +2 -3
  50. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  51. package/src/libs/feature/map/src/lib/utils/map-utils.service.ts +33 -0
  52. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  53. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +216 -8
  54. package/src/libs/feature/record/src/lib/map-view/map-view.component.ts +9 -1
  55. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  56. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.ts +2 -2
  57. package/src/libs/ui/dataviz/src/index.ts +0 -1
  58. package/src/libs/ui/elements/src/index.ts +1 -0
  59. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  60. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +7 -3
  61. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  62. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  63. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css +13 -0
  64. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html +13 -3
  65. package/src/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts +43 -6
  66. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  67. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  68. package/src/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.ts +2 -2
  69. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  70. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +5 -8
  71. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  72. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  73. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  74. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  75. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  76. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  77. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +2 -2
  78. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  79. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  80. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  81. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  82. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  83. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  84. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  85. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  86. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  87. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  88. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  89. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +4 -54
  90. package/src/libs/ui/map/src/lib/map-utils.ts +48 -0
  91. package/src/libs/ui/search/src/lib/record-preview-feed/record-preview-feed.component.ts +1 -1
  92. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  93. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  94. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  95. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  96. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  97. package/src/libs/util/app-config/src/lib/app-config.ts +21 -9
  98. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +1 -2
  99. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +6 -3
  100. package/src/libs/util/i18n/src/index.ts +0 -1
  101. package/src/libs/util/i18n/src/lib/i18n.constants.ts +17 -11
  102. package/src/libs/util/i18n/src/lib/i18n.interceptor.ts +2 -2
  103. package/src/libs/util/i18n/src/lib/i18n.providers.ts +14 -17
  104. package/src/libs/util/i18n/src/lib/test.translate.loader.ts +48 -0
  105. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  106. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  107. package/src/libs/util/shared/src/lib/utils/geojson.ts +58 -1
  108. package/style.css +0 -1
  109. package/tailwind.base.css +15 -0
  110. package/translations/de.json +84 -47
  111. package/translations/en.json +89 -52
  112. package/translations/es.json +58 -21
  113. package/translations/fr.json +86 -49
  114. package/translations/it.json +85 -48
  115. package/translations/nl.json +57 -20
  116. package/translations/pt.json +58 -21
  117. package/translations/sk.json +58 -21
  118. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -48,8 +48,7 @@ export interface EditorField extends EditorFieldIdentification {
48
48
  gridColumnSpan?: number
49
49
 
50
50
  // a hidden field won't show but can still be used to modify the record
51
- // FIXME: currently this is redundant with an absence of formFieldConfig but necessary for clarity
52
- hidden?: boolean
51
+ hidden?: boolean | EditorFieldExpression
53
52
 
54
53
  // the result of this expression will replace the field value on save
55
54
  onSaveProcess?: EditorFieldExpression
@@ -58,7 +57,7 @@ export interface EditorField extends EditorFieldIdentification {
58
57
  export interface EditorSection {
59
58
  labelKey?: string
60
59
  descriptionKey?: string
61
- hidden: boolean
60
+ hidden?: boolean | EditorFieldExpression
62
61
  fields: EditorField[]
63
62
  }
64
63
 
@@ -1,4 +1,4 @@
1
- import { Injectable, inject } from '@angular/core'
1
+ import { inject, Injectable } from '@angular/core'
2
2
  import { Observable, switchMap } from 'rxjs'
3
3
  import { map, tap } from 'rxjs/operators'
4
4
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
@@ -16,7 +16,8 @@ export class EditorService {
16
16
  saveRecord(
17
17
  record: CatalogRecord,
18
18
  recordSource: string,
19
- fieldsConfig: EditorConfig
19
+ fieldsConfig: EditorConfig,
20
+ publish: boolean
20
21
  ): Observable<[CatalogRecord, string]> {
21
22
  const savedRecord = { ...record }
22
23
 
@@ -27,22 +28,17 @@ export class EditorService {
27
28
  // run onSave processes
28
29
  for (const field of fields) {
29
30
  if (field.onSaveProcess && field.model) {
30
- const evaluator = evaluate(field.onSaveProcess)
31
+ const { evaluator } = evaluate(field.onSaveProcess)
31
32
  savedRecord[field.model] = evaluator({
32
- model: field.model,
33
- value: record[field.model],
33
+ globals: {
34
+ record,
35
+ },
34
36
  })
35
37
  }
36
38
  }
37
- let publishToAll = true
38
- // if the record is new, generate a new unique identifier and pass publishToAll as false
39
- if (!record.uniqueIdentifier) {
40
- savedRecord.uniqueIdentifier = null
41
- publishToAll = false
42
- }
43
39
 
44
40
  return this.recordsRepository
45
- .saveRecord(savedRecord, recordSource, publishToAll)
41
+ .saveRecord(savedRecord, recordSource, publish)
46
42
  .pipe(
47
43
  switchMap((uniqueIdentifier) =>
48
44
  this.recordsRepository.openRecordForEdition(uniqueIdentifier)
@@ -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
  }
@@ -1 +1,111 @@
1
- @if (record) {}
1
+ <gn-ui-button
2
+ type="outline"
3
+ extraClass="w-full flex flex-row justify-between"
4
+ [style.--gn-ui-button-rounded]="'10px'"
5
+ (buttonClick)="toggleOverlay()"
6
+ >
7
+ <span translate>service.metadata.reuse</span>
8
+ <ng-icon class="mr-[5px]" name="iconoirAppWindow"></ng-icon>
9
+ </gn-ui-button>
10
+ <ng-template #notifyReuseForm>
11
+ <div
12
+ class="w-[400px] max-w-[90vw] flex flex-col gap-3 bg-gray-50 rounded-l overflow-clip px-4 py-5 shadow-[0px_9px_12px_0px_rgba(9,30,66,0.15),0px_0px_1px_0px_rgba(9,30,66,0.31)]"
13
+ >
14
+ <div class="flex flex-col gap-3 pb-3">
15
+ <div class="flex items-start justify-between pb-3">
16
+ <p class="text-xl font-medium text-black" translate>
17
+ notify.reuse.form.title
18
+ </p>
19
+ <gn-ui-button
20
+ type="gray-light"
21
+ class="shrink-0"
22
+ (buttonClick)="closeOverlay()"
23
+ [style.--gn-ui-button-padding]="0"
24
+ [style.--gn-ui-button-width]="'21px'"
25
+ [style.--gn-ui-button-height]="'21px'"
26
+ aria-label="Close"
27
+ >
28
+ <ng-icon
29
+ class="text-[22px] !w-[21px] !h-[21px]"
30
+ name="matCloseOutline"
31
+ ></ng-icon>
32
+ </gn-ui-button>
33
+ </div>
34
+
35
+ <div class="flex flex-col gap-[10px]">
36
+ <div class="flex flex-col gap-2">
37
+ <div class="flex items-center">
38
+ <label class="font-bold text-base text-main leading-6">
39
+ <span translate>notify.reuse.form.field.title</span>
40
+ <span class="text-red-500">*</span>
41
+ </label>
42
+ </div>
43
+ <gn-ui-text-input
44
+ #titleInput
45
+ [(value)]="title"
46
+ [placeholder]="
47
+ 'notify.reuse.form.field.title.placeholder' | translate
48
+ "
49
+ [required]="true"
50
+ ></gn-ui-text-input>
51
+ </div>
52
+
53
+ <div class="flex flex-col gap-2 pt-3">
54
+ <div class="flex items-center">
55
+ <label class="font-bold text-base text-main leading-6">
56
+ <span translate>notify.reuse.form.field.url</span>
57
+ <span class="text-red-500">*</span>
58
+ </label>
59
+ </div>
60
+ <gn-ui-text-input
61
+ #urlInput
62
+ [(value)]="url"
63
+ [type]="'url'"
64
+ [placeholder]="
65
+ 'notify.reuse.form.field.url.placeholder' | translate
66
+ "
67
+ [required]="true"
68
+ ></gn-ui-text-input>
69
+ </div>
70
+
71
+ <div class="flex flex-col gap-2 pt-3">
72
+ <div class="flex items-center">
73
+ <label class="font-bold text-base text-main leading-6">
74
+ <span translate>notify.reuse.form.field.email</span>
75
+ <span class="text-red-500">*</span>
76
+ </label>
77
+ </div>
78
+ <gn-ui-text-input
79
+ #emailInput
80
+ [(value)]="email"
81
+ [type]="'email'"
82
+ [placeholder]="
83
+ 'notify.reuse.form.field.email.placeholder' | translate
84
+ "
85
+ [required]="true"
86
+ ></gn-ui-text-input>
87
+ </div>
88
+ </div>
89
+
90
+ <p class="font-medium text-sm text-gray-700 leading-5" translate>
91
+ notify.reuse.form.required.hint
92
+ </p>
93
+ </div>
94
+
95
+ <div class="flex flex-col">
96
+ <gn-ui-button
97
+ type="primary"
98
+ extraClass="w-full h-8 justify-center gap-2"
99
+ [disabled]="!isFormValid() || loading()"
100
+ (buttonClick)="submit()"
101
+ >
102
+ @if (loading()) {
103
+ <mat-spinner [diameter]="16"></mat-spinner>
104
+ } @else {
105
+ <span translate>notify.reuse.form.submit</span>
106
+ <ng-icon name="iconoirPlusCircle" size="1.5em"></ng-icon>
107
+ }
108
+ </gn-ui-button>
109
+ </div>
110
+ </div>
111
+ </ng-template>
@@ -1,21 +1,229 @@
1
+ import {
2
+ Overlay,
3
+ OverlayModule,
4
+ OverlayRef,
5
+ ScrollStrategyOptions,
6
+ } from '@angular/cdk/overlay'
7
+ import { TemplatePortal } from '@angular/cdk/portal'
8
+ import { CommonModule, LocationStrategy } from '@angular/common'
1
9
  import {
2
10
  ChangeDetectionStrategy,
3
11
  Component,
4
- EventEmitter,
5
- Input,
6
- Output,
12
+ computed,
13
+ inject,
14
+ InjectionToken,
15
+ input,
16
+ OnDestroy,
17
+ signal,
18
+ TemplateRef,
19
+ viewChild,
20
+ ViewContainerRef,
7
21
  } from '@angular/core'
8
- import { ReuseRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
22
+ import { toSignal } from '@angular/core/rxjs-interop'
23
+ import { MatProgressSpinner } from '@angular/material/progress-spinner'
24
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
25
+ import {
26
+ CatalogRecord,
27
+ Individual,
28
+ OnlineLinkResource,
29
+ ReuseRecord,
30
+ } from '../../../../../../libs/common/domain/src/lib/model/record'
31
+ import { PlatformServiceInterface } from '../../../../../../libs/common/domain/src/lib/platform.service.interface'
32
+ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
33
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
34
+ import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
35
+ import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
36
+ import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
37
+ import { matCloseOutline } from '@ng-icons/material-icons/outline'
38
+ import {
39
+ TranslateDirective,
40
+ TranslatePipe,
41
+ TranslateService,
42
+ } from '@ngx-translate/core'
43
+
44
+ marker('notify.reuse.form.error.title')
45
+ marker('notify.reuse.form.error.body')
46
+
47
+ export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
9
48
 
10
49
  @Component({
11
50
  selector: 'gn-ui-notify-reuse-form',
12
51
  standalone: true,
13
- imports: [],
52
+ imports: [
53
+ CommonModule,
54
+ OverlayModule,
55
+ TextInputComponent,
56
+ ButtonComponent,
57
+ TranslatePipe,
58
+ TranslateDirective,
59
+ NgIcon,
60
+ MatProgressSpinner,
61
+ ],
14
62
  templateUrl: './notify-reuse-form.component.html',
15
63
  styleUrl: './notify-reuse-form.component.css',
16
64
  changeDetection: ChangeDetectionStrategy.OnPush,
65
+ viewProviders: [
66
+ provideIcons({
67
+ iconoirAppWindow,
68
+ iconoirPlusCircle,
69
+ matCloseOutline,
70
+ }),
71
+ provideNgIconsConfig({
72
+ size: '1.5em',
73
+ }),
74
+ ],
17
75
  })
18
- export class NotifyReuseFormComponent {
19
- @Input() record: ReuseRecord | null = null
20
- @Output() recordChange = new EventEmitter<ReuseRecord>()
76
+ export class NotifyReuseFormComponent implements OnDestroy {
77
+ private overlay = inject(Overlay)
78
+ private viewContainerRef = inject(ViewContainerRef)
79
+ private scrollStrategies = inject(ScrollStrategyOptions)
80
+ private overlayRef: OverlayRef | null = null
81
+ private recordsRepository = inject(RecordsRepositoryInterface)
82
+ private readonly translate = inject(TranslateService)
83
+ private readonly notificationsService = inject(NotificationsService)
84
+ private readonly platformServiceInterface = inject(PlatformServiceInterface)
85
+ reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
86
+ private locationStrategy = inject(LocationStrategy)
87
+
88
+ me = toSignal(this.platformServiceInterface.getMe())
89
+ initialEmail = computed(() => this.me()?.email)
90
+ templateRef = viewChild<TemplateRef<HTMLElement>>('notifyReuseForm')
91
+ record = input<Partial<CatalogRecord | null>>(null)
92
+
93
+ // TODO: use a form() signal once migrated to Angular 22
94
+ title = signal('')
95
+ url = signal('')
96
+ email = signal(this.initialEmail() ?? '')
97
+
98
+ titleInput = viewChild<TextInputComponent>('titleInput')
99
+ urlInput = viewChild<TextInputComponent>('urlInput')
100
+ emailInput = viewChild<TextInputComponent>('emailInput')
101
+
102
+ loading = signal(false)
103
+
104
+ isFormValid = computed(
105
+ () =>
106
+ this.titleInput().isValid() &&
107
+ this.urlInput().isValid() &&
108
+ this.emailInput().isValid()
109
+ )
110
+
111
+ clearInputs() {
112
+ this.title.set('')
113
+ this.url.set('')
114
+ this.email.set(this.initialEmail() ?? '')
115
+ }
116
+
117
+ toggleOverlay() {
118
+ if (this.overlayRef) {
119
+ this.closeOverlay()
120
+ } else {
121
+ this.openOverlay()
122
+ }
123
+ }
124
+
125
+ openOverlay() {
126
+ const positionStrategy = this.overlay
127
+ .position()
128
+ .global()
129
+ .centerVertically()
130
+ .right('16px')
131
+
132
+ this.overlayRef = this.overlay.create({
133
+ positionStrategy,
134
+ hasBackdrop: false,
135
+ backdropClass: 'cdk-overlay-transparent-backdrop',
136
+ scrollStrategy: this.scrollStrategies.noop(),
137
+ })
138
+
139
+ this.overlayRef.attach(
140
+ new TemplatePortal(this.templateRef(), this.viewContainerRef)
141
+ )
142
+ this.overlayRef.backdropClick().subscribe(() => this.closeOverlay())
143
+ }
144
+
145
+ closeOverlay() {
146
+ this.clearInputs()
147
+ this.overlayRef?.dispose()
148
+ this.overlayRef = null
149
+ }
150
+
151
+ ngOnDestroy() {
152
+ this.closeOverlay()
153
+ }
154
+
155
+ submit() {
156
+ if (!this.isFormValid()) return
157
+ const onlineResource: OnlineLinkResource = {
158
+ type: 'link',
159
+ url: new URL(this.url()),
160
+ name: this.url(),
161
+ description: this.translate.instant('notify.reuse.link.description'),
162
+ }
163
+ const contact: Individual = {
164
+ email: this.email(),
165
+ role: 'point_of_contact',
166
+ }
167
+ const reuseRecord: ReuseRecord = {
168
+ uniqueIdentifier: '',
169
+ kind: 'reuse',
170
+ title: this.title(),
171
+ abstract: '',
172
+ ownerOrganization: { name: '' },
173
+ contacts: [contact],
174
+ contactsForResource: [],
175
+ recordUpdated: new Date(),
176
+ topics: [],
177
+ keywords: [],
178
+ licenses: [],
179
+ legalConstraints: [],
180
+ securityConstraints: [],
181
+ otherConstraints: [],
182
+ overviews: [],
183
+ defaultLanguage: 'en',
184
+ otherLanguages: [],
185
+ lineage: '',
186
+ reuseType: 'application',
187
+ sourceRecords: [
188
+ {
189
+ uuid: this.record()?.uniqueIdentifier ?? '',
190
+ title: this.record()?.title ?? '',
191
+ },
192
+ ],
193
+ onlineResources: [onlineResource],
194
+ spatialExtents: [],
195
+ temporalExtents: [],
196
+ }
197
+ this.loading.set(true)
198
+ this.recordsRepository.saveRecord(reuseRecord, undefined, false).subscribe({
199
+ next: (uniqueIdentifier) => {
200
+ this.loading.set(false)
201
+ this.closeOverlay()
202
+ const baseUrl = `${this.reuseFormUrl ?? ''}`.replace(/\/+$/, '')
203
+ // Normalisation du baseHref pour éviter les doubles /
204
+ const baseHrefClean = this.locationStrategy
205
+ .getBaseHref()
206
+ .replace(/^\/+|\/+$/g, '')
207
+ const prefix = baseHrefClean ? `/${baseHrefClean}` : ''
208
+ const rawRedirectUrl = `${window.location.origin}${prefix}/reuse/${uniqueIdentifier}`
209
+ const redirect_on_leave = encodeURIComponent(rawRedirectUrl)
210
+ window.open(
211
+ `${baseUrl}/edit/${uniqueIdentifier}?redirect_on_leave=${redirect_on_leave}`,
212
+ '_self'
213
+ )
214
+ },
215
+ error: (err) => {
216
+ this.loading.set(false)
217
+ this.notificationsService.showNotification(
218
+ {
219
+ type: 'error',
220
+ title: this.translate.instant('notify.reuse.form.error.title'),
221
+ text: this.translate.instant('notify.reuse.form.error.body'),
222
+ },
223
+ undefined,
224
+ err
225
+ )
226
+ },
227
+ })
228
+ }
21
229
  }
@@ -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,10 +1,10 @@
1
1
  import {
2
2
  Component,
3
3
  EventEmitter,
4
+ inject,
4
5
  Input,
5
6
  OnDestroy,
6
7
  Output,
7
- inject,
8
8
  } from '@angular/core'
9
9
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
10
10
  import { SearchFacade } from '../state/search.facade'
@@ -67,6 +67,7 @@ export class ResultsTableContainerComponent implements OnDestroy {
67
67
 
68
68
  async handleDeleteRecord(item: unknown) {
69
69
  const uniqueIdentifier = (item as CatalogRecord).uniqueIdentifier
70
+ const recordKind = (item as CatalogRecord).kind
70
71
  this.subscription.add(
71
72
  this.recordsRepository.deleteRecord(uniqueIdentifier).subscribe({
72
73
  next: () => {
@@ -76,10 +77,12 @@ export class ResultsTableContainerComponent implements OnDestroy {
76
77
  {
77
78
  type: 'success',
78
79
  title: this.translateService.instant(
79
- 'editor.record.deleteSuccess.title'
80
+ 'editor.record.deleteSuccess.title',
81
+ { recordKind }
80
82
  ),
81
83
  text: `${this.translateService.instant(
82
- 'editor.record.deleteSuccess.body'
84
+ 'editor.record.deleteSuccess.body',
85
+ { recordKind }
83
86
  )}`,
84
87
  },
85
88
  2500
@@ -90,10 +93,12 @@ export class ResultsTableContainerComponent implements OnDestroy {
90
93
  {
91
94
  type: 'error',
92
95
  title: this.translateService.instant(
93
- 'editor.record.deleteError.title'
96
+ 'editor.record.deleteError.title',
97
+ { recordKind }
94
98
  ),
95
99
  text: `${this.translateService.instant(
96
- 'editor.record.deleteError.body'
100
+ 'editor.record.deleteError.body',
101
+ { recordKind }
97
102
  )} ${error}`,
98
103
  closeMessage: this.translateService.instant(
99
104
  'editor.record.deleteError.closeMessage'
@@ -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'
@@ -11,6 +11,7 @@ export * from './lib/image-overlay-preview/image-overlay-preview.component'
11
11
  export * from './lib/kind-badge/kind-badge.component'
12
12
  export * from './lib/external-link-card/external-link-card.component'
13
13
  export * from './lib/markdown-editor/markdown-editor.component'
14
+ export * from './lib/keyword-badge/keyword-badge.component'
14
15
  export * from './lib/markdown-parser/markdown-parser.component'
15
16
  export * from './lib/metadata-catalog/metadata-catalog.component'
16
17
  export * from './lib/metadata-contact/metadata-contact.component'