From a36bec20c7fa4ed7e7d0ec8a451bb982ec293cde Mon Sep 17 00:00:00 2001 From: Sebastian-Ion TINCU <Sebastian-Ion.TINCU@ext.ec.europa.eu> Date: Mon, 24 Jun 2024 07:13:02 +0200 Subject: [PATCH] EDELIVERY-11590 SMP UI Improvements Breadcrumbs Refactor existing code. --- .../breadcrumb-item.component.html | 15 +++- .../breadcrumb-item.component.ts | 18 ++--- .../sidenav/navigation-model.service.ts | 79 ++++++++++++++----- 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.html b/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.html index 060f05687..4906e5502 100644 --- a/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.html +++ b/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.html @@ -1,10 +1,17 @@ -<a - class="smp-breadcrumb-item" +<a *ngIf="clickable" class="smp-breadcrumb-item" (click)="triggerClickEvent()" [matTooltip]="description"> + <ng-container [ngTemplateOutlet]="content"></ng-container> +</a> + +<div *ngIf="!clickable" class="smp-breadcrumb-item non-clickable"> + <ng-container [ngTemplateOutlet]="content"></ng-container> +</div> + +<ng-template #content> <div class="smp-breadcrumb-arrow top" [ngClass]="{'smp-breadcrumb-item-selected': value.selected}"></div> <div class="smp-breadcrumb-arrow bottom" [ngClass]="{'smp-breadcrumb-item-selected': value.selected}"></div> - <div class="smp-breadcrumb-content" > + <div class="smp-breadcrumb-content"> <mat-icon *ngIf="icon" style="vertical-align: middle;">{{icon}}</mat-icon> <span>{{name}}</span> </div> -</a> +</ng-template> diff --git a/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.ts b/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.ts index 19c7c9045..8780f83c0 100644 --- a/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.ts +++ b/smp-angular/src/app/window/breadcrumb/breadcrumb-item/breadcrumb-item.component.ts @@ -3,7 +3,7 @@ import {NavigationNode} from "../../sidenav/navigation-model.service"; /** - * Top page navigation bar Breadcrumb- side navigation panel of the DomiSMP. The component shows all tools/pages according to user role and permissions + * Top page navigation bar Breadcrumbs - side navigation panel of the DomiSMP. The component shows all tools/pages according to user role and permissions * * @author Joze Rihtarsic * @since 5.0 @@ -18,23 +18,23 @@ export class BreadcrumbItemComponent { @Output() onClickEvent: EventEmitter<NavigationNode> = new EventEmitter(); @Input() value : NavigationNode; - - constructor() { - } - - get icon(){ + get icon() { return this.value.icon; } - get name(){ + + get name() { return this.value.name; } - get description(){ + get description() { return this.value.code; } triggerClickEvent() { - this.onClickEvent.emit(this.value); + this.clickable && this.onClickEvent.emit(this.value); } + get clickable(): boolean { + return this.value.clickable; + } } diff --git a/smp-angular/src/app/window/sidenav/navigation-model.service.ts b/smp-angular/src/app/window/sidenav/navigation-model.service.ts index 5a7f24406..bacda9300 100644 --- a/smp-angular/src/app/window/sidenav/navigation-model.service.ts +++ b/smp-angular/src/app/window/sidenav/navigation-model.service.ts @@ -32,7 +32,6 @@ let PUBLIC_NAVIGATION_TREE: NavigationNode = { icon: "find_in_page", tooltip: "Search registered resources", routerLink: "search-resources", - } ] } @@ -51,6 +50,7 @@ export interface NavigationNode { tooltip?: string; routerLink?: string; children?: NavigationNode[]; + clickable?: boolean; selected?: boolean; transient?: boolean; // if true then node must be ignored } @@ -107,8 +107,8 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { } select(node: NavigationNode) { - let targetNode = this.findLeaf(node); + if (targetNode === this.selected) { console.log("Already selected skip"); return @@ -122,29 +122,53 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { this.selected = targetNode this.selected.selected = true; this.selectedPath = this.findPathForNode(this.selected, this.rootNode); + this.markNodesAsClickable(this.selectedPath); this.selectedPathSubject.next(this.selectedPath); - let navigationPath: string[] = this.getNavigationPath(this.selectedPath); - // navigate to selected path + // navigate to selected path + let navigationPath: string[] = this.getNavigationPath(this.selectedPath); this.router.navigate(navigationPath); } else { this.selectedPathSubject.next(null); } } + private markNodesAsClickable(selectedPath: NavigationNode[]) { + if (selectedPath) { + // reset all nodes (maybe some previously marked as non-clickable) + selectedPath.forEach(value => value.clickable = true); + + if (selectedPath.length) { + let leafIndex = selectedPath.length - 1; + + // mark the selected leaf as non-clickable + selectedPath[leafIndex].clickable = false; + + // mark the parent of the first leaf in a menu as non-clickable + let parent = this.findParent(selectedPath[leafIndex]); + if (parent && parent.children && parent.children[0] == selectedPath[leafIndex]) { + parent.clickable = false; + } + + // mark the root parent as non-clickable when selecting the very first leaf in a three level tree + let userRootLeaf = this.getDeepestLeaf(this.rootNode); + if (userRootLeaf == selectedPath[leafIndex] && selectedPath.length == 3) { + this.rootNode.clickable = false; + } + } + } + } + selectPreviousNode() { this.select(this.previousSelected) } - public reset() { this.rootNode = PUBLIC_NAVIGATION_TREE; this.data = this.rootNode.children; this.select(this.rootNode) - } - protected getNavigationPath(path: NavigationNode[]): string [] { return path.map(node => node.routerLink); } @@ -159,12 +183,31 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { } protected noTargetChildren(targetNode: NavigationNode): boolean { - if (!targetNode || !targetNode.children || targetNode.children.length == 0) { - return true; + return this.findSiblings(targetNode).length == 0; + } + + protected findSiblings(node:NavigationNode): NavigationNode[] { + if (!node || !node.children || node.children.length == 0) { + return []; + } + + return node.children.filter(node => !node.transient); + } + + protected findParent(node: NavigationNode): NavigationNode { + let path = this.findPathForNode(node, this.rootNode); + if (path) { + let parentIndex = path.indexOf(node) - 1; + return path[parentIndex]; } + return null; + } - let nonTransient = targetNode.children.filter(node => !node.transient); - return nonTransient.length == 0; + private getDeepestLeaf(currentNode: NavigationNode): NavigationNode { + if (this.noTargetChildren(currentNode)) { + return currentNode; + } + return this.getDeepestLeaf(currentNode.children[0]); } /** @@ -235,10 +278,13 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { } setNavigationTreeByPath(path: string[], userRootNode: NavigationNode) { - // find the node by the navigation + this.rootNode = userRootNode; + this.data = this.rootNode?.children; + this.selectStartNode(path, userRootNode); + } + private selectStartNode(path: string[], userRootNode: NavigationNode) { let startNode = userRootNode; - for (let index in path) { let pathSegment = path[index]; // the first node is empty - skip all empty nodes @@ -249,18 +295,13 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { } } } - - this.rootNode = userRootNode; - this.data = this.rootNode?.children; this.select(startNode); } - getSelectedPathObservable(): Observable<NavigationNode[]> { return this.selectedPathSubject.asObservable(); } - /** Add node as child of parent */ public add(node: NavigationNode, parent: NavigationNode) { // add root node @@ -362,9 +403,11 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { icon: "login", name: "Login", routerLink: "login", + clickable: true, selected: true, tooltip: "", transient: true, } } + } -- GitLab