geonetwork-ui 2.10.0-dev.fb2893f44 → 2.10.0

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 (109) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1017 -468
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +112 -111
  4. package/index.d.ts.map +1 -1
  5. package/package.json +1 -1
  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 +4 -3
  23. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  24. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +4 -0
  25. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +13 -0
  26. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +45 -1
  27. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -1
  28. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +40 -0
  29. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +47 -1
  30. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +12 -8
  31. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +9 -20
  32. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  33. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +2 -1
  34. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  35. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +57 -0
  36. package/src/libs/feature/editor/src/index.ts +1 -0
  37. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +4 -1
  38. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +26 -19
  39. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +5 -1
  40. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +7 -6
  41. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +10 -8
  42. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.html +5 -14
  43. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.ts +3 -28
  44. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.html +5 -5
  45. package/src/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts +2 -1
  46. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +1 -0
  47. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts +1 -1
  48. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts +12 -5
  49. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.html +4 -4
  50. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-rich/form-field-rich.component.ts +7 -0
  51. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.html +6 -2
  52. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-update-frequency/form-field-update-frequency.component.ts +3 -1
  53. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +27 -16
  54. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -0
  55. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +14 -5
  56. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +41 -0
  57. package/src/libs/feature/editor/src/lib/expressions.ts +380 -8
  58. package/src/libs/feature/editor/src/lib/fields.config.ts +295 -291
  59. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +6 -3
  60. package/src/libs/feature/editor/src/lib/services/editor.service.ts +8 -12
  61. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  62. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  63. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  64. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  65. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  66. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +219 -8
  67. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  68. package/src/libs/feature/search/src/lib/results-table/results-table-container.component.ts +10 -5
  69. package/src/libs/ui/elements/src/index.ts +1 -0
  70. package/src/libs/ui/elements/src/lib/contact-details/contact-details.component.html +48 -56
  71. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.html +2 -1
  72. package/src/libs/ui/elements/src/lib/external-link-card/external-link-card.component.ts +2 -2
  73. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.html +14 -0
  74. package/src/libs/ui/elements/src/lib/keyword-badge/keyword-badge.component.ts +85 -0
  75. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +5 -0
  76. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +4 -7
  77. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +2 -2
  78. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.html +4 -5
  79. package/src/libs/ui/elements/src/lib/record-api-form/record-api-form.component.ts +4 -0
  80. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +33 -26
  81. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +9 -0
  82. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  83. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +3 -3
  84. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  85. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  86. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  87. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.html +5 -5
  88. package/src/libs/ui/layout/src/lib/form-field-wrapper/form-field-wrapper.component.ts +9 -1
  89. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.html +1 -1
  90. package/src/libs/ui/layout/src/lib/interactive-table/interactive-table.component.ts +3 -8
  91. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.html +7 -7
  92. package/src/libs/ui/layout/src/lib/sortable-list/sortable-list.component.ts +14 -1
  93. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +12 -8
  94. package/src/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +3 -1
  95. package/src/libs/ui/search/src/lib/results-table/results-table.component.html +1 -0
  96. package/src/libs/ui/widgets/src/lib/popover/popover.component.ts +7 -1
  97. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  98. package/src/libs/util/shared/src/lib/links/link-utils.ts +22 -0
  99. package/tailwind.base.css +9 -2
  100. package/translations/de.json +84 -31
  101. package/translations/en.json +94 -41
  102. package/translations/es.json +58 -5
  103. package/translations/fr.json +91 -38
  104. package/translations/it.json +85 -32
  105. package/translations/nl.json +57 -4
  106. package/translations/pt.json +58 -5
  107. package/translations/sk.json +58 -5
  108. package/material-styles.css +0 -483
  109. package/src/libs/feature/editor/src/lib/components/generic-keywords/generic-keywords.component.css +0 -3
@@ -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)
@@ -1 +1,2 @@
1
+ export * from './lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component'
1
2
  export * from './lib/notify-reuse-form/notify-reuse-form.component'
