Skip to content

Commit

Permalink
feat(admin-ui): Allow default dashboard widget widths to be set
Browse files Browse the repository at this point in the history
Relates to #334
  • Loading branch information
michaelbromley committed Nov 18, 2020
1 parent aa835e8 commit 3e33bbc
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 }>>;
Original file line number Diff line number Diff line change
@@ -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)) {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -16,36 +16,23 @@ 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`;
case 4:
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;
}
}
18 changes: 12 additions & 6 deletions packages/admin-ui/src/lib/dashboard/src/default-widgets.ts
Original file line number Diff line number Diff line change
@@ -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,
},
};

0 comments on commit 3e33bbc

Please sign in to comment.