Browse Source

feat(admin-ui): Allow default dashboard widget widths to be set

Relates to #334
Michael Bromley 5 years ago
parent
commit
3e33bbc819

+ 8 - 2
packages/admin-ui/src/lib/core/src/providers/dashboard-widget/dashboard-widget-types.ts

@@ -1,5 +1,7 @@
 import { Type } from '@angular/core';
 
+export type DashboardWidgetWidth = 3 | 4 | 6 | 8 | 12;
+
 export interface DashboardWidgetConfig {
     /**
      * Used to specify the widget component. Supports both eager- and lazy-loading.
@@ -19,12 +21,16 @@ export interface DashboardWidgetConfig {
      */
     title?: string;
     /**
-     * The width of the widget, in terms of a Bootstrap-style 12-column grid.
+     * The supported widths of the widget, in terms of a Bootstrap-style 12-column grid.
+     * If omitted, then it is assumed the widget supports all widths.
      */
-    width: 3 | 4 | 6 | 12;
+    supportedWidths?: DashboardWidgetWidth[];
     /**
      * If set, the widget will only be displayed if the current user has all the
      * specified permissions.
      */
     requiresPermissions?: string[];
 }
+
+export type WidgetLayoutDefinition = Array<{ id: string; width: DashboardWidgetWidth }>;
+export type WidgetLayout = Array<Array<{ config: DashboardWidgetConfig; width: DashboardWidgetWidth }>>;

+ 68 - 19
packages/admin-ui/src/lib/core/src/providers/dashboard-widget/dashboard-widget.service.ts

@@ -1,17 +1,22 @@
 import { Injectable } from '@angular/core';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 
