diff --git a/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html b/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html index 6a1b918ab..a61140368 100644 --- a/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html +++ b/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.html @@ -1,26 +1,59 @@ - - - - - - - + + +
+
    + record.action.duplicate + record.action.delete +
+
+
+ +
+ {{ + 'editor.record.delete.confirmation.title' | translate + }} + {{ + 'editor.record.delete.confirmation.message' | translate + }} +
+ {{ + 'editor.record.delete.confirmation.confirmText' | translate + }} + {{ + 'editor.record.delete.confirmation.cancelText' | translate + }} +
+
+
+
diff --git a/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts b/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts index 415889095..2f3b99ec6 100644 --- a/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts +++ b/libs/ui/search/src/lib/results-table/action-menu/action-menu.component.ts @@ -1,4 +1,6 @@ +import { CommonModule } from '@angular/common' import { + ChangeDetectorRef, Component, EventEmitter, Input, @@ -9,13 +11,9 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog' import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu' import { ConfirmationDialogComponent } from '@geonetwork-ui/ui/elements' import { ButtonComponent } from '@geonetwork-ui/ui/inputs' -import { TranslateModule, TranslateService } from '@ngx-translate/core' -import { - NgIconComponent, - provideIcons, - provideNgIconsConfig, -} from '@ng-icons/core' -import { matMoreVert } from '@ng-icons/material-icons/baseline' +import { TranslateModule } from '@ngx-translate/core' + +type ActionMenuPage = 'mainMenu' | 'deleteMenu' @Component({ selector: 'gn-ui-action-menu', @@ -23,58 +21,38 @@ import { matMoreVert } from '@ng-icons/material-icons/baseline' styleUrls: ['./action-menu.component.css'], standalone: true, imports: [ + CommonModule, ButtonComponent, MatMenuModule, MatDialogModule, ConfirmationDialogComponent, TranslateModule, - NgIconComponent, ], - providers: [provideIcons({ matMoreVert })], }) export class ActionMenuComponent { @Input() canDuplicate: boolean @Input() canDelete: boolean @Output() duplicate = new EventEmitter() @Output() delete = new EventEmitter() + @Output() closeActionMenu = new EventEmitter() @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger - constructor( - public dialog: MatDialog, - private translateService: TranslateService - ) {} + sectionDisplayed: ActionMenuPage = 'mainMenu' + + constructor(public dialog: MatDialog, private cdr: ChangeDetectorRef) {} openMenu() { this.trigger.openMenu() } - openDeleteConfirmationDialog() { - const dialogRef = this.dialog.open(ConfirmationDialogComponent, { - data: { - title: this.translateService.instant( - 'editor.record.delete.confirmation.title' - ), - message: this.translateService.instant( - 'editor.record.delete.confirmation.message' - ), - confirmText: this.translateService.instant( - 'editor.record.delete.confirmation.confirmText' - ), - cancelText: this.translateService.instant( - 'editor.record.delete.confirmation.cancelText' - ), - }, - restoreFocus: false, - }) + displayMainMenu() { + this.sectionDisplayed = 'mainMenu' + this.cdr.markForCheck() + } - // Manually restore focus to the menu trigger since the element that - // opens the dialog won't be in the DOM any more when the dialog closes. - dialogRef.afterClosed().subscribe((confirmed) => { - this.trigger.focus() - if (confirmed) { - this.delete.emit() - } - }) + displayDeleteMenu() { + this.sectionDisplayed = 'deleteMenu' + this.cdr.markForCheck() } } diff --git a/libs/ui/search/src/lib/results-table/results-table.component.html b/libs/ui/search/src/lib/results-table/results-table.component.html index b11980b3f..b91ae31a2 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.html +++ b/libs/ui/search/src/lib/results-table/results-table.component.html @@ -138,13 +138,25 @@ - - + + + + + + diff --git a/libs/ui/search/src/lib/results-table/results-table.component.spec.ts b/libs/ui/search/src/lib/results-table/results-table.component.spec.ts index 916ee0f16..572a59341 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.spec.ts +++ b/libs/ui/search/src/lib/results-table/results-table.component.spec.ts @@ -158,19 +158,13 @@ describe('ResultsTableComponent', () => { beforeEach(() => { recordToBeDuplicated = null - component.duplicateRecord.subscribe((r) => (recordToBeDuplicated = r)) + component.duplicateRecord.subscribe((r) => { + recordToBeDuplicated = r + }) }) it('emits a duplicateRecord event', () => { - const menuButton = fixture.debugElement.query( - By.css('[data-test="record-menu-button"]') - ).nativeElement as HTMLButtonElement - menuButton.click() - fixture.detectChanges() - const duplicateButton = fixture.debugElement.query( - By.css('[data-test="record-menu-duplicate-button"]') - ).nativeElement as HTMLButtonElement - duplicateButton.click() + component.handleDuplicate(datasetRecordsFixture()[0]) expect(JSON.stringify(recordToBeDuplicated)).toEqual( JSON.stringify(datasetRecordsFixture()[0]) ) diff --git a/libs/ui/search/src/lib/results-table/results-table.component.ts b/libs/ui/search/src/lib/results-table/results-table.component.ts index 26c20f54e..edd269538 100644 --- a/libs/ui/search/src/lib/results-table/results-table.component.ts +++ b/libs/ui/search/src/lib/results-table/results-table.component.ts @@ -1,5 +1,15 @@ import { CommonModule } from '@angular/common' -import { Component, EventEmitter, Input, Output } from '@angular/core' +import { + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + Output, + QueryList, + ViewChildren, + ViewContainerRef, +} from '@angular/core' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { FieldSort, @@ -20,6 +30,14 @@ import { TranslateModule } from '@ngx-translate/core' import { ActionMenuComponent } from './action-menu/action-menu.component' import { NgIconComponent, provideIcons } from '@ng-icons/core' import { iconoirUser } from '@ng-icons/iconoir' +import { + CdkConnectedOverlay, + CdkOverlayOrigin, + Overlay, + OverlayRef, +} from '@angular/cdk/overlay' +import { TemplatePortal } from '@angular/cdk/portal' +import { matMoreVert } from '@ng-icons/material-icons/baseline' @Component({ selector: 'gn-ui-results-table', @@ -35,8 +53,10 @@ import { iconoirUser } from '@ng-icons/iconoir' BadgeComponent, ActionMenuComponent, NgIconComponent, + CdkOverlayOrigin, + CdkConnectedOverlay, ], - providers: [provideIcons({ iconoirUser })], + providers: [provideIcons({ iconoirUser, matMoreVert })], }) export class ResultsTableComponent { @Input() records: CatalogRecord[] = [] @@ -56,6 +76,67 @@ export class ResultsTableComponent { [CatalogRecord[], boolean] >() + @ViewChildren('actionMenuButton', { read: ElementRef }) + actionMenuButtons!: QueryList + private overlayRef!: OverlayRef + + isActionMenuOpen = false + + constructor( + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private cdr: ChangeDetectorRef + ) {} + + openActionMenu(item, template) { + this.isActionMenuOpen = true + const index = this.records.indexOf(item) + const buttonElement = this.actionMenuButtons.toArray()[index] + + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo(buttonElement) + .withFlexibleDimensions(true) + .withPush(true) + .withPositions([ + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + }, + { + originX: 'end', + originY: 'top', + overlayX: 'end', + overlayY: 'bottom', + }, + ]) + + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'cdk-overlay-transparent-backdrop', + positionStrategy: positionStrategy, + scrollStrategy: this.overlay.scrollStrategies.reposition(), + }) + + const portal = new TemplatePortal(template, this.viewContainerRef) + + this.overlayRef.attach(portal) + + this.overlayRef.backdropClick().subscribe(() => { + this.closeActionMenu() + }) + } + + closeActionMenu() { + if (this.overlayRef) { + this.isActionMenuOpen = false + this.overlayRef.dispose() + this.cdr.markForCheck() + } + } + dateToString(date: Date): string { return date?.toLocaleDateString(undefined, { year: 'numeric', @@ -102,6 +183,7 @@ export class ResultsTableComponent { handleDelete(item: unknown) { this.deleteRecord.emit(item as CatalogRecord) + this.closeActionMenu() } setSortBy(col: string, order: 'asc' | 'desc') { diff --git a/translations/de.json b/translations/de.json index a25584600..ecfc133cd 100644 --- a/translations/de.json +++ b/translations/de.json @@ -196,7 +196,7 @@ "editor.record.delete.confirmation.cancelText": "Stornieren", "editor.record.delete.confirmation.confirmText": "Löschen", "editor.record.delete.confirmation.message": "Sind Sie sicher, dass Sie diesen Datensatz löschen möchten?", - "editor.record.delete.confirmation.title": "Datensatz löschen?", + "editor.record.delete.confirmation.title": "Datensatz löschen", "editor.record.deleteError.body": "Der Datensatz konnte nicht gelöscht werden:", "editor.record.deleteError.closeMessage": "Verstanden", "editor.record.deleteError.title": "Fehler beim Löschen des Datensatzes", diff --git a/translations/en.json b/translations/en.json index 8f812703d..3c9925a55 100644 --- a/translations/en.json +++ b/translations/en.json @@ -195,8 +195,8 @@ "editor.form.placeKeywordWithoutExtent": "This keyword is not associated with a geographical extent", "editor.record.delete.confirmation.cancelText": "Cancel", "editor.record.delete.confirmation.confirmText": "Delete", - "editor.record.delete.confirmation.message": "Are you sure you want to delete this record?", - "editor.record.delete.confirmation.title": "Delete record?", + "editor.record.delete.confirmation.message": "Are you sure you want to delete this record ?", + "editor.record.delete.confirmation.title": "Delete record", "editor.record.deleteError.body": "The record could not be deleted:", "editor.record.deleteError.closeMessage": "Understood", "editor.record.deleteError.title": "Error deleting record", diff --git a/translations/fr.json b/translations/fr.json index ac64b0bb8..96b301279 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -196,7 +196,7 @@ "editor.record.delete.confirmation.cancelText": "Annuler", "editor.record.delete.confirmation.confirmText": "Supprimer", "editor.record.delete.confirmation.message": "Êtes-vous sûr de vouloir supprimer cette fiche ?", - "editor.record.delete.confirmation.title": "Supprimer la fiche ?", + "editor.record.delete.confirmation.title": "Supprimer la fiche", "editor.record.deleteError.body": "La fiche n'a pas pu être supprimée :", "editor.record.deleteError.closeMessage": "Compris", "editor.record.deleteError.title": "Erreur lors de la suppression",