geonetwork-ui 2.10.0-dev.02185426a → 2.10.0-dev.08a12212e

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 (61) hide show
  1. package/fesm2022/geonetwork-ui.mjs +272 -102
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +39 -18
  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/gn4.converter.ts +1 -0
  21. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +1 -1
  22. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +4 -0
  23. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +13 -0
  24. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +45 -1
  25. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +13 -1
  26. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +40 -0
  27. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +47 -1
  28. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +7 -3
  29. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +9 -20
  30. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +10 -0
  31. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -1
  32. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +6 -0
  33. package/src/libs/feature/editor/src/index.ts +1 -0
  34. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.html +7 -6
  35. package/src/libs/feature/editor/src/lib/components/contact-details/contact-details-form.component.ts +10 -8
  36. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +7 -0
  37. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +2 -0
  38. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -1
  39. package/src/libs/feature/editor/src/lib/fields.config.ts +44 -1
  40. package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +4 -0
  41. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  42. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.css +0 -0
  43. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.html +23 -0
  44. package/src/libs/feature/notify-reuse/src/lib/edit-delete-reuse-buttons/edit-delete-reuse-buttons.component.ts +114 -0
  45. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +111 -1
  46. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +221 -8
  47. package/src/libs/feature/notify-reuse/src/lib/utils/url.ts +16 -0
  48. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.css +0 -3
  49. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.html +11 -7
  50. package/src/libs/ui/inputs/src/lib/text-input/text-input.component.ts +56 -30
  51. package/src/libs/ui/widgets/src/lib/spinning-loader/spinning-loader.component.html +1 -1
  52. package/tailwind.base.css +0 -2
  53. package/translations/de.json +31 -2
  54. package/translations/en.json +36 -7
  55. package/translations/es.json +31 -2
  56. package/translations/fr.json +36 -7
  57. package/translations/it.json +32 -3
  58. package/translations/nl.json +31 -2
  59. package/translations/pt.json +31 -2
  60. package/translations/sk.json +31 -2
  61. package/material-styles.css +0 -483
@@ -44,6 +44,7 @@ import { FormFieldContactsComponent } from './form-field-contacts/form-field-con
44
44
  import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component'
45
45
  import { FormFieldOnlineLinkResourcesComponent } from './form-field-online-link-resources/form-field-online-link-resources.component'
46
46
  import { FormFieldOnlineResourcesComponent } from './form-field-online-resources/form-field-online-resources.component'
47
+ import { FormFieldOnlineSingleLinkResourceComponent } from './form-field-online-single-link-resource/form-field-online-single-link-resource.component'
47
48
  import { FormFieldOverviewsComponent } from './form-field-overviews/form-field-overviews.component'
48
49
  import { FormFieldRichComponent } from './form-field-rich/form-field-rich.component'
49
50
  import { FormFieldSimpleComponent } from './form-field-simple/form-field-simple.component'
@@ -78,6 +79,7 @@ import { FormFieldTopicsComponent } from './form-field-topics/form-field-topics.
78
79
  FormFieldContactsForResourceComponent,
79
80
  FormFieldOnlineResourcesComponent,
80
81
  FormFieldOnlineLinkResourcesComponent,
82
+ FormFieldOnlineSingleLinkResourceComponent,
81
83
  FormFieldContactsComponent,
82
84
  FormFieldConstraintsComponent,
83
85
  FormFieldConstraintsShortcutsComponent,