-import { DashboardWidgetConfig } from './dashboard-widget-types';
+import {
+    DashboardWidgetConfig,
+    DashboardWidgetWidth,
+    WidgetLayout,
+    WidgetLayoutDefinition,
+} from './dashboard-widget-types';
 
 /**
- * Registers a component to be used as a dashboard widget.
+ * Responsible for registering dashboard widget components and querying for layouts.
  */
 @Injectable({
     providedIn: 'root',
 })
 export class DashboardWidgetService {
     private registry = new Map<string, DashboardWidgetConfig>();
-    private layout: ReadonlyArray<string> = [];
+    private layoutDef: WidgetLayoutDefinition = [];
 
     registerWidget(id: string, config: DashboardWidgetConfig) {
         if (this.registry.has(id)) {
@@ -21,30 +26,74 @@ export class DashboardWidgetService {
         this.registry.set(id, config);
     }
 
-    setDefaultLayout(ids: string[] | ReadonlyArray<string>) {
-        this.layout = ids;
+    setDefaultLayout(layout: WidgetLayoutDefinition) {
+        this.layoutDef = layout;
     }
 
-    getDefaultLayout(): ReadonlyArray<string> {
-        return this.layout;
+    getDefaultLayout(): WidgetLayoutDefinition {
+        return this.layoutDef;
     }
 
-    getWidgets(): DashboardWidgetConfig[] {
-        return this.layout
-            .map(id => {
+    getWidgetLayout(): WidgetLayout {
+        const intermediateLayout = this.layoutDef
+            .map(({ id, width }) => {
                 const config = this.registry.get(id);
                 if (!config) {
-                    // tslint:disable-next-line:no-console
-                    console.error(
-                        `No dashboard widget was found with the id "${id}"\nAvailable ids: ${[
-                            ...this.registry.keys(),
-                        ]
-                            .map(_id => `"${_id}"`)
-                            .join(', ')}`,
-                    );
+                    return this.idNotFound(id);
                 }
-                return config;
+                return { config, width: this.getValidWidth(id, config, width) };
             })
             .filter(notNullOrUndefined);
+
+        return this.buildLayout(intermediateLayout);
+    }
+
+    private idNotFound(id: string): undefined {
+        // tslint:disable-next-line:no-console
+        console.error(
+            `No dashboard widget was found with the id "${id}"\nAvailable ids: ${[...this.registry.keys()]
+                .map(_id => `"${_id}"`)
+                .join(', ')}`,
+        );
+        return;
+    }
+
+    private getValidWidth(
+        id: string,
+        config: DashboardWidgetConfig,
+        targetWidth: DashboardWidgetWidth,
+    ): DashboardWidgetWidth {
+        let adjustedWidth = targetWidth;
+        const supportedWidths = config.supportedWidths?.length
+            ? config.supportedWidths
+            : ([3, 4, 6, 8, 12] as DashboardWidgetWidth[]);
+        if (!supportedWidths.includes(targetWidth)) {
+            // Fall back to the largest supported width
+            const sortedWidths = supportedWidths.sort((a, b) => a - b);
+            const fallbackWidth = supportedWidths[sortedWidths.length - 1];
+            // tslint:disable-next-line:no-console
+            console.error(
+                `The "${id}" widget does not support the specified width (${targetWidth}).\nSupported widths are: [${sortedWidths.join(
+                    ', ',
+                )}].\nUsing (${fallbackWidth}) instead.`,
+            );
+            adjustedWidth = fallbackWidth;
+        }
+        return adjustedWidth;
+    }
+
+    private buildLayout(intermediateLayout: WidgetLayout[number]): WidgetLayout {
+        const layout: WidgetLayout = [];
+        let row: WidgetLayout[number] = [];
+        for (const { config, width } of intermediateLayout) {
+            const rowSize = row.reduce((size, c) => size + c.width, 0);
+            if (12 < rowSize + width) {
+                layout.push(row);
+                row = [];
+            }
+            row.push({ config, width });
+        }
+        layout.push(row);
+        return layout;
     }
 }

+ 12 - 3
packages/admin-ui/src/lib/core/src/providers/dashboard-widget/register-dashboard-widget.ts

@@ -1,8 +1,13 @@
 import { APP_INITIALIZER, FactoryProvider } from '@angular/core';
 
-import { DashboardWidgetConfig } from './dashboard-widget-types';
+import { DashboardWidgetConfig, WidgetLayoutDefinition } from './dashboard-widget-types';
 import { DashboardWidgetService } from './dashboard-widget.service';
 
+/**
+ * @description
+ * Registers a dashboard widget. Once registered, the widget can be set as part of the default
+ * (using {@link setDashboardWidgetLayout}).
+ */
 export function registerDashboardWidget(id: string, config: DashboardWidgetConfig): FactoryProvider {
     return {
         provide: APP_INITIALIZER,
@@ -14,12 +19,16 @@ export function registerDashboardWidget(id: string, config: DashboardWidgetConfi
     };
 }
 
-export function setDashboardWidgetLayout(ids: string[] | ReadonlyArray<string>): FactoryProvider {
+/**
+ * @description
+ * Sets the default widget layout for the Admin UI dashboard.
+ */
+export function setDashboardWidgetLayout(layoutDef: WidgetLayoutDefinition): FactoryProvider {
     return {
         provide: APP_INITIALIZER,
         multi: true,
         useFactory: (dashboardWidgetService: DashboardWidgetService) => () => {
-            dashboardWidgetService.setDefaultLayout(ids);
+            dashboardWidgetService.setDefaultLayout(layoutDef);
         },
         deps: [DashboardWidgetService],
     };

+ 1 - 1
packages/admin-ui/src/lib/dashboard/src/components/dashboard/dashboard.component.html

@@ -1,5 +1,5 @@
 <div class="clr-row dashboard-row" *ngFor="let row of widgetLayout">
     <div *ngFor="let widget of row" [ngClass]="getClassForWidth(widget.width)">
-        <vdr-dashboard-widget *vdrIfPermissions="widget.requiresPermissions || null" [widgetConfig]="widget"></vdr-dashboard-widget>
+        <vdr-dashboard-widget *vdrIfPermissions="widget.config.requiresPermissions || null" [widgetConfig]="widget.config"></vdr-dashboard-widget>
     </div>
 </div>

+ 6 - 19
packages/admin-ui/src/lib/dashboard/src/components/dashboard/dashboard.component.ts

@@ -1,8 +1,8 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
-import { DashboardWidgetConfig, DashboardWidgetService } from '@vendure/admin-ui/core';
+import { DashboardWidgetConfig, DashboardWidgetService, DashboardWidgetWidth } from '@vendure/admin-ui/core';
 import { assertNever } from '@vendure/common/lib/shared-utils';
 
-type WidgetLayout = DashboardWidgetConfig[][];
+type WidgetLayout = Array<Array<{ width: DashboardWidgetWidth; config: DashboardWidgetConfig }>>;
 
 @Component({
     selector: 'vdr-dashboard',
@@ -16,10 +16,10 @@ export class DashboardComponent implements OnInit {
     constructor(private dashboardWidgetService: DashboardWidgetService) {}
 
     ngOnInit() {
-        this.widgetLayout = this.buildLayout(this.dashboardWidgetService.getWidgets());
+        this.widgetLayout = this.dashboardWidgetService.getWidgetLayout();
     }
 
-    getClassForWidth(width: DashboardWidgetConfig['width']): string {
+    getClassForWidth(width: DashboardWidgetWidth): string {
         switch (width) {
             case 3:
                 return `clr-col-12 clr-col-sm-6 clr-col-lg-3`;
@@ -27,25 +27,12 @@ export class DashboardComponent implements OnInit {
                 return `clr-col-12 clr-col-sm-6 clr-col-lg-4`;
             case 6:
                 return `clr-col-12 clr-col-lg-6`;
+            case 8:
+                return `clr-col-12 clr-col-lg-8`;
             case 12:
                 return `clr-col-12`;
             default:
                 assertNever(width);
         }
     }
-
-    private buildLayout(widgetConfigs: DashboardWidgetConfig[]): WidgetLayout {
-        const layout: WidgetLayout = [];
-        let row: DashboardWidgetConfig[] = [];
-        for (const config of widgetConfigs) {
-            const rowSize = row.reduce((size, c) => size + c.width, 0);
-            if (12 < rowSize + config.width) {
-                layout.push(row);
-                row = [];
-            }
-            row.push(config);
-        }
-        layout.push(row);
-        return layout;
-    }
 }

+ 12 - 6
packages/admin-ui/src/lib/dashboard/src/default-widgets.ts

@@ -1,34 +1,40 @@
 import { APP_INITIALIZER, FactoryProvider } from '@angular/core';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { DashboardWidgetConfig, DashboardWidgetService, Permission } from '@vendure/admin-ui/core';
+import {
+    DashboardWidgetConfig,
+    DashboardWidgetService,
+    Permission,
+    WidgetLayoutDefinition,
+} from '@vendure/admin-ui/core';
 
 import { LatestOrdersWidgetComponent } from './widgets/latest-orders-widget/latest-orders-widget.component';
 import { OrderSummaryWidgetComponent } from './widgets/order-summary-widget/order-summary-widget.component';
 import { TestWidgetComponent } from './widgets/test-widget/test-widget.component';
 import { WelcomeWidgetComponent } from './widgets/welcome-widget/welcome-widget.component';
 
-export const DEFAULT_DASHBOARD_WIDGET_LAYOUT = ['welcome', 'orderSummary', 'latestOrders'] as const;
+export const DEFAULT_DASHBOARD_WIDGET_LAYOUT: WidgetLayoutDefinition = [
+    { id: 'welcome', width: 12 },
+    { id: 'orderSummary', width: 6 },
+    { id: 'latestOrders', width: 6 },
+];
 
 export const DEFAULT_WIDGETS: { [id: string]: DashboardWidgetConfig } = {
     welcome: {
         loadComponent: () => WelcomeWidgetComponent,
-        width: 12,
     },
     orderSummary: {
         title: _('dashboard.orders-summary'),
         loadComponent: () => OrderSummaryWidgetComponent,
-        width: 6,
         requiresPermissions: [Permission.ReadOrder],
     },
     latestOrders: {
         title: _('dashboard.latest-orders'),
         loadComponent: () => LatestOrdersWidgetComponent,
-        width: 6,
+        supportedWidths: [6, 8, 12],
         requiresPermissions: [Permission.ReadOrder],
     },
     testWidget: {
         title: 'Test Widget',
         loadComponent: () => TestWidgetComponent,
-        width: 4,
     },
 };