Browse Source

feat(admin-ui): Persist dashboard layout to localStorage

Relates to #334
Michael Bromley 5 years ago
parent
commit
15cae77563

+ 2 - 1
packages/admin-ui/src/lib/core/src/core.module.ts

@@ -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);

+ 28 - 13
packages/admin-ui/src/lib/core/src/providers/local-storage/local-storage.service.ts

@@ -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_';
 
 /**
@@ -20,7 +30,7 @@ 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));
     }
@@ -28,7 +38,10 @@ export class LocalStorageService {
     /**
      * 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);
     }
@@ -36,7 +49,7 @@ export class LocalStorageService {
     /**
      * 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));
     }
@@ -44,7 +57,7 @@ export class LocalStorageService {
     /**
      * 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;
@@ -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);
@@ -76,7 +91,7 @@ export class LocalStorageService {
         return key + path;
     }
 
-    private keyName(key: LocalStorageKey): string {
+    private keyName(key: keyof LocalStorageTypeMap): string {
         return PREFIX + key;
     }
 }

+ 1 - 0
packages/admin-ui/src/lib/dashboard/src/components/dashboard-widget/dashboard-widget.component.scss

@@ -6,6 +6,7 @@
 
 .card {
     margin-top: 0;
+    min-height: 200px;
 }
 
 .card-header {

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

@@ -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"

+ 33 - 7
packages/admin-ui/src/lib/dashboard/src/components/dashboard/dashboard.component.ts

@@ -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';
@@ -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 {
@@ -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;
     }
@@ -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], [])
@@ -101,5 +126,6 @@ export class DashboardComponent implements OnInit {
             width: item.width,
         }));
         this.widgetLayout = this.dashboardWidgetService.getWidgetLayout(newLayoutDef);
+        setTimeout(() => this.changedDetectorRef.markForCheck());
     }
 }

+ 0 - 3
packages/admin-ui/src/lib/dashboard/src/default-widgets.ts

@@ -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 } = {