@@ -5,7 +5,7 @@
5
5
  ) {
6
6
  @if (evaluateExpression(section.hidden)() !== true) {
7
7
  <div
8
- class="flex flex-col gap-6 border p-8 rounded-[8px] shadow scroll-mt-[90px]"
8
+ class="flex flex-col gap-6 border p-8 rounded-[8px] shadow scroll-mt-[90px] bg-white"
9
9
  gnUiFieldFocus
10
10
  #sectionFocus="fieldFocus"
11
11
  >
@@ -8,7 +8,9 @@ export interface ISOTopic {
8
8
  }
9
9
 
10
10
  /**
11
- * This file contains the configuration of the fields that will be displayed in the editor.
11
+ * This file contains the configuration of the fields
12
+ * that will be displayed in the editor
13
+ * and the reuse light editor.
12
14
  */
13
15
 
14
16
  // keeping track of labels to not lose existing translations
@@ -281,6 +283,47 @@ export const DEFAULT_CONFIGURATION: EditorConfig = {
281
283
  ],
282
284
  }
283
285
 
286
+ // reduced configuration used by the light edition page for reuse records;
287
+ // the point of contact is edited outside the record form on that page
288
+ export const REUSE_LIGHT_CONFIGURATION: EditorConfig = {
289
+ pages: [
290
+ {
291
+ sections: [
292
+ {
293
+ hidden: false,
294
+ fields: [
295
+ {
296
+ model: 'title',
297
+ formFieldConfig: {
298
+ labelKey: marker('editor.record.form.field.title'),
299
+ },
300
+ },
301
+ {
302
+ model: 'abstract',
303
+ formFieldConfig: {
304
+ labelKey: marker('editor.record.form.field.abstract'),
305
+ },
306
+ },
307
+ {
308
+ model: 'onlineResources',
309
+ modelSpecifier: 'onlineResourceType:singleLink',
310
+ formFieldConfig: {
311
+ labelKey: marker('editor.record.form.field.reuseUrl'),
312
+ },
313
+ },
314
+ {
315
+ model: 'overviews',
316
+ formFieldConfig: {
317
+ labelKey: marker('editor.record.form.field.overviews'),
318
+ },
319
+ },
320
+ ],
321
+ },
322
+ ],
323
+ },
324
+ ],
325
+ }
326
+
284
327
  /**
285
328
  * OTHER SETTINGS
286
329
  */
@@ -19,12 +19,16 @@ export interface FormFieldConfig {
19
19
  // This is used for instance to target only certain online resources in a field
20
20
  type OnlineLinkResourceSpecifier = `onlineResourceType:link`
21
21
  type DatasetDistributionsSpecifier = `onlineResourceType:!link`
22
+ // Renders a single URL input bound to the first online resource of the record
23
+ // (FormFieldOnlineSingleLinkResourceComponent)
24
+ type OnlineSingleLinkResourceSpecifier = `onlineResourceType:singleLink`
22
25
  // When set on the `contacts` field, contacts are rendered as editable detail
23
26
  // fields (ContactDetailsFormComponent) instead of cards (ContactCardComponent)
24
27
  type EditableContactDetailsSpecifier = `contact:editableDetails`
25
28
  export type FieldModelSpecifier =
26
29
  | OnlineLinkResourceSpecifier
27
30
  | DatasetDistributionsSpecifier
31
+ | OnlineSingleLinkResourceSpecifier
28
32
  | EditableContactDetailsSpecifier
29
33
 
30
34
  export type FormFieldComponentName =
@@ -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,234 @@
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
+ firstName: this.me()?.name,
170
+ lastName: this.me()?.surname,
171
+ email: this.email(),
172
+ role: 'point_of_contact',
173
+ organization: { name: this.me()?.organisation },
174
+ }
175
+ const reuseRecord: ReuseRecord = {
176
+ kind: 'reuse',
177
+ reuseType: 'application',
178
+ uniqueIdentifier: null,
179
+ title: this.title(),
180
+ abstract: '',
181
+ overviews: [],
182
+ ownerOrganization: {
183
+ name: 'Owner organization',
184
+ },
185
+ recordUpdated: new Date(),
186
+ updateFrequency: 'unknown',
187
+ otherLanguages: [],
188
+ defaultLanguage: defaultLang,
189
+ topics: [],
190
+ keywords: [],
191
+ licenses: [],
192
+ legalConstraints: [NOT_KNOWN_CONSTRAINT],
193
+ securityConstraints: [],
194
+ otherConstraints: [],
195
+ contacts: [],
196
+ contactsForResource: [contact],
197
+ lineage: '',
198
+ sourceRecords: [
199
+ {
200
+ uuid: this.record()?.uniqueIdentifier ?? '',
201
+ title: this.record()?.title ?? '',
202
+ },
203
+ ],
204
+ associatedRecords: [],
205
+ onlineResources: [onlineResource],
206
+ spatialExtents: [],
207
+ temporalExtents: [],
208
+ }
209
+ this.loading.set(true)
210
+ this.recordsRepository.saveRecord(reuseRecord, undefined, false).subscribe({
211
+ next: (uniqueIdentifier) => {
212
+ this.loading.set(false)
213
+ this.closeOverlay()
214
+ navigateToLightEdit(
215
+ this.reuseFormUrl,
216
+ this.locationStrategy.getBaseHref(),
217
+ uniqueIdentifier
218
+ )
219
+ },
220
+ error: (err) => {
221
+ this.loading.set(false)
222
+ this.notificationsService.showNotification(
223
+ {
224
+ type: 'error',
225
+ title: this.translate.instant('notify.reuse.form.error.title'),
226
+ text: this.translate.instant('notify.reuse.form.error.body'),
227
+ },
228
+ undefined,
229
+ err
230
+ )
231
+ },
232
+ })
233
+ }
21
234
  }
@@ -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,3 +0,0 @@
1
- .invalid {
2
- border-color: var(--color-secondary);
3
- }
@@ -1,13 +1,17 @@
1
1
  <input
2
2
  #input
3
3
  class="gn-ui-text-input"
4
- [ngClass]="extraClass"
5
- type="text"
6
- [value]="value"
4
+ [class]="extraClass()"
5
+ [ngClass]="{
6
+ 'border-red-400 focus:border-red-800 hover:border-red-800 text-red-800':
7
+ !isValid(),
8
+ }"
9
+ [type]="type()"
10
+ [(ngModel)]="inputModel"
7
11
  (change)="handleChange($event)"
8
12
  (input)="handleChange($event)"
9
- [placeholder]="placeholder"
10
- [attr.aria-label]="placeholder"
11
- [attr.required]="required || null"
12
- [disabled]="disabled"
13
+ [placeholder]="placeholder()"
14
+ [attr.aria-label]="placeholder()"
15
+ [attr.required]="required() || null"
16
+ [disabled]="disabled()"
13
17
  />