Random Stuff (#3798)
This commit is contained in:
parent
574cf4b78e
commit
70f00895e8
63 changed files with 659 additions and 567 deletions
|
@ -1,31 +1,39 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component, DestroyRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
|
||||
import {AccountService} from 'src/app/_services/account.service';
|
||||
import {ActionableEntity, ActionItem} from 'src/app/_services/action-factory.service';
|
||||
import {AsyncPipe, NgTemplateOutlet} from "@angular/common";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {Breakpoint, UtilityService} from "../../shared/_services/utility.service";
|
||||
import {ActionableModalComponent} from "../actionable-modal/actionable-modal.component";
|
||||
import {User} from "../../_models/user";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-card-actionables',
|
||||
imports: [NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, DynamicListPipe, TranslocoDirective, AsyncPipe, NgTemplateOutlet],
|
||||
templateUrl: './card-actionables.component.html',
|
||||
styleUrls: ['./card-actionables.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
selector: 'app-card-actionables',
|
||||
imports: [
|
||||
NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem,
|
||||
DynamicListPipe, TranslocoDirective, AsyncPipe, NgTemplateOutlet
|
||||
],
|
||||
templateUrl: './card-actionables.component.html',
|
||||
styleUrls: ['./card-actionables.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CardActionablesComponent implements OnInit {
|
||||
export class CardActionablesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly accountService = inject(AccountService);
|
||||
|
@ -37,58 +45,69 @@ export class CardActionablesComponent implements OnInit {
|
|||
|
||||
@Input() iconClass = 'fa-ellipsis-v';
|
||||
@Input() btnClass = '';
|
||||
@Input() actions: ActionItem<any>[] = [];
|
||||
@Input() inputActions: ActionItem<any>[] = [];
|
||||
@Input() labelBy = 'card';
|
||||
/**
|
||||
* Text to display as if actionable was a button
|
||||
*/
|
||||
@Input() label = '';
|
||||
@Input() disabled: boolean = false;
|
||||
|
||||
@Input() entity: ActionableEntity = null;
|
||||
/**
|
||||
* This will only emit when the action is clicked and the entity is null. Otherwise, the entity callback handler will be invoked.
|
||||
*/
|
||||
@Output() actionHandler = new EventEmitter<ActionItem<any>>();
|
||||
|
||||
|
||||
isAdmin: boolean = false;
|
||||
canDownload: boolean = false;
|
||||
canPromote: boolean = false;
|
||||
actions: ActionItem<ActionableEntity>[] = [];
|
||||
currentUser: User | undefined = undefined;
|
||||
submenu: {[key: string]: NgbDropdown} = {};
|
||||
private closeTimeout: any = null;
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((user) => {
|
||||
if (!user) return;
|
||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||
this.canDownload = this.accountService.hasDownloadRole(user);
|
||||
this.canPromote = this.accountService.hasPromoteRole(user);
|
||||
|
||||
// We want to avoid an empty menu when user doesn't have access to anything
|
||||
if (!this.isAdmin && this.actions.filter(a => !a.requiresAdmin).length === 0) {
|
||||
this.actions = [];
|
||||
}
|
||||
|
||||
this.currentUser = user;
|
||||
this.actions = this.inputActions.filter(a => this.willRenderAction(a, user!));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.actions = this.inputActions.filter(a => this.willRenderAction(a, this.currentUser!));
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.cancelCloseSubmenus();
|
||||
}
|
||||
|
||||
preventEvent(event: any) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
performAction(event: any, action: ActionItem<any>) {
|
||||
performAction(event: any, action: ActionItem<ActionableEntity>) {
|
||||
this.preventEvent(event);
|
||||
|
||||
if (typeof action.callback === 'function') {
|
||||
this.actionHandler.emit(action);
|
||||
if (this.entity === null) {
|
||||
this.actionHandler.emit(action);
|
||||
} else {
|
||||
action.callback(action, this.entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
willRenderAction(action: ActionItem<any>) {
|
||||
return (action.requiresAdmin && this.isAdmin)
|
||||
|| (action.action === Action.Download && (this.canDownload || this.isAdmin))
|
||||
|| (!action.requiresAdmin && action.action !== Action.Download)
|
||||
|| (action.action === Action.Promote && (this.canPromote || this.isAdmin))
|
||||
|| (action.action === Action.UnPromote && (this.canPromote || this.isAdmin))
|
||||
;
|
||||
/**
|
||||
* The user has required roles (or no roles defined) and action shouldRender returns true
|
||||
* @param action
|
||||
* @param user
|
||||
*/
|
||||
willRenderAction(action: ActionItem<ActionableEntity>, user: User) {
|
||||
return (!action.requiredRoles?.length || this.accountService.hasAnyRole(user, action.requiredRoles)) && action.shouldRender(action, this.entity, user);
|
||||
}
|
||||
|
||||
shouldRenderSubMenu(action: ActionItem<any>, dynamicList: null | Array<any>) {
|
||||
|
@ -109,13 +128,41 @@ export class CardActionablesComponent implements OnInit {
|
|||
}
|
||||
|
||||
closeAllSubmenus() {
|
||||
Object.keys(this.submenu).forEach(key => {
|
||||
this.submenu[key].close();
|
||||
// Clear any existing timeout to avoid race conditions
|
||||
if (this.closeTimeout) {
|
||||
clearTimeout(this.closeTimeout);
|
||||
}
|
||||
|
||||
// Set a new timeout to close submenus after a short delay
|
||||
this.closeTimeout = setTimeout(() => {
|
||||
Object.keys(this.submenu).forEach(key => {
|
||||
this.submenu[key].close();
|
||||
delete this.submenu[key];
|
||||
});
|
||||
});
|
||||
}, 100); // Small delay to prevent premature closing (dropdown tunneling)
|
||||
}
|
||||
|
||||
performDynamicClick(event: any, action: ActionItem<any>, dynamicItem: any) {
|
||||
cancelCloseSubmenus() {
|
||||
if (this.closeTimeout) {
|
||||
clearTimeout(this.closeTimeout);
|
||||
this.closeTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
hasRenderableChildren(action: ActionItem<ActionableEntity>, user: User): boolean {
|
||||
if (!action.children || action.children.length === 0) return false;
|
||||
|
||||
for (const child of action.children) {
|
||||
const dynamicList = child.dynamicList;
|
||||
if (dynamicList !== undefined) return true; // Dynamic list gets rendered if loaded
|
||||
|
||||
if (this.willRenderAction(child, user)) return true;
|
||||
if (child.children?.length && this.hasRenderableChildren(child, user)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
performDynamicClick(event: any, action: ActionItem<ActionableEntity>, dynamicItem: any) {
|
||||
action._extra = dynamicItem;
|
||||
this.performAction(event, action);
|
||||
}
|
||||
|
@ -124,6 +171,7 @@ export class CardActionablesComponent implements OnInit {
|
|||
this.preventEvent(event);
|
||||
|
||||
const ref = this.modalService.open(ActionableModalComponent, {fullscreen: true, centered: true});
|
||||
ref.componentInstance.entity = this.entity;
|
||||
ref.componentInstance.actions = this.actions;
|
||||
ref.componentInstance.willRenderAction = this.willRenderAction.bind(this);
|
||||
ref.componentInstance.shouldRenderSubMenu = this.shouldRenderSubMenu.bind(this);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue