Nested Menus (#1554)

* added initial submenu

* added submenu - needs a bit of more work

* removed admin and nonadmin action split

* the whole menu is build under the resetactions function

* removed download from seriesAction

* changed submenu layout
changed submenu toggle icon
fix for the hovering of submenu toggle

* moved the cdMarkForCheck in the subscribe block
This commit is contained in:
Korakot Santiudommongkol 2022-09-23 07:37:30 -07:00 committed by GitHub
parent dec6802f88
commit 3cdf8df1db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 310 additions and 185 deletions

View file

@ -130,7 +130,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this))
.filter(item => item.action !== Action.Edit);
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false});
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
this.libraryService.getLibraryType(this.libraryId).subscribe(type => {
this.libraryType = type;

View file

@ -2,10 +2,22 @@
<div ngbDropdown container="body" class="d-inline-block">
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle (click)="preventClick($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
<button ngbDropdownItem *ngFor="let action of nonAdminActions" (click)="performAction($event, action)">{{action.title}}</button>
<div class="dropdown-divider" *ngIf="nonAdminActions.length > 1 && adminActions.length > 1"></div>
<button ngbDropdownItem *ngFor="let action of adminActions" (click)="performAction($event, action)">{{action.title}}</button>
<ng-container *ngTemplateOutlet="submenu; context: { list: actions }"></ng-container>
</div>
</div>
<!-- IDEA: If we are not on desktop, then let's open a bottom drawer instead-->
<ng-template #submenu let-list="list">
<ng-container *ngFor="let action of list">
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 else submenuDropdown">
<button ngbDropdownItem *ngIf="willRenderAction(action)" (click)="performAction($event, action)">{{action.title}}</button>
</ng-container>
<ng-template #submenuDropdown>
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right" (mouseover)="preventClick($event); openSubmenu(action.title, subMenuHover)" (mouseleave)="preventClick($event)">
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{action.title}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
</div>
</div>
</ng-template>
</ng-container>
</ng-template>
</ng-container>

View file

@ -1,3 +1,28 @@
.dropdown-toggle:after {
content: none !important;
}
}
.submenu-toggle {
display: block;
width: 100%;
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
font-weight: 400;
text-align: inherit;
border: 0;
color: var(--dropdown-item-text-color);
background-color: var(--dropdown-item-bg-color);
&:hover {
color: var(--dropdown-item-text-color);
background-color: var(--dropdown-item-hover-bg-color);
cursor: pointer;
}
&:focus-visible {
color: var(--dropdown-item-text-color);
background-color: var(--dropdown-item-hover-bg-color);
}
}
.submenu-icon {
float: right;
padding: var(--bs-dropdown-item-padding-y) 0;
}

View file

@ -1,5 +1,8 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ActionItem } from 'src/app/_services/action-factory.service';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { take } from 'rxjs';
import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
@Component({
selector: 'app-card-actionables',
@ -16,16 +19,19 @@ export class CardActionablesComponent implements OnInit {
@Input() disabled: boolean = false;
@Output() actionHandler = new EventEmitter<ActionItem<any>>();
adminActions: ActionItem<any>[] = [];
nonAdminActions: ActionItem<any>[] = [];
isAdmin: boolean = false;
canDownload: boolean = false;
submenu: {[key: string]: NgbDropdown} = {};
constructor(private readonly cdRef: ChangeDetectorRef) { }
constructor(private readonly cdRef: ChangeDetectorRef, private accountService: AccountService) { }
ngOnInit(): void {
this.nonAdminActions = this.actions.filter(item => !item.requiresAdmin);
this.adminActions = this.actions.filter(item => item.requiresAdmin);
this.cdRef.markForCheck();
this.accountService.currentUser$.pipe(take(1)).subscribe((user) => {
if (!user) return;
this.isAdmin = this.accountService.hasAdminRole(user);
this.canDownload = this.accountService.hasDownloadRole(user);
this.cdRef.markForCheck();
});
}
preventClick(event: any) {
@ -41,4 +47,23 @@ export class CardActionablesComponent implements OnInit {
}
}
willRenderAction(action: ActionItem<any>): boolean {
return (action.requiresAdmin && this.isAdmin)
|| (action.action === Action.Download && (this.canDownload || this.isAdmin))
|| (!action.requiresAdmin && action.action !== Action.Download)
}
openSubmenu(actionTitle: string, subMenu: NgbDropdown) {
// We keep track when we open and when we get a request to open, if we have other keys, we close them and clear their keys
if (Object.keys(this.submenu).length > 0) {
const keys = Object.keys(this.submenu).filter(k => k !== actionTitle);
keys.forEach(key => {
this.submenu[key].close();
delete this.submenu[key];
});
}
this.submenu[actionTitle] = subMenu;
subMenu.open();
}
}