Skip to content

Commit

Permalink
Merge pull request geonetwork#1024 from geonetwork/ME-rework-options-…
Browse files Browse the repository at this point in the history
…menu

[Editor]: Rework action-menu
  • Loading branch information
cmoinier authored Oct 25, 2024
2 parents dbb45b4 + 633452d commit 09f416f
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,59 @@
<gn-ui-button
type="light"
[matMenuTriggerFor]="menu"
(buttonClick)="openMenu()"
data-test="record-menu-button"
>
<ng-icon name="matMoreVert"></ng-icon>
</gn-ui-button>
<mat-menu #menu="matMenu">
<button
mat-menu-item
[disabled]="!canDuplicate"
(click)="duplicate.emit()"
data-test="record-menu-duplicate-button"
>
<span translate>record.action.duplicate</span>
</button>
<button
mat-menu-item
[disabled]="!canDelete"
(click)="openDeleteConfirmationDialog()"
data-test="record-menu-delete-button"
>
<span translate>record.action.delete</span>
</button>
</mat-menu>
<ng-container [ngSwitch]="sectionDisplayed">
<ng-container *ngSwitchCase="'mainMenu'">
<div
data-test="mainMenuSection"
class="mt-2 border border-gray-100 p-2 flex items-center bg-white shadow-2xl rounded-2xl"
>
<ul class="flex flex-col gap-2 w-full">
<gn-ui-button
type="light"
extraClass="flex flex-row items-center gap-2 w-full justify-start"
(buttonClick)="duplicate.emit()"
[disabled]="!canDuplicate"
data-test="record-menu-duplicate-button"
><span translate>record.action.duplicate</span></gn-ui-button
>
<gn-ui-button
type="light"
extraClass="flex flex-row items-center gap-2 w-full justify-start"
(buttonClick)="displayDeleteMenu()"
[disabled]="!canDelete"
data-test="record-menu-delete-button"
><span translate>record.action.delete</span></gn-ui-button
>
</ul>
</div>
</ng-container>
<ng-container *ngSwitchCase="'deleteMenu'">
<div
data-test="deleteMenuSection"
class="w-72 p-6 flex flex-col gap-3 mt-2 border border-gray-100 bg-white shadow-2xl rounded-2xl"
>
<span class="text-lg font-bold text-center">{{
'editor.record.delete.confirmation.title' | translate
}}</span>
<span class="text-center">{{
'editor.record.delete.confirmation.message' | translate
}}</span>
<div class="flex flex-row gap-8 justify-center">
<gn-ui-button
(buttonClick)="delete.emit()"
cdkFocusInitial
type="primary"
data-cy="confirm-button"
[style.--gn-ui-button-width]="'100px'"
>{{
'editor.record.delete.confirmation.confirmText' | translate
}}</gn-ui-button
>
<gn-ui-button
[style.--gn-ui-button-width]="'100px'"
(buttonClick)="closeActionMenu.emit()"
>{{
'editor.record.delete.confirmation.cancelText' | translate
}}</gn-ui-button
>
</div>
</div>
</ng-container>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { CommonModule } from '@angular/common'
import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
Expand All @@ -9,72 +11,48 @@ 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',
templateUrl: './action-menu.component.html',
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<void>()
@Output() delete = new EventEmitter<void>()
@Output() closeActionMenu = new EventEmitter<void>()

@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()
}
}
24 changes: 18 additions & 6 deletions libs/ui/search/src/lib/results-table/results-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,25 @@
<gn-ui-interactive-table-column>
<ng-template #header> </ng-template>
<ng-template #cell let-item>
<gn-ui-action-menu
[canDuplicate]="canDuplicate(item)"
[canDelete]="canDelete(item)"
(duplicate)="handleDuplicate(item)"
(delete)="handleDelete(item)"
<gn-ui-button
cdkOverlayOrigin
#actionMenuButton
(buttonClick)="openActionMenu(item, template)"
type="light"
data-test="record-menu-button"
>
</gn-ui-action-menu>
<ng-icon name="matMoreVert"></ng-icon>
</gn-ui-button>
<ng-template #template>
<gn-ui-action-menu
[canDuplicate]="canDuplicate(item)"
[canDelete]="canDelete(item)"
(duplicate)="handleDuplicate(item)"
(delete)="handleDelete(item)"
(closeActionMenu)="closeActionMenu()"
>
</gn-ui-action-menu>
</ng-template>
</ng-template>
</gn-ui-interactive-table-column>
</gn-ui-interactive-table>
Original file line number Diff line number Diff line change
Expand Up @@ -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])
)
Expand Down
86 changes: 84 additions & 2 deletions libs/ui/search/src/lib/results-table/results-table.component.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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',
Expand All @@ -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[] = []
Expand All @@ -56,6 +76,67 @@ export class ResultsTableComponent {
[CatalogRecord[], boolean]
>()

@ViewChildren('actionMenuButton', { read: ElementRef })
actionMenuButtons!: QueryList<ElementRef>
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',
Expand Down Expand Up @@ -102,6 +183,7 @@ export class ResultsTableComponent {

handleDelete(item: unknown) {
this.deleteRecord.emit(item as CatalogRecord)
this.closeActionMenu()
}

setSortBy(col: string, order: 'asc' | 'desc') {
Expand Down
2 changes: 1 addition & 1 deletion translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 09f416f

Please sign in to comment.