Skip to content

Commit

Permalink
feat(admin-ui): Persist dashboard layout to localStorage
Browse files Browse the repository at this point in the history
Relates to #334
  • Loading branch information
michaelbromley committed Nov 20, 2020
1 parent 9a52bdf commit 15cae77
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 25 deletions.
3 changes: 2 additions & 1 deletion packages/admin-ui/src/lib/core/src/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export class CoreModule {
.join(', ')}]`,
);
}
const uiLanguage = availableLanguages.includes(lastLanguage) ? lastLanguage : defaultLanguage;
const uiLanguage =
lastLanguage && availableLanguages.includes(lastLanguage) ? lastLanguage : defaultLanguage;
this.localStorageService.set('uiLanguageCode', uiLanguage);
this.i18nService.setLanguage(uiLanguage);
this.i18nService.setDefaultLanguage(defaultLanguage);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Location } from '@angular/common';
import { Injectable } from '@angular/core';

export type LocalStorageKey =
| 'activeChannelToken'
| 'authToken'
| 'uiLanguageCode'
| 'orderListLastCustomFilters';
export type LocalStorageLocationBasedKey = 'shippingTestOrder' | 'shippingTestAddress';
import { LanguageCode } from '../../common/generated-types';
import { WidgetLayoutDefinition } from '../dashboard-widget/dashboard-widget-types';

export type LocalStorageTypeMap = {
activeChannelToken: string;
authToken: string;
uiLanguageCode: LanguageCode;
orderListLastCustomFilters: any;
dashboardWidgetLayout: WidgetLayoutDefinition;
};

export type LocalStorageLocationBasedTypeMap = {
shippingTestOrder: any;
shippingTestAddress: any;
};

const PREFIX = 'vnd_';

/**
Expand All @@ -20,31 +30,34 @@ export class LocalStorageService {
/**
* Set a key-value pair in the browser's LocalStorage
*/
public set(key: LocalStorageKey, value: any): void {
public set<K extends keyof LocalStorageTypeMap>(key: K, value: LocalStorageTypeMap[K]): void {
const keyName = this.keyName(key);
localStorage.setItem(keyName, JSON.stringify(value));
}

/**
* Set a key-value pair specific to the current location (url)
*/
public setForCurrentLocation(key: LocalStorageLocationBasedKey, value: any) {
public setForCurrentLocation<K extends keyof LocalStorageLocationBasedTypeMap>(
key: K,
value: LocalStorageLocationBasedTypeMap[K],
) {
const compositeKey = this.getLocationBasedKey(key);
this.set(compositeKey as any, value);
}

/**
* Set a key-value pair in the browser's SessionStorage
*/
public setForSession(key: LocalStorageKey, value: any): void {
public setForSession<K extends keyof LocalStorageTypeMap>(key: K, value: LocalStorageTypeMap[K]): void {
const keyName = this.keyName(key);
sessionStorage.setItem(keyName, JSON.stringify(value));
}

/**
* Get the value of the given key from the SessionStorage or LocalStorage.
*/
public get(key: LocalStorageKey): any {
public get<K extends keyof LocalStorageTypeMap>(key: K): LocalStorageTypeMap[K] | null {
const keyName = this.keyName(key);
const item = sessionStorage.getItem(keyName) || localStorage.getItem(keyName);
let result: any;
Expand All @@ -60,12 +73,14 @@ export class LocalStorageService {
/**
* Get the value of the given key for the current location (url)
*/
public getForCurrentLocation(key: LocalStorageLocationBasedKey): any {
public getForCurrentLocation<K extends keyof LocalStorageLocationBasedTypeMap>(
key: K,
): LocalStorageLocationBasedTypeMap[K] {
const compositeKey = this.getLocationBasedKey(key);
return this.get(compositeKey as any);
}

public remove(key: LocalStorageKey): void {
public remove(key: keyof LocalStorageTypeMap): void {
const keyName = this.keyName(key);
sessionStorage.removeItem(keyName);
localStorage.removeItem(keyName);
Expand All @@ -76,7 +91,7 @@ export class LocalStorageService {
return key + path;
}

private keyName(key: LocalStorageKey): string {
private keyName(key: keyof LocalStorageTypeMap): string {
return PREFIX + key;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

.card {
margin-top: 0;
min-height: 200px;
}

.card-header {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div cdkDropListGroup>
<div
class="clr-row dashboard-row"
*ngFor="let row of widgetLayout; index as rowIndex"
*ngFor="let row of widgetLayout; index as rowIndex; trackBy: trackRow"
cdkDropList
(cdkDropListDropped)="drop($event)"
cdkDropListOrientation="horizontal"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import {
DashboardWidgetConfig,
DashboardWidgetService,
DashboardWidgetWidth,
LocalStorageService,
WidgetLayout,
WidgetLayoutDefinition,
} from '@vendure/admin-ui/core';
Expand All @@ -20,11 +21,15 @@ export class DashboardComponent implements OnInit {
availableWidgetIds: string[];
private readonly deletionMarker = '__delete__';

constructor(private dashboardWidgetService: DashboardWidgetService) {}
constructor(
private dashboardWidgetService: DashboardWidgetService,
private localStorageService: LocalStorageService,
private changedDetectorRef: ChangeDetectorRef,
) {}

ngOnInit() {
this.widgetLayout = this.dashboardWidgetService.getWidgetLayout();
this.availableWidgetIds = this.dashboardWidgetService.getAvailableIds();
this.widgetLayout = this.initLayout(this.availableWidgetIds);
}

getClassForWidth(width: DashboardWidgetWidth): string {
Expand Down Expand Up @@ -53,6 +58,11 @@ export class DashboardComponent implements OnInit {
this.recalculateLayout();
}

trackRow(index: number, row: WidgetLayout[number]) {
const id = row.map(item => `${item.id}:${item.width}`).join('|');
return id;
}

trackRowItem(index: number, item: WidgetLayout[number][number]) {
return item.config;
}
Expand Down Expand Up @@ -84,14 +94,29 @@ export class DashboardComponent implements OnInit {
}

drop(event: CdkDragDrop<{ index: number }>) {
const previousLayoutRow = this.widgetLayout[event.previousContainer.data.index];
const newLayoutRow = this.widgetLayout[event.container.data.index];
const { currentIndex, previousIndex, previousContainer, container } = event;
if (previousIndex === currentIndex && previousContainer.data.index === container.data.index) {
// Nothing changed
return;
}
const previousLayoutRow = this.widgetLayout[previousContainer.data.index];
const newLayoutRow = this.widgetLayout[container.data.index];

previousLayoutRow.splice(event.previousIndex, 1);
newLayoutRow.splice(event.currentIndex, 0, event.item.data);
previousLayoutRow.splice(previousIndex, 1);
newLayoutRow.splice(currentIndex, 0, event.item.data);
this.recalculateLayout();
}

private initLayout(availableIds: string[]): WidgetLayout {
const savedLayoutDef = this.localStorageService.get('dashboardWidgetLayout');
let layoutDef: WidgetLayoutDefinition | undefined;
if (savedLayoutDef) {
// validate all the IDs from the saved layout are still available
layoutDef = savedLayoutDef.filter(item => availableIds.includes(item.id));
}
return this.dashboardWidgetService.getWidgetLayout(layoutDef);
}

private recalculateLayout() {
const flattened = this.widgetLayout
.reduce((flat, row) => [...flat, ...row], [])
Expand All @@ -101,5 +126,6 @@ export class DashboardComponent implements OnInit {
width: item.width,
}));
this.widgetLayout = this.dashboardWidgetService.getWidgetLayout(newLayoutDef);
setTimeout(() => this.changedDetectorRef.markForCheck());
}
}
3 changes: 0 additions & 3 deletions packages/admin-ui/src/lib/dashboard/src/default-widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ export const DEFAULT_DASHBOARD_WIDGET_LAYOUT: WidgetLayoutDefinition = [
{ id: 'welcome', width: 12 },
{ id: 'orderSummary', width: 6 },
{ id: 'latestOrders', width: 6 },
{ id: 'testWidget', width: 4 },
{ id: 'testWidget', width: 4 },
{ id: 'testWidget', width: 4 },
];

export const DEFAULT_WIDGETS: { [id: string]: DashboardWidgetConfig } = {
Expand Down

0 comments on commit 15cae77

Please sign in to comment.