@@ -0,0 +1,23 @@
1
+ <div class="flex flex-row gap-6">
2
+ <gn-ui-button
3
+ class="grow"
4
+ type="primary"
5
+ extraClass="w-full flex flex-row justify-between"
6
+ [style.--gn-ui-button-rounded]="'10px'"
7
+ (buttonClick)="editReuse()"
8
+ data-cy="editReuseButton"
9
+ >
10
+ <span translate>notify.reuse.record.action.edit</span>
11
+ <ng-icon class="mr-[5px]" name="iconoirEdit"></ng-icon>
12
+ </gn-ui-button>
13
+ <gn-ui-button
14
+ type="outline"
15
+ [style.--gn-ui-button-rounded]="'10px'"
16
+ [style.--gn-ui-button-height]="'50px'"
17
+ [title]="'notify.reuse.record.action.delete' | translate"
18
+ (buttonClick)="deleteReuse()"
19
+ data-cy="deleteReuseButton"
20
+ >
21
+ <ng-icon name="iconoirTrash"></ng-icon>
22
+ </gn-ui-button>
23
+ </div>
@@ -0,0 +1,114 @@
1
+ import { LocationStrategy } from '@angular/common'
2
+ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
3
+ import { MatDialog } from '@angular/material/dialog'
4
+ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
5
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
6
+ import { MdViewFacade } from '../../../../../../libs/feature/record/src'
7
+ import { RouterFacade } from '../../../../../../libs/feature/router/src'
8
+ import { ConfirmationDialogComponent } from '../../../../../../libs/ui/elements/src'
9
+ import { ButtonComponent } from '../../../../../../libs/ui/inputs/src'
10
+ import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
11
+ import { iconoirEdit, iconoirTrash } from '@ng-icons/iconoir'
12
+ import {
13
+ TranslateDirective,
14
+ TranslatePipe,
15
+ TranslateService,
16
+ } from '@ngx-translate/core'
17
+ import { filter, map, take } from 'rxjs/operators'
18
+ import { REUSE_FORM_URL } from '../../index'
19
+ import { navigateToLightEdit } from '../utils/url'
20
+
21
+ @Component({
22
+ selector: 'gn-ui-edit-delete-reuse-buttons',
23
+ templateUrl: './edit-delete-reuse-buttons.component.html',
24
+ styleUrl: './edit-delete-reuse-buttons.component.css',
25
+ changeDetection: ChangeDetectionStrategy.OnPush,
26
+ standalone: true,
27
+ imports: [ButtonComponent, TranslatePipe, TranslateDirective, NgIcon],
28
+ viewProviders: [
29
+ provideIcons({
30
+ iconoirEdit,
31
+ iconoirTrash,
32
+ }),
33
+ provideNgIconsConfig({
34
+ size: '1.5em',
35
+ }),
36
+ ],
37
+ })
38
+ export class EditDeleteReuseButtonsComponent {
39
+ metadataViewFacade = inject(MdViewFacade)
40
+ private recordsRepository = inject(RecordsRepositoryInterface)
41
+ private notificationsService = inject(NotificationsService)
42
+ private routerFacade = inject(RouterFacade)
43
+ private translateService = inject(TranslateService)
44
+ private dialog = inject(MatDialog)
45
+ reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
46
+ private locationStrategy = inject(LocationStrategy)
47
+
48
+ metadataUuid$ = this.metadataViewFacade.metadata$.pipe(
49
+ map((record) => record?.uniqueIdentifier),
50
+ filter(Boolean)
51
+ )
52
+
53
+ editReuse() {
54
+ this.metadataUuid$.pipe(take(1)).subscribe((uniqueIdentifier) => {
55
+ navigateToLightEdit(
56
+ this.reuseFormUrl,
57
+ this.locationStrategy.getBaseHref(),
58
+ uniqueIdentifier
59
+ )
60
+ })
61
+ }
62
+
63
+ deleteReuse() {
64
+ const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
65
+ data: {
66
+ title: this.translateService.instant(
67
+ 'record.reuse.delete.confirmation.title'
68
+ ),
69
+ message: this.translateService.instant(
70
+ 'record.reuse.delete.confirmation.message'
71
+ ),
72
+ confirmText: this.translateService.instant(
73
+ 'record.reuse.delete.confirmation.confirmText'
74
+ ),
75
+ cancelText: this.translateService.instant(
76
+ 'record.reuse.delete.confirmation.cancelText'
77
+ ),
78
+ focusCancel: true,
79
+ },
80
+ restoreFocus: true,
81
+ })
82
+
83
+ dialogRef.afterClosed().subscribe((confirmed) => {
84
+ if (confirmed) {
85
+ this.performReuseDeletion()
86
+ }
87
+ })
88
+ }
89
+
90
+ private performReuseDeletion() {
91
+ this.metadataUuid$.pipe(take(1)).subscribe((uuid) =>
92
+ this.recordsRepository.deleteRecord(uuid).subscribe({
93
+ next: () => {
94
+ this.routerFacade.setSearch()
95
+ },
96
+ error: (error) => {
97
+ this.notificationsService.showNotification(
98
+ {
99
+ type: 'error',
100
+ title: this.translateService.instant(
101
+ 'record.reuse.deleteError.title'
102
+ ),
103
+ text: `${this.translateService.instant(
104
+ 'record.reuse.deleteError.body'
105
+ )} ${error}`,
106
+ },
107
+ undefined,
108
+ error
109
+ )
110
+ },
111
+ })
112
+ )
113
+ }
114
+ }
@@ -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>notify.reuse.record.action.declare</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,232 @@
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 { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
34
+ import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
35
+ import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
36
+ import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
37
+ import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
38
+ import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
39
+ import { matCloseOutline } from '@ng-icons/material-icons/outline'
40
+ import {
41
+ TranslateDirective,
42
+ TranslatePipe,
43
+ TranslateService,
44
+ } from '@ngx-translate/core'
45
+ import { navigateToLightEdit } from '../utils/url'
46
+
47
+ marker('notify.reuse.form.error.title')
48
+ marker('notify.reuse.form.error.body')
49
+
50
+ export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
9
51
 
10
52
  @Component({
11
53
  selector: 'gn-ui-notify-reuse-form',
12
54
  standalone: true,
13
- imports: [],
55
+ imports: [
56
+ CommonModule,
57
+ OverlayModule,
58
+ TextInputComponent,
59
+ ButtonComponent,
60
+ TranslatePipe,
61
+ TranslateDirective,
62
+ NgIcon,
63
+ MatProgressSpinner,
64
+ ],
14
65
  templateUrl: './notify-reuse-form.component.html',
15
66
  styleUrl: './notify-reuse-form.component.css',
16
67
  changeDetection: ChangeDetectionStrategy.OnPush,
68
+ viewProviders: [
69
+ provideIcons({
70
+ iconoirAppWindow,
71
+ iconoirPlusCircle,
72
+ matCloseOutline,
73
+ }),
74
+ provideNgIconsConfig({
75
+ size: '1.5em',
76
+ }),
77
+ ],
17
78
  })
18
- export class NotifyReuseFormComponent {
19
- @Input() record: ReuseRecord | null = null
20
- @Output() recordChange = new EventEmitter<ReuseRecord>()
79
+ export class NotifyReuseFormComponent implements OnDestroy {
80
+ private overlay = inject(Overlay)
81
+ private viewContainerRef = inject(ViewContainerRef)
82
+ private scrollStrategies = inject(ScrollStrategyOptions)
83
+ private overlayRef: OverlayRef | null = null
84
+ private recordsRepository = inject(RecordsRepositoryInterface)
85
+ private readonly translate = inject(TranslateService)
86
+ private readonly notificationsService = inject(NotificationsService)
87
+ private readonly platformServiceInterface = inject(PlatformServiceInterface)
88
+ reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
89
+ private locationStrategy = inject(LocationStrategy)
90
+
91
+ me = toSignal(this.platformServiceInterface.getMe())
92
+ initialEmail = computed(() => this.me()?.email)
93
+ templateRef = viewChild<TemplateRef<HTMLElement>>('notifyReuseForm')
94
+ record = input<Partial<CatalogRecord | null>>(null)
95
+
96
+ // TODO: use a form() signal once migrated to Angular 22
97
+ title = signal('')
98
+ url = signal('')
99
+ email = signal(this.initialEmail() ?? '')
100
+
101
+ titleInput = viewChild<TextInputComponent>('titleInput')
102
+ urlInput = viewChild<TextInputComponent>('urlInput')
103
+ emailInput = viewChild<TextInputComponent>('emailInput')
104
+
105
+ loading = signal(false)
106
+
107
+ isFormValid = computed(
108
+ () =>
109
+ this.titleInput().isValid() &&
110
+ this.urlInput().isValid() &&
111
+ this.emailInput().isValid()
112
+ )
113
+
114
+ clearInputs() {
115
+ this.title.set('')
116
+ this.url.set('')
117
+ this.email.set(this.initialEmail() ?? '')
118
+ }
119
+
120
+ toggleOverlay() {
121
+ if (this.overlayRef) {
122
+ this.closeOverlay()
123
+ } else {
124
+ this.openOverlay()
125
+ }
126
+ }
127
+
128
+ openOverlay() {
129
+ const positionStrategy = this.overlay
130
+ .position()
131
+ .global()
132
+ .centerVertically()
133
+ .right('16px')
134
+
135
+ this.overlayRef = this.overlay.create({
136
+ positionStrategy,
137
+ hasBackdrop: false,
138
+ backdropClass: 'cdk-overlay-transparent-backdrop',
139
+ scrollStrategy: this.scrollStrategies.noop(),
140
+ })
141
+
142
+ this.overlayRef.attach(
143
+ new TemplatePortal(this.templateRef(), this.viewContainerRef)
144
+ )
145
+ this.overlayRef.backdropClick().subscribe(() => this.closeOverlay())
146
+ }
147
+
148
+ closeOverlay() {
149
+ this.clearInputs()
150
+ this.overlayRef?.dispose()
151
+ this.overlayRef = null
152
+ }
153
+
154
+ ngOnDestroy() {
155
+ this.closeOverlay()
156
+ }
157
+
158
+ submit() {
159
+ if (!this.isFormValid()) return
160
+ const defaultLang =
161
+ getOptionalEditorConfig()?.NEW_RECORD_DEFAULT_LANGUAGE ??
162
+ this.translate.currentLang
163
+ const onlineResource: OnlineLinkResource = {
164
+ type: 'link',
165
+ url: new URL(this.url()),
166
+ description: this.translate.instant('notify.reuse.link.description'),
167
+ }
168
+ const contact: Individual = {
169
+ email: this.email(),
170
+ role: 'point_of_contact',
171
+ organization: { name: this.me()?.organisation },
172
+ }
173
+ const reuseRecord: ReuseRecord = {
174
+ kind: 'reuse',
175
+ reuseType: 'application',
176
+ uniqueIdentifier: null,
177
+ title: this.title(),
178
+ abstract: '',
179
+ overviews: [],
180
+ ownerOrganization: {
181
+ name: 'Owner organization',
182
+ },
183
+ recordUpdated: new Date(),
184
+ updateFrequency: 'unknown',
185
+ otherLanguages: [],
186
+ defaultLanguage: defaultLang,
187
+ topics: [],
188
+ keywords: [],
189
+ licenses: [],
190
+ legalConstraints: [NOT_KNOWN_CONSTRAINT],
191
+ securityConstraints: [],
192
+ otherConstraints: [],
193
+ contacts: [],
194
+ contactsForResource: [contact],
195
+ lineage: '',
196
+ sourceRecords: [
197
+ {
198
+ uuid: this.record()?.uniqueIdentifier ?? '',
199
+ title: this.record()?.title ?? '',
200
+ },
201
+ ],
202
+ associatedRecords: [],
203
+ onlineResources: [onlineResource],
204
+ spatialExtents: [],
205
+ temporalExtents: [],
206
+ }
207
+ this.loading.set(true)
208
+ this.recordsRepository.saveRecord(reuseRecord, undefined, false).subscribe({
209
+ next: (uniqueIdentifier) => {
210
+ this.loading.set(false)
211
+ this.closeOverlay()
212
+ navigateToLightEdit(
213
+ this.reuseFormUrl,
214
+ this.locationStrategy.getBaseHref(),
215
+ uniqueIdentifier
216
+ )
217
+ },
218
+ error: (err) => {
219
+ this.loading.set(false)
220
+ this.notificationsService.showNotification(
221
+ {
222
+ type: 'error',
223
+ title: this.translate.instant('notify.reuse.form.error.title'),
224
+ text: this.translate.instant('notify.reuse.form.error.body'),
225
+ },
226
+ undefined,
227
+ err
228
+ )
229
+ },
230
+ })
231
+ }
21
232
  }
@@ -0,0 +1,16 @@
1
+ export const navigateToLightEdit = (
2
+ reuseFormUrl: string,
3
+ baseHref: string,
4
+ uniqueIdentifier: string
5
+ ) => {
6
+ const baseUrl = `${reuseFormUrl ?? ''}`.replace(/\/+$/, '')
7
+ // Normalisation du baseHref pour éviter les doubles /
8
+ const baseHrefClean = baseHref.replace(/^\/+|\/+$/g, '')
9
+ const prefix = baseHrefClean ? `/${baseHrefClean}` : ''
10
+ const rawRedirectUrl = `${window.location.origin}${prefix}/reuse/${uniqueIdentifier}`
11
+ const redirect_on_leave = encodeURIComponent(rawRedirectUrl)
12
+ window.open(
13
+ `${baseUrl}/light-edit/${uniqueIdentifier}?redirect_on_leave=${redirect_on_leave}`,
14
+ '_self'
15
+ )
16
+ }
@@ -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'
@@ -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'