Ver código fonte

feat(admin-ui): Allow status badges to be defined for NavMenuItems

Michael Bromley 5 anos atrás
pai
commit
97e209c4c3

+ 10 - 0
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html

@@ -7,6 +7,9 @@
                 [class.collapsible]="section.collapsible"
                 *vdrIfPermissions="section.requiresPermission"
             >
+                <ng-container *ngIf="navBuilderService.sectionBadges[section.id] | async as sectionBadge">
+                    <div *ngIf="sectionBadge !== 'none'" class="status-badge" [class]="sectionBadge"></div>
+                </ng-container>
                 <input [id]="section.id" type="checkbox" [checked]="section.collapsedByDefault" />
                 <label [for]="section.id">{{ section.label | translate }}</label>
                 <ul class="nav-list">
@@ -17,6 +20,13 @@
                             [routerLink]="getRouterLink(item)"
                             routerLinkActive="active"
                         >
+                            <ng-container *ngIf="item.statusBadge | async as itemBadge">
+                                <div
+                                    *ngIf="itemBadge.type !== 'none'"
+                                    class="status-badge"
+                                    [class]="itemBadge.type"
+                                ></div>
+                            </ng-container>
                             <clr-icon [attr.shape]="item.icon || 'block'" size="20"></clr-icon>
                             {{ item.label | translate }}
                         </a>

+ 36 - 1
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss

@@ -1,4 +1,4 @@
-@import "variables";
+@import 'variables';
 
 :host {
     // flex: 0 0 auto;
@@ -14,3 +14,38 @@ nav.sidenav {
 .nav-list clr-icon {
     margin-right: 12px;
 }
+
+.nav-group,
+.nav-link {
+    position: relative;
+}
+
+.status-badge {
+    width: 10px;
+    height: 10px;
+    position: absolute;
+    border-radius: 50%;
+    border: 1px solid $color-grey-200;
+
+    &.info {
+        background-color: $color-primary-600;
+    }
+    &.success {
+        background-color: $color-success-500;
+    }
+    &.warning {
+        background-color: $color-warning-500;
+    }
+    &.error {
+        background-color: $color-error-400;
+    }
+}
+
+.nav-group .status-badge {
+    left: 10px;
+    top: 6px;
+}
+.nav-link .status-badge {
+    left: 25px;
+    top: 3px;
+}

+ 0 - 25
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.spec.ts

@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { MainNavComponent } from './main-nav.component';
-
-describe('MainNavComponent', () => {
-    /*let component: MainNavComponent;
-    let fixture: ComponentFixture<MainNavComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ MainNavComponent ]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(MainNavComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });*/
-});

+ 17 - 0
packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts

@@ -4,6 +4,22 @@ import { Observable } from 'rxjs';
 import { DataService } from '../../data/providers/data.service';
 import { NotificationService } from '../notification/notification.service';
 
+export type NavMenuBadgeType = 'none' | 'info' | 'success' | 'warning' | 'error';
+
+/**
+ * A color-coded notification badge which will be displayed by the
+ * NavMenuItem's icon.
+ */
+export interface NavMenuBadge {
+    type: NavMenuBadgeType;
+    /**
+     * If true, the badge will propagate to the NavMenuItem's
+     * parent section, displaying a notification badge next
+     * to the section name.
+     */
+    propagateToSection?: boolean;
+}
+
 /**
  * A NavMenuItem is a menu item in the main (left-hand side) nav
  * bar.
@@ -15,6 +31,7 @@ export interface NavMenuItem {
     onClick?: (event: MouseEvent) => void;
     icon?: string;
     requiresPermission?: string;
+    statusBadge?: Observable<NavMenuBadge>;
 }
 
 /**

+ 41 - 9
packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts

@@ -1,11 +1,18 @@
 import { APP_INITIALIZER, Injectable, Provider } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
+import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
 import { map, shareReplay } from 'rxjs/operators';
 
 import { Permission } from '../../common/generated-types';
 
-import { ActionBarItem, NavMenuItem, NavMenuSection, RouterLinkDefinition } from './nav-builder-types';
+import {
+    ActionBarItem,
+    NavMenuBadgeType,
+    NavMenuItem,
+    NavMenuSection,
+    RouterLinkDefinition,
+} from './nav-builder-types';
 
 /**
  * @description
@@ -126,6 +133,7 @@ export function addActionBarItem(config: ActionBarItem): Provider {
 export class NavBuilderService {
     navMenuConfig$: Observable<NavMenuSection[]>;
     actionBarConfig$: Observable<ActionBarItem[]>;
+    sectionBadges: { [sectionId: string]: Observable<NavMenuBadgeType> } = {};
 
     private initialNavMenuConfig$ = new BehaviorSubject<NavMenuSection[]>([]);
     private addedNavMenuSections: Array<{ config: NavMenuSection; before?: string }> = [];
@@ -195,19 +203,19 @@ export class NavBuilderService {
         const itemAdditions$ = of(this.addedNavMenuItems);
 
         const combinedConfig$ = combineLatest(this.initialNavMenuConfig$, sectionAdditions$).pipe(
-            map(([initalConfig, additions]) => {
+            map(([initialConfig, additions]) => {
                 for (const { config, before } of additions) {
                     if (!config.requiresPermission) {
                         config.requiresPermission = Permission.Authenticated;
                     }
-                    const index = initalConfig.findIndex(c => c.id === before);
+                    const index = initialConfig.findIndex(c => c.id === before);
                     if (-1 < index) {
-                        initalConfig.splice(index, 0, config);
+                        initialConfig.splice(index, 0, config);
                     } else {
-                        initalConfig.push(config);
+                        initialConfig.push(config);
                     }
                 }
-                return initalConfig;
+                return initialConfig;
             }),
             shareReplay(1),
         );
@@ -219,9 +227,7 @@ export class NavBuilderService {
                     if (!section) {
                         // tslint:disable-next-line:no-console
                         console.error(
-                            `Could not add menu item "${item.config.id}", section "${
-                                item.sectionId
-                            }" does not exist`,
+                            `Could not add menu item "${item.config.id}", section "${item.sectionId}" does not exist`,
                         );
                     } else {
                         const index = section.items.findIndex(i => i.id === item.before);
@@ -232,6 +238,32 @@ export class NavBuilderService {
                         }
                     }
                 }
+
+                // Aggregate any badges defined for the nav items in each section
+                for (const section of sections) {
+                    const itemBadgeStatuses = section.items
+                        .map(i => i.statusBadge)
+                        .filter(notNullOrUndefined);
+                    this.sectionBadges[section.id] = combineLatest(itemBadgeStatuses).pipe(
+                        map(badges => {
+                            const propagatingBadges = badges.filter(b => b.propagateToSection);
+                            if (propagatingBadges.length === 0) {
+                                return 'none';
+                            }
+                            const statuses = propagatingBadges.map(b => b.type);
+                            if (statuses.includes('error')) {
+                                return 'error';
+                            } else if (statuses.includes('warning')) {
+                                return 'warning';
+                            } else if (statuses.includes('info')) {
+                                return 'info';
+                            } else {
+                                return 'none';
+                            }
+                        }),
+                    );
+                }
+
                 return sections;
             }),
         );