All files / src/model si-widget-storage.ts

96.77% Statements 30/31
91.66% Branches 11/12
100% Functions 6/6
96.77% Lines 30/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159            2x                     2x   15x                 2x                                                                                           2x                           2x                   2x     30x       30x 30x       23x 16x 16x 7x 5x         7x                 2x 2x 2x       2x 2x 2x 1x   1x           20x 15x   5x     20x 20x     20x      
/**
 * Copyright (c) Siemens 2016 - 2026
 * SPDX-License-Identifier: MIT
 */
import { inject, InjectionToken } from '@angular/core';
import { MenuItem } from '@siemens/element-ng/common';
import { BehaviorSubject, Observable, of } from 'rxjs';
 
import type { SiFlexibleDashboardComponent } from '../components/flexible-dashboard/si-flexible-dashboard.component';
import type { provideDashboardToolbarItems } from './configuration';
import { DashboardToolbarItem } from './si-dashboard-toolbar.model';
import { WidgetConfig } from './widgets.model';
 
/**
 * Injection token to configure your widget store implementation. Use
 * `{ provide: SI_WIDGET_STORE, useClass: AppWidgetStorage }` in your app configuration.
 */
export const SI_WIDGET_STORE = new InjectionToken<SiWidgetStorage>(
  'Injection token to configure your widget store implementation.',
  { providedIn: 'root', factory: () => new SiDefaultWidgetStorage() }
);
 
/**
 * Widget storage api to provide the persistence layer of the widgets of a dashboard
 * (typically from a web service). The dashboard grid uses this API to load and save
 * the widget configurations. Applications using siemens-dashboard needs to implement
 * this abstract class and provide it in the module configuration.
 */
export abstract class SiWidgetStorage {
  /**
   * Returns an observable with the dashboard widget configuration. The dashboard subscribes to the
   * observable and updates when a new value emits.
   */
  abstract load(dashboardId?: string): Observable<WidgetConfig[]>;
 
  /**
   * Saves the given widget configuration. New widgets have `id`
   * generated through the `SiWidgetIdProvider.generateWidgetId` implementation
   * and if ids comes from backend then it is in the responsibility of the implementor to update
   * the ids of the new widgets with the ids returned from backend upon save. In addition,
   * the implementor needs to check if objects that have been available before are missing. These
   * widgets have been removed by the user. As a result of this method, the observables returned
   * by the `load()` method should emit the new widget config objects, before also returning them.
   * @param modifiedWidgets - The existing widget config objects to be saved.
   * @param addedWidgets - The new widget config objects to be saved.
   * @param removedWidgets - The widget config objects that have been removed.
   * @param dashboardId - The id of the dashboard if present to allow dashboard specific storage.
   * @returns An observable that emits the saved widget config objects with their ids.
   */
 
  abstract save(
    modifiedWidgets: WidgetConfig[],
    addedWidgets: WidgetConfig[],
    removedWidgets?: WidgetConfig[],
    dashboardId?: string
  ): Observable<WidgetConfig[]>;
 
  /**
   * Optional method to provide primary and secondary toolbar menu items.
   * @param dashboardId - The id of the dashboard if present to allow dashboard specific menu items.
   *
   * @deprecated use {@link provideDashboardToolbarItems} to provide common toolbar items.
   * Additionally configure individual dashboard actions via {@link SiFlexibleDashboardComponent.primaryEditActions} and {@link SiFlexibleDashboardComponent.secondaryEditActions} respectively.
   */
  getToolbarMenuItems?: (dashboardId?: string) => {
    primary: Observable<(MenuItem | DashboardToolbarItem)[]>;
    secondary: Observable<(MenuItem | DashboardToolbarItem)[]>;
  };
}
 
/**
 * The {@link SiDefaultWidgetStorage} uses this key to persist the dashboard
 * configurations in the session of local storage.
 */
export const STORAGE_KEY = 'dashboard-store';
 
/**
 * Injection token to optionally inject the {@link Storage} implementation
 * that will be used by the {@link SiDefaultWidgetStorage}. The default implementation
 * is {@link sessionStorage}.
 *
 * @example
 * The following shows how to provide the localStorage.
 * ```
 * providers: [..., { provide: DEFAULT_WIDGET_STORAGE_TOKEN, useValue: localStorage }]
 * ```
 *
 */
export const DEFAULT_WIDGET_STORAGE_TOKEN = new InjectionToken<Storage>(
  'default storage for dashboard store'
);
 
/**
 * Default implementation of {@link SiWidgetStorage} that uses the
 * {@link Storage} implementation provided by the {@link DEFAULT_WIDGET_STORAGE_TOKEN}.
 * In general, it persists the dashboard's configurations in the Browser's session or local
 * storage. *
 */
export class SiDefaultWidgetStorage extends SiWidgetStorage {
  storage: Storage;
 
  private map = new Map<string, BehaviorSubject<WidgetConfig[]>>();
  private widgets?: BehaviorSubject<WidgetConfig[]>;
 
  constructor() {
    super();
    this.storage = inject(DEFAULT_WIDGET_STORAGE_TOKEN, { optional: true }) ?? sessionStorage;
  }
 
  load(dashboardId?: string): Observable<WidgetConfig[]> {
    if (!dashboardId) {
      this.widgets ??= new BehaviorSubject<WidgetConfig[]>(this.loadFromStorage());
      return this.widgets;
    } else if (!this.map.has(dashboardId)) {
      this.map.set(
        dashboardId,
        new BehaviorSubject<WidgetConfig[]>(this.loadFromStorage(dashboardId))
      );
    }
    return this.map.get(dashboardId)!;
  }
 
  save(
    modifiedWidgets: WidgetConfig[],
    addedWidgets: WidgetConfig[],
    removedWidgets?: WidgetConfig[],
    dashboardId?: string
  ): Observable<WidgetConfig[]> {
    const newWidgets = [...addedWidgets, ...modifiedWidgets];
    this.update(newWidgets, dashboardId);
    return of(newWidgets);
  }
 
  protected update(widgetConfigs: WidgetConfig[], dashboardId?: string): void {
    const widgets$ = this.load(dashboardId) as BehaviorSubject<WidgetConfig[]>;
    widgets$.next(widgetConfigs);
    if (!dashboardId) {
      this.storage.setItem(STORAGE_KEY, JSON.stringify(widgetConfigs));
    } else {
      this.storage.setItem(`${STORAGE_KEY}-${dashboardId}`, JSON.stringify(widgetConfigs));
    }
  }
 
  protected loadFromStorage(dashboardId?: string): WidgetConfig[] {
    let configStr;
    if (!dashboardId) {
      configStr = this.storage.getItem(STORAGE_KEY);
    } else {
      configStr = this.storage.getItem(`${STORAGE_KEY}-${dashboardId}`);
    }
 
    let widgetConfigs: WidgetConfig[] = [];
    Iif (configStr) {
      widgetConfigs = JSON.parse(configStr);
    }
    return widgetConfigs;
  }